How does this water ripple work?

Hello! I didn’t know where to put this, so I put this in game design support. I was scrolling through YouTube when I found this video.

I’m quite interested on how this works. Anyone can explain?

1 Like

I’m pretty sure they made that used Stop Motion, but you could try using Script Terrain

In the description it said they used terrain, however it is from 2015 so it might be removed now.

1 Like

Really? I think the ripple effect looks quite smooth for stop motion (if it’s stop motion).

True, though why would roblox remove a feature like this?

Dont know, it was just the first thing that came to my mind!

1 Like

Hmm must have
A - Been a godly scripter
B - stop motion
C - a roblox plugin
Im not 100% positive that it is still, here sorry :+1:

  • Sav
1 Like

If it was a roblox plugin, what plugin would it be? I don’t think it’s stop motion, so it’s most likely A.

1 Like

Probably ill have a look at that video again

Try and look how to make water ripples with normal blocks and when you find out how to do that, you could use Terrain:FillBlock() for every part in the ripple?

1 Like

I don’t have a great idea about what that means, because I am a builder. That that is a good idea, I will look that up.

This isn’t really a ROBLOX feature you can enable. I’d assume he achieved this look through a script.

Interesting stuff! I think I know how they did it - or at least how I’d do it. It involves an area of physics called kinematics, as well as some trigonometry. If you haven’t taken those courses in school yet (kinematics would fall under a general physics class) this might be a bit difficult, so I’ll do my best to explain stuff. Also be sure to reference the roblox wiki terrain page linked by @DybalaplaysYT earlier when working with terrain. Here’s how I’d do it!

  1. Determine if you want this to be client side based (faster/smoother + requires custom replication system) or server side based (more memory to work with + can stutter when replicating to clients). I recommend client based, but if you’re not comfortable with remote events and are okay with a choppier result go with server.

  2. In a script create a 2D table of water terrain cells at the same height. These will be the only cells you change. For each cell that exists set it in the 2D table as 0, which refers to the offset of the water from the regular voxel height. If there’s no water there set it as nil / don’t set it as anything.

  3. Create a new table that stores impacts. Each impact could be its own small dictionary, with energy (0.5mass(velocity^2)), a position V2 of the impact, and the impact time. Whenever you have something impact the water (this can be checked through touch events, raycasts, height checks, etc) just create a new impact and insert it into the table.

  4. Now set up a loop that will run multiple times a second. I advise using something from the runservice to make the loop. The remainder of the steps will happen within this loop.

  5. Make a copy of the 2D water terrain table in the render loop - you’ll be referencing it at the end. Once you’ve done that set every terrain value to 0 in the master copy.

  6. The way to do this is to first figure out where in its life cycle the ring is. If you subtract the current time/tick from the original impact time/tick stored in the impact table, you can determine how many seconds it has been since the impact.

  7. Next you need to determine how far away from the center it should be at this point. Feel free to come up with a more advanced algorithm, but I’m going to suggest the easy route of having a uniform speed of around 5-6 blocks a second. As is the basis for much of kinematics, we know that by multiplying this uniform speed by the time we get the distance traveled over that period of time. In this case, that distance is now our current ring radius!

  1. Now we should calculate the amplitude of the waves. One thing to appreciate is that an ocean wave isn’t just the part of the water, but it is also the sinking of it below the surface. The amplitude is the distance between the wave at its highest point (the crest), and the lowest point (the dip between waves). To get the amplitude at 0 you can look up a lot of fluid mechanic formulas, or you can just take a percent of the impact energy stored earlier. This second method has no basis in physics, but to be honest you could probably get a good enough effect with it.

  2. Now over time the amplitude of the wave will fall. This can also be done in more advanced way to get added realism, but for us I’m going to just go with a simple exponential reduction. This means that it loses a consistent percent of its amplitude every second. As math that looks like amplitude = startingAmplitude * (0.9^time since impact). Feel free to change the 0.9 to make the waves last longer or shorter.

  3. At this stage we can remove the impact dictionary from the active impacts table if the amplitude is too small, thus freeing up processor space from having to calculate invisible former impacts.

  4. So, now we have the distance from the center, and the amplitude of the wave at that point! Since we’re probably not going to sink the water in this simulation, feel free to divide the amplitude by 2 in order to get the raising above the water distance. The main thing we now need to do is get the x/y coordinates of each section of the ring at an equal distance to the center impact point. In order to do this we need to round the distance from the center into voxel scale. If I recall, voxels are 4x4x4. You can convert it to voxel distance by dividing it be 4, then rounding to the nearest voxel.

