Now we want our collider too:
- colliders[i] = new GameObject();
- colliders[i].name = "Trigger";
- colliders[i].AddComponent<BoxCollider2D>();
- colliders[i].transform.parent = transform;
- colliders[i].transform.position = new Vector3(Left + Width * (i + 0.5f) / edgecount, Top - 0.5f, 0);
- colliders[i].transform.localScale = new Vector3(Width / edgecount, 1, 1);
- colliders[i].GetComponent<BoxCollider2D>().isTrigger = true;
- colliders[i].AddComponent<WaterDetector>();
Now that we have our mesh, we need a function to update it as the water moves:
- void UpdateMeshes()
- {
- for (int i = 0; i < meshes.Length; i++)
- {
- Vector3[] Vertices = new Vector3[4];
- Vertices[0] = new Vector3(xpositions[i], ypositions[i], z);
- Vertices[1] = new Vector3(xpositions[i+1], ypositions[i+1], z);
- Vertices[2] = new Vector3(xpositions[i], bottom, z);
- Vertices[3] = new Vector3(xpositions[i+1], bottom, z);
- meshes[i].vertices = Vertices;
- }
- }
Our next task is to make the water itself work. We'll use FixedUpdate() to modify them all incrementally.
- void FixedUpdate()
- {
First, we're going to combine Hooke's Law with the Euler method to find the new positions, accelerations and velocities.
So, Hooke's Law is \(F = kx\), where \(F\) is the force produced by a spring (remember, we're modelling the surface of the water as a row of springs), \(k\) is the spring constant, and \(x\) is the displacement. Our displacement is simply going to be the y-position of each node minus the base height of the nodes.
Next, we add a damping factor proportional to the velocity of the force to dampen the force.
- for (int i = 0; i < xpositions.Length ; i++)
- {
- float force = springconstant * (ypositions[i] - baseheight) + velocities[i]*damping ;
- accelerations[i] = -force;
- ypositions[i] += velocities[i];
- velocities[i] += accelerations[i];
- Body.SetPosition(i, new Vector3(xpositions[i], ypositions[i], z));
- }
Note: I just assumed the mass of each node was 1 here, but you'll want to use:
- accelerations[i] = -force/mass;
Tip: For precise physics, we would use Verlet integration, but because we're adding damping, we can only use the Euler method, which is a lot quicker to calculate. Generally, though, the Euler method will exponentially introduce kinetic energy from nowhere into your physics system, so don't use it for anything precise.
Now we're going to create wave propagation. The following code is adapted from Michael Hoffman's tutorial.
- float[] leftDeltas = new float[xpositions.Length];
- float[] rightDeltas = new float[xpositions.Length];
Then, we'll check the height of the subsequent node against the height of the node we're checking, and put that difference into rightDeltas. (We'll also multiply all values by a spread constant).
- for (int j = 0; j < 8; j++)
- {
- for (int i = 0; i < xpositions.Length; i++)
- {
- if (i > 0)
- {
- leftDeltas[i] = spread * (ypositions[i] - ypositions[i-1]);
- velocities[i - 1] += leftDeltas[i];
- }
- if (i < xpositions.Length - 1)
- {
- rightDeltas[i] = spread * (ypositions[i] - ypositions[i + 1]);
- velocities[i + 1] += rightDeltas[i];
- }
- }
- }
- for (int i = 0; i < xpositions.Length; i++)
- {
- if (i > 0)
- {
- ypositions[i-1] += leftDeltas[i];
- }
- if (i < xpositions.Length - 1)
- {
- ypositions[i + 1] += rightDeltas[i];
- }
- }
Also, note that we contained this whole code in a loop, and ran it eight times. This is because we want to run this process in small doses multiple times, rather than one large calculation, which would be a lot less fluid.
Adding Splashes
Now we have water that flows, and it shows. Next, we need to be able to disturb the water!
For this, let's add a function called Splash(), which will check the x-position of the splash, and the velocity of whatever is hitting it. It should be public so that we can call it from our colliders later.
- public void Splash(float xpos, float velocity)
- {
- if (xpos >= xpositions[0] && xpos <= xpositions[xpositions.Length-1])
- {
- xpos -= xpositions[0];
- int index = Mathf.RoundToInt((xpositions.Length-1)*(xpos / (xpositions[xpositions.Length-1] - xpositions[0])));
- We take the position of the splash relative to the position of the left edge of the water (xpos).
- We divide this by the position of the right edge relative to the position of the left edge of the water.
- This gives us a fraction that tells us where the splash is. For instance, a splash three-quarters of the way along the body of water would give a value of 0.75.
- We multiply this by the number of edges and round this number, which gives us the node our splash was closest to.
- velocities[index] = velocity;
Note: You could change this line to whatever suits you. For instance, you could add the velocity to its current velocity, or you could use momentum instead of velocity and divide by your node's mass.
![]() |
First, we want to set the parameters of the splash to change with the velocity of the object.
- float lifetime = 0.93f + Mathf.Abs(velocity)*0.07f;
- splash.GetComponent<ParticleSystem>().startSpeed = 8+2*Mathf.Pow(Mathf.Abs(velocity),0.5f);
- splash.GetComponent<ParticleSystem>().startSpeed = 9 + 2 * Mathf.Pow(Mathf.Abs(velocity), 0.5f);
- splash.GetComponent<ParticleSystem>().startLifetime = lifetime;
You may be looking at that code and thinking, "Why has he set the startSpeed twice?", and you'd be right to wonder that. The problem is, we're using a particle system (Shuriken, provided with the project) that has its start speed set to "random between two constants". Unfortunately, we don't have much access over Shuriken by scripts, so to get that behaviour to work we have to set the value twice.
Now I'm going to add a line that you may or may not want to omit from your script:
- Vector3 position = new Vector3(xpositions[index],ypositions[index]-0.35f,5);
- Quaternion rotation = Quaternion.LookRotation(new Vector3(xpositions[Mathf.FloorToInt(xpositions.Length / 2)], baseheight + 8, 5) - position);
- Stick them in the background. (You can tell this by the z-position being 5).
- Tilt the particle system to always point towards the center of your body of water—this way, the particles won't splash onto the land.
- GameObject splish = Instantiate(splash,position,rotation) as GameObject;
- Destroy(splish, lifetime+0.3f);
- }
- }
Yes! We're finally done, right?
Collision Detection
Wrong! We need to detect our objects, or this was all for nothing!
Remember we added that script to all our colliders before? The one called WaterDetector?
Well we're going to make it now! We only want one function in it:
- void OnTriggerEnter2D(Collider2D Hit)
- {
- if (Hit.rigidbody2D != null)
- {
- transform.parent.GetComponent<Water>().Splash(transform.position.x, Hit.rigidbody2D.velocity.y*Hit.rigidbody2D.mass / 40f);
- }
- }
Remember again, I said you could either pass velocity or momentum, if you wanted it to be more physically accurate? Well here's where you have to pass the right one. If you multiply the object's y-velocity by its mass, you'll have its momentum. If you just want to use its velocity, get rid of the mass from that line.
Finally, you'll want to call SpawnWater() from somewhere. Let's do it at launch:
- void Start()
- {
- SpawnWater(-10,20,0,-10);
- }
![]() |
As an extra bonus, I've added a few lines of code to the top of SpawnWater().
- gameObject.AddComponent<BoxCollider2D>();
- gameObject.GetComponent<BoxCollider2D>().center = new Vector2(Left + Width / 2, (Top + Bottom) / 2);
- gameObject.GetComponent<BoxCollider2D>().size = new Vector2(Width, Top - Bottom);
- gameObject.GetComponent<BoxCollider2D>().isTrigger = true;
You'll want to make a function called OnTriggerStay2D() which takes a parameter of Collider2D Hit. Then, you can use a modified version of the spring formula we used before that checks the mass of the object, and add a force or velocity to your rigidbody2D to make it float in the water.
Make a Splash
In this tutorial, we implemented a simple water simulation for use in 2D games with simple physics code and a line renderer, mesh renderers, triggers and particles. Perhaps you will add wavy bodies of fluid water as an obstacle to your next platformer, ready for your characters to dive into or carefully cross with floating stepping stones, or maybe you could use this in a sailing or windsurfing game, or even a game where you simply skip rocks across the water from a sunny beach. Good luck!
Written by Alex Rose
If you found this post interesting, follow and support us.
Suggest for you:
Make VR Games in Unity with C# - Cardboard, Gear VR, Oculus
Learn to Code by Making Games - The Complete Unity Developer
Make a Multiplayer Shooter in Unity
Unity 5 Host Your Game Server Online like a PRO
Start Learning Unity3d by Making 5 Games from Scratch


The original tutorial
ReplyDeletehttps://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143