163. 2D platform game #8 – Lighting
This post is about the lighting I used in the side scroller. Lighting isn’t at all necessary – you could just leave any underground areas brightly lit, or simply draw a slightly transparent rectangle over the underground part to make it look a bit creepy – but I liked the idea of a flickering torch.
To do this does involve “shaders”, which are not for beginners, but if you are comfortable with Codea and meshes, you shouldn’t be afraid of learning about them, either, as they look harder than they are. A shader is simply a way of controlling drawing very precisely (and fast), right down to individual pixels, and is great for special effects. I should stress that I am not a professional programmer nor do I love math. I just like cool stuff. If you do, too, then try shaders out, once you understand meshes.
By the way, this post is not all about shaders. It starts with quite an interesting little problem I needed to solve in Codea.
How the lighting works
My lighting works like this.
I define a large rectangle mesh to cover the dark area, and I use a shader to draw this mesh.
The shader colours everything black, unless it is close to the top of the rectangle, in which case it fades from black to nothing (ie fully transparent). This makes the transition from light to dark look more natural as your player goes down. You can see this in the video above.
Then my shader allows for up to three lights. These have an x,y position (within the rectangle) and a radius. The shader fades the light from the centre outwards by calculating the distance from each pixel to the light (that’s a lot of calculations, if you think about it, but I told you shaders were fast!).
The shader calculates and adds the light from all these sources, and then draws the pixel. It isn’t perfect -I’m not sure how realistic it is when the two lights overlap in the video above – but I think it’s pretty good.
Making noise
The flickering is quite simple, and is controlled from Codea. I simply use the noise function to create a series of random variations in the radius. Why noise? Because whereas random numbers jump all over the place, noise moves up and down smoothly (depending on how noisy you want it, of course), so it doesn’t go from very small to very large in one step. So Codea calculates the (noisy) radius at each frame, and passes it to the shader.
I also used noise to create a rolling mountain background for the game.
If you haven’t played with noise before, have a look at this.
Making the light portable
Given what I’ve done above, allowing the player to jump up and grab a light isn’t difficult, at least where the shader is concerned. All I need to do is change the x,y position of the light. Well – “all I need to do” sounds simple, but it’s quite fiddly. At least it’s all in Codea.
It’s easy enough to have a variable which tells Codea that the player is holding light #2 (say), and then only show the light if the player is well into the dark area.
The hard part is positioning the light. We need to put it in his hand. This means some trial and error until we find the right offset from the player’s x,y position. What makes it a little tricky is that there are two player images, and the front hand is placed differently in them. So I put the torch in the back hand, which is in the same position in both images. This isn’t ideal, but is OK.
The other problem is that I reverse the player images when the player walks left, so the torch has to be put on the other side of the player. But because it is a mirror image centred on the x position of the player, I only have to reverse the torch offset position too (ie make positives negative, and negatives positive).
Alternative lighting
I did consider other ways of providing a light. One idea I had was to create a small image and draw a series of transparent circles getting bigger and darker, outwards from the centre. I could then sprite this image at different sizes to create flickering. However, the image would have sharp square black edges, and I would still have to figure out how to draw the rest of the dark area around this light – because darkness is an overlay that gets drawn last. I would have to draw the dark area as a black rectangle with a hole where my light image would go. And creating holes in rectangles isn’t easy (you can use a shader, but that’s another story).
Sometimes, trying to be clever just gives you headaches…
Code
If you’ve read this far, you deserve a present. Here is a standalone version of the lighting code, with instructions on how to use it in your project.