image

  1. Now that we have the voxel radius, let’s calculate the rest of the ring. We will be breaking it into 4 quadrants, and solving each quadrant with the sine ratio used in trigonometry. For each quadrant we’ll start with a voxel that is in the position Vector2.new(voxelRadius, 0), and our end goal will be Vector2.new(0, voxelRadius). This means that each move should move X towards 0, and Y towards the voxel radius. In order to avoid a lot of duplicate code, create a function to do all this math in, with your only input variables being the voxelRadius, as well as a V2 with the corresponding signs of the quadrant.

  1. So now we have to do the sin math. The idea is to create multiple triangles along the curve. This means we’re going to make a for loop that iterates voxelRadius times. We’re going to iterate the Y ring position up by one each loop. Using that we’re going to calculate the expected X ring position. The resulting effect should be a circular curve. Before we determine X we need to determine the angle of the triangle. With this we’ll use the arcsine ratio. Where sine will convert an angle into a side length, an arcsine will convert a set of sides into an angle. Assuming that Y = opposite side from the angle, we can get the angle using math.arcsin(y/voxelRadius). This angle will be in radians, so if you need to check it convert to degrees for readability, however all Roblox calculations use radians.

  2. Now we should convert the angle and Y side into a workable X side of the triangle! Trigonometry tells us that sin(angle) = opposite/hypotenuse. By translating that into our context we get math.sin(currentAngle) = newX/voxelRadius. By rearranging that with some basic algebra you get newX = math.sin(currentAngle) * voxelRadius. Be sure to round the newX to the nearest full voxel. Also at certain steeper points, it may skip entire voxels, be sure to track the most recent X added so that you can fill any Xs between the newX and the previous newX with the previous Y.

  3. Now that you have the new X, and the new Y (which was the loop iteration), you have a vector2 coordinate you can change! Go back into the water terrain cell 2D table, and add the amplitude (converted to voxel scale) to the existing value, likely taking it from 0 to 1 if the water if flat.

  4. Now go through and adjust the terrain using this map, adding voxels to reach heights changed on the height map. Remember that copy of the table we made in step 4? Iterate through every cell and make sure it lines up with the current model. This is very important for erasing old water changes. If this is too much of a headache just reset all water to flat water every frame - this will likely take longer though, so I advise the suggested way if you can handle it.

And you’re done! It should now go through and write changes to the water every frame! There are certainly ways to optimize it, such as only iterating through terrain that is expected to have changed, but it should be functional. One thing to consider is to perform the ring calculations multiple times at different radii at reduced amplitudes to make waves with a wavelength above a single voxel. One day you may consider even adding basic wave rebounding for if it hits a wall.

Hope this helped! Happy scripting!

25 Likes

I know this isn’t a normal feature, I was just wondering if anyone knew how to replicate this.

Oh my. I forgot to mention that I am not a scripter. But still, thanks for the help.

Ah, no worries. In which case, hope my answer at least shed some light on all the backend math and structuring! Anyways, if you ever do team up with a scripter for the matter maybe this can be of use then :+1:

2 Likes

I see that there is a pretty small grid on the baseplate, idk if that means anything but this is pretty clearly a stop motion video (you can tell because of how slow stuff moves)

Like I said in another reply, it doesn’t seem to be stop motion.

it seems to me it is also do you know what the grid on the baseplate is? cuz I don’t know

Just grows water terrain around it. It is just using the terrain tools in roblox