Thursday Report: Feeling the Breeze
I've been focusing on smaller aspects of the game over the last couple of weeks. While a lot of 'polish' isn't always necessary at such an early stage, I think it's good for the proverbial soul – when testing something that feels more polished it's easier to get excited, and that helps with motivation on more arduous tasks.
I had been toying in my head for some time about rendering the foliage for the game's trees leaf-by-leaf. Previously, foliage for any tree would be a single pre-rendered texture. This worked fine, and it meant texturing and shading was easily managed, but it also meant that foliage was what-you-see-is-what-you-get.
Instead, I thought perhaps I could import a single 'leaf' texture, have that leaf be its own node, and then individually place each leaf on the tree in order to form the foliage. This way, each leaf can be altered by itself – it's colour changed, it's position changed – all on the fly.
I gave each leaf it's own AnimationPlayer node, with a simple animation of the leaf rising and falling. Then, when launching the game, each leaf determines its own playback speed at random. This way, none of the leaves are in-sync, and they can react more naturally.
The result was transformative. Although subtle, having that soft sense of motion really loosens the game up. Things feel less like cardboard cut outs, and more like there's a weight and an air to the environment.
Godot's animation blending also helps give a sort of pseudo-physics impression to the trees, where I can fade in a more violent rustle of the leaf when the tree is reaction to being hit or chopped, for instance. Hopefully in the future I'll be able to have all trees react to stronger wind, or even rainy weather.
My biggest concern was performance. Each layer of a tree's foliage could have some three hundred leaf nodes, each tree could have three or four layers, and there could be dozens of trees on screen at any one time. Suddenly what would have been a a few nodes, is a few thousand nodes, all individually doing something more complex than those few nodes ever were to begin with.
And for the most part, that's okay. Being new to programming, I'm often surprised just how fast computers actually are, and how many lines of code can be completed in a fraction of a second.
The Garden Path needn't be a demanding game – if my computer can run Witcher 3 without a hitch then it shouldn't be struggling with a 2D gardening game – so I'll be hunting down drops in performance first and foremost. Having two or three trees on screen was okay, but when dropping in some twenty-or-so trees, performance began to cut in half very quickly.
As a compromise, I realized that it's the foliage in the foreground that plays the biggest role in given the environment a sense of movement. As such, I decided to make static the very back layer of the foliage by making it a single texture as before, effectively cutting the number of nodes being generated by a third. The visual difference is negligible, especially when seeing how much smoother the game runs as a result. As I begin really filling out the scenes, I may also comprise the middle layer of a series of multiple leaf batches – so instead of each leaf animating itself, maybe five or six move and react together. This would likely bring that layer's node's down from a few hundred to just a handful, while still giving that sense of motion underneath the first layer.
The trees have already been well received on social media, and will certainly help make the game look even more professional.
I also finally made a bit more progress on a global shadow system. Godot doesn't yet have directional lighting (as in, a light coming from a single, global source, like the sun) for 2D games, because none of the items have a Z-height to draw a shadow from – how long do you know a shadow will be if you don't know how tall what's casting it is? The lighting option for 2D games currently is omnidirectonal, it casts shadows all around the object. Which, while technically is what our sun does, the sun is so huge and far away that it visibly casts shadows in only one direction.
One solution, then, since the sun is so huge and far away, is to create a light source that's ridiculously huge and very far away. It's not elegant, but it kind of works, especially for a small scene. The available map in the Garden Path is going to be quite large, however, and the larger the map, the larger and more far away said light source would need to be. Otherwise, the light source would run out on one part of the map, like the dark side of the moon. The further away the light source, the less accurate the shadow casting becomes.
One idea was to create a dynamic polygon with a fading gradient that I could shape through code to react as though a shadow would. There's a number of problems with this method. The first is that polygons, being vectors, are very clean lines, whereas shadows often bleed or soften depending on the light source. The second, is that shadows are the absence of light, not the presence of a shadow – creating a shadow by drawing them means that multiple shadows begin to stack – getting gradually darker the more shadows are stacked. That's not how shadows work.
I realized, however, I could put the light source on the CanvasLayer. This is the layer that the user interface sits on – it doesn't move with the rest of the game world, the game world moves underneath it. As such, the game world can be as large as it needs to be, and the sun is sat among the menus and the clock, just off screen
This means, well, the 'sun' needn't be quite so huge, or quite so far away.
Again, it's not elegant, but I'd hate to see the game launching without it – shadows give a real depth to any on-screen environment.