Hey again! Last time I promised that this installment would be about transparency and how to generate a single mesh with which to replace all those cubes. Unfortunately I have lost the Python script I used to generate the mesh and it is taken a bit longer than I thought to rewrite it, so instead I’ll write about how to make objects appear as if they are floating on the surface of the cube sea.
You expects objects such as ships to be affected by the seas motion, so if they are not, that would disrupts the illusion and the immersion in the game. The effect we’re after here is like the ship floating in the cube sea in the video embedded below (it is the same video I showed you in the previous post). The ship tilts and bobs up and down with the water.
Making objects float
If we think of everything we would want to throw into the water as being anchored on an cross, as shown in the image to the left, then our goal is simply to set the upwards position of the points left, right, front and back in order to give the floating object the right tilt. So let’s look at how to script that.
Recall that to animate the water itself, we used simplex noise to set the height of each seacube in relation to its position on the (x,z)-plane like so:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
... public void Update() { _offset += velocity * Time.deltaTime; for (int i = 0; i < cubes.Length; ++i) { Vector3 p = cubes[i].position; p.y = GetY(p.x, p.z); cubes[i].position = p; } } public float GetY(float x, float z) { return waveSize.y * Simplex.instance.Noise(x * waveSize.x + _offset.x, z * waveSize.z + _offset.z); } ... |
The reason that GetY method is public is to allow other scripts to trace the surface of the sea, which is exactly what we will be doing. For starters let’s simply use the y-coordinate supplied by this method to set the vertical position of a floating object. So if we have a reference to the script controlling the waves, we can do the following on a floating object:
|
1 2 3 4 5 |
public void Update() { float y = waves.GetY(p.x, p.z); transform.position = new Vector3(p.x, y, p.z); ... |
So that’s the height taken care of – now for the rotation. To get the desired rotation for our floating object, we need to position the four extremities of the cross from before. To calculate these we first find where the points would be if the object was sitting on a flat surface, only rotated around the global y-axis, and then we displace them using GetY like we did with the position of the object itself above. This can be implemented like this:
|
1 2 3 4 5 6 7 8 9 10 11 |
... Quaternion worldUpLookRotation = Quaternion.LookRotation(transform.forward, Vector3.up); Vector3 front = p + worldUpLookRotation * Vector3.forward * length / 2f; Vector3 back = p - worldUpLookRotation * Vector3.forward * length / 2f; Vector3 right = p + worldUpLookRotation * Vector3.right * width / 2f; Vector3 left = p - worldUpLookRotation * Vector3.right * width / 2f; front.y = waves.GetY(front.x, front.z); back.y = waves.GetY(back.x, back.z); left.y = waves.GetY(left.x, left.z); right.y = waves.GetY(right.x, right.z); ... |
Now that the anchoring cross itself is rotated correctly, we just need to rotate the object in accordance with it. We can do this by calculating what would be the local up-vector of the cross which is the crossproduct of the two vectors forming it cross (Back – Front and Left – Right), and rotating the object such that it’s local up-vector matches the one we calculated for the cross, like this:
|
1 2 3 4 5 6 |
... Vector3 up = Vector3.Cross(back - front, left - right); transform.rotation = Quaternion.Lerp( Quaternion.identity, Quaternion.LookRotation(front - back, up), tiltSeverity); } |
And that’s pretty much it. You can see the effect in action if you download this unity-package containing a prefab and a script using the above method. Notice that the floater you instantiate need a reference to the sea. Then you should be able to get result like this:
Next time, I hope to tell you about transparency and a replacing the cubes in the cubesea with a single mesh. Until then, have fun!
Update
The next part of this post series is now up!

Good stuff!
I think it’s great that you’re also explaining the code.