Hey everyone, a lot has happened since
my last video. Most notably I’ve been porting the entire game over from Unity
to Godot - no prizes for guessing why. Anyway despite that, today I’d like
to show you how I created this water trail effect and also how I made the sprite
gradually cut off as it goes into the water. Let’s start with the water trail.
Previously I just had these particles, but I wanted to see if I could make something
a bit nicer. And it would also need to be applicable to all the different creatures.
After a lot of experimenting I had this idea to combine a Line trail with
a shader, and this is the result. To make a line in Godot you can simply use the
Line2D node and adjust the points. But in order to make it act like a trail you need to add a
script that continuously update those points. In the script there is a queue
to keep track of all the points, and the current position will get added to that
queue every frame. If the queue reaches the max length the excess points are then dequeued. Then
all that's left is to overwrite the Line2D’s point array using the points from our queue.
With that done, the line now looks like this… I also adjusted the gradient and width curves so
that the line is wider and fades out towards the tail end. Eventually there will be ripples
travelling along the direction of the fade, so in anticipation of that I also created this
gradient texture in photoshop and assigned it to the line. The texture kinda gives it that V
shape which the shader will make use of later. One thing that immediately stuck out to
me was how the line collapses in a very unnatural way. The tail end should not remain
that wide when the line becomes short. Instead, it should collapse like this. So I added
a bit of logic in that script from earlier to adjust the tail width based on how far away
it is from the player. Now it looks like this: Cool so now that we've got the trail, the
next step is to add in the ripples.The main idea behind this shader is to take the red
channel of the texture and plug that into a sine function. To make it move, we simply add the
current time to our input for the sine function. It looks quite strange to
use this result by itself, so instead we can remap and subtract it from the
original colour to make the effect more subtle. Right now the colours are also quite blended
and fuzzy, which doesn’t really fit with my pixel art style. So I decided to add another
section of logic to quantize the colour. This was achieved by multiplying, flooring and
dividing the value by some number parameter. It’s looking better but now we have to tackle the
problem of making it into pixel art. Unfortunately because the uv’s of the Line2D node are
very stretched and distorted, we can’t use the old trick of just quantizing them.
In order to pixelate it without distortion, the only way I could think of was to use a
SubViewport. This node will render its children onto a texture which you can then use on a sprite.
For those of you that are familiar with Unity, it’s sort of like Unity’s RenderTexture.
I really wanted to avoid using this due to performance concerns, but I’m hoping by setting
the resolution of the texture to a very low number it won’t have much impact. The low resolution
will also create the pixelised look we want! Now another thing I wanted to do was
cover up the end under the player, because I don’t like the way it disappears into
nothing. To do this I simply added this circular sprite as a child of the SubViewport. Then it
kinda gets merged together. I put different sized circles on each animal, and this ended up
creating some nice stationary ripples as a bonus! Ok so now all that’s left to fix is
the trail being visible on land. To address this I decided to slowly fade the
trail in and out depending on the depth. This was easily done by adding a new brightness
parameter to the shader, and then adding some code to update that parameter based on depth.
Aand that’s the water trail complete! So the other thing I wanted to show was how I made
the sprite disappear into the water. Although it might look slightly strange at times, what’s great
about this method is that it can be applied to any creature. There’s no need to painstakingly
hand-draw a different animation for each one. Basically if you move the sprite down and
mask off the bottom at the same time, it kinda gives the illusion that it’s in the water.
In Godot 4, creating a mask is really simple. Sprite2D nodes have the option to clip all
children, which is exactly what we need. So in Photoshop I drew up these mask shapes,
making them slightly different depending on the creature’s imaginary contour
lines. I made sure to put a band of semi-transparency in the middle to help soften
the line. This is what it looks like in game. Next we want the sprite to
move up and down automatically, while the mask stays stationary. So
I wrote a lil function to override the position of the body relative
to its parent (which is the mask). First, an InverseLerp function is used to
remap the current water height to a percentage, where 0% would be at the shore and
100% would be at some very deep point. By the way if you are wondering where the current
height comes from, it comes from the heightmap used to procedurally-generate the terrain.
Next we scale and add any initial positioning back. The scale should be set depending on
how much you want the sprite to move down. Now sometimes we may not want our animals to
move down that much. For example if this chicken moved down as much as the player, it would go
completely underwater. So to prevent this we can clamp the value to a “max depth” amount. Then
that can be set individually for each creature. That’s pretty much it for the code, all
that’s left is to apply the new position. As a finishing touch I also added
a small bobbing movement when the creature reaches max depth. This was
done using a simple sine wave offset. Alright that’s all I wanted to show today, hope
you found it interesting or helpful! By the way, if anyone has any other ideas on how
to implement a water trail like effect for 2D games, I would really like to hear them. In the meantime I’ll be continuing to
port everything over to Godot. See ya