Dynamic 2D Water in Unity

Share on facebook
Facebook
Share on google
Google+
Share on twitter
Twitter
Share on linkedin
LinkedIn

In this tutorial we are going to simulate dynamic 2D water in Unity with simple physics calculation. This post is actually inspired by this article from tutsplus. The problem with that method is the result is not quite optimized (around 200 drawcalls). In this post, we will try to make the more optimized version.

Steps:

  • Create an empty Gameobject and attach a new script called DynamicWater2D.cs (or whatever name you want. really.)
  • Edit DynamicWater2D.cs  and declare all variables we need
[System.Serializable]
public struct Bound {
	public float top;
	public float right;
	public float bottom;
	public float left;
}

[Header ("Water Settings")]
public Bound bound;
public int quality;

public Material waterMaterial;
public GameObject splash;

private Vector3[] vertices;

private Mesh mesh;

[Header ("Physics Settings")]
public float springconstant = 0.02f;
public float damping = 0.1f;
public float spread = 0.1f;
public float collisionVelocityFactor = 0.04f;

float[] velocities;
float[] accelerations;
float[] leftDeltas;
float[] rightDeltas;

private float timer;
  • This part of code is totally different from tutsplus tutorial. We want to make it only 1 mesh rather than so many meshes joined together
private void GenerateMesh () {
	float range = (bound.right - bound.left) / (quality - 1);
	vertices = new Vector3[quality * 2];

	// generate vertices
	// top vertices
	for (int i = 0; i < quality; i++) {
		vertices[i] = new Vector3 (bound.left + (i * range), bound.top, 0);
	}
	// bottom vertices
	for (int i = 0; i < quality; i++) {
		vertices[i + quality] = new Vector2 (bound.left + (i * range), bound.bottom);
	}

	// generate tris. the algorithm is messed up but works. lol.
	int[] template = new int[6];
	template[0] = quality;
	template[1] = 0;
	template[2] = quality + 1;
	template[3] = 0;
	template[4] = 1;
	template[5] = quality + 1;

	int marker = 0;
	int[] tris = new int[((quality - 1) * 2) * 3];
	for (int i = 0; i < tris.Length; i++) {
		tris[i] = template[marker++]++;
		if (marker >= 6) marker = 0;
	}

	// generate mesh
	MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer> ();
	if (waterMaterial) meshRenderer.sharedMaterial = waterMaterial;

	MeshFilter meshFilter = gameObject.AddComponent<MeshFilter> ();

	mesh = new Mesh ();
	mesh.vertices = vertices;
	mesh.triangles = tris;
	mesh.RecalculateNormals ();
	mesh.RecalculateBounds ();

	// set up mesh
	meshFilter.mesh = mesh;
}
  • Don’t forget to add a Collider. This part is also different. We only need 1 big Collider rather than so many colliders placed side by side
private void SetBoxCollider2D () {
	BoxCollider2D col = gameObject.AddComponent<BoxCollider2D> ();
	col.isTrigger = true;
}
  • Now the update function. Pretty much the same with the tutorial from tutsplus. Except, I added a timer so it won’t update the physics all the time. It’s just for optimization sake.
private void Update () {
	// optimization. we don't want to calculate all of this on every update.
	if(timer <= 0) return;
	timer -= Time.deltaTime;

	// updating physics
	for (int i = 0; i < quality; i++) {
		float force = springconstant * (vertices[i].y - bound.top) + velocities[i] * damping;
		accelerations[i] = -force;
		vertices[i].y += velocities[i];
		velocities[i] += accelerations[i];
	}

	for (int i = 0; i < quality; i++) {
		if (i > 0) {
			leftDeltas[i] = spread * (vertices[i].y - vertices[i - 1].y);
			velocities[i - 1] += leftDeltas[i];
		}
		if (i < quality - 1) {
			rightDeltas[i] = spread * (vertices[i].y - vertices[i + 1].y);
			velocities[i + 1] += rightDeltas[i];
		}
	}

	// updating mesh
	mesh.vertices = vertices;
}
  • The last piece of code is adding functions to detect collisions with other objects. 
private void OnTriggerEnter2D(Collider2D col) {
	Rigidbody2D rb = col.GetComponent<Rigidbody2D>();
	Splash(col, rb.velocity.y * collisionVelocityFactor);
}

public void Splash (Collider2D col, float force) {
	timer = 3f;
	float radius = col.bounds.max.x - col.bounds.min.x;
	Vector2 center = new Vector2(col.bounds.center.x, bound.top) ;

	// instantiate splash particle
	GameObject splashGO = Instantiate(splash, new Vector3(center.x, center.y, 0), Quaternion.Euler(0,0,60));
	Destroy(splashGO, 2f);

	// applying physics
	for (int i = 0; i < quality; i++) {
		if (PointInsideCircle (vertices[i], center, radius)) {	
			velocities[i] = force;
			}
		}
	}

bool PointInsideCircle (Vector2 point, Vector2 center, float radius) {
	return Vector2.Distance (point, center) < radius;
}
  • That’s all for our DynamicWater2D.cs. Now, create a new script to test the simulation. We want to randomly spawn boxes that fall into the water.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ilhamhe {

	public class BoxSpawner : MonoBehaviour {
		public GameObject box;

		private void Update () {
			if (Input.GetKeyDown (KeyCode.Space)) {
				GameObject go = Instantiate (box, new Vector3 (Random.Range (-7f, 7f), Random.Range (3f, 5f), 0), Quaternion.identity) as GameObject;
				go.transform.localScale = new Vector2(Random.Range(.5f, 1.5f), Random.Range(.5f, 1.5f));
				Destroy(go, 3f);
			}
		}
	}

}
  • To test it out, attach BoxSpawner.cs to empty Gameobject and set up some prefabs for our boxes. The result will look like this:

That’s all for this tutorial. If you have any question, don’t hesitate to write a comment. And also, you can download the whole project here from Github: Unity 2D Dynamic Water

this article is contributed by:claygamestudio.com´╗┐

More to explorer

Week After Itch.io

Hello guys.  It’s been a week since our first demo release at Itch.io. At that time we thought that should be a

Sprite Flash in Unity

Flashing effect is commonly used in Shoot ‘Em Up and Beat ‘Em Up games. It gives clue to players that those enemies

Leave a Comment

Your email address will not be published. Required fields are marked *