Lighting system for ASCII based games

Keywords: light source, player move, dark tile, tile fade, illuminated cell, animation, illumination, scene, time, lower, operation. Powered by TextRank.

I've recently picked up where I had left off on my roguelike game. After going over the codebase from 2 years ago I decided to give it an overhaul by changing the graphics system from a tile based approach to a scene graph. There are many other changes and one of the changes I want to write about today is the lighting system changes.

Ligthing for ASCII tiles is pretty simple. You just need to keep a value that represents how much light is received on a cell. There is no refraction/reflection or other difficult problems to worry about. This value called illumination, basically acts as a multiplier on the RGB components of the cell.

var fgcolor: SKColor {
	set(newColor) {
	    let rgba = newColor.rgba
	    foreground.color = SKColor(red: CGFloat(rgba.red * illumination),
	                             green: CGFloat(rgba.green * illumination),
                                  blue: CGFloat(rgba.blue * illumination),
                                 alpha: CGFloat(rgba.alpha)).usingColorSpace(.genericRGB)!
    }
...
}

Now that we have the basics down and can change the lighting on a single cell, how do we choose which cells to apply this to? There a couple of options but the one I chose is to use a shadow casting algorithm to pick the cells. So our light source will be a point light source which emmits rays and changes the illumination values of the surrounding cells. Ambient lighting can be simulated by setting the illumination value of the cells to a lower value then the illuminated cells. Usually unexplored areas are completely dark, areas that are beyond the light source reach are darkish.

For roguelike games you usually want the light source to move with the player so you can either attach a light source to the player object or make the player a light source itself. I went with the prior option after I refactored the engine to support a scene graph. This makes the design more flexible. Now since the the light node is attached to the player we will need to recompute the illuminated cells when the player moves and highlight them. There is no escaping the recomputation but there is a little optimization that be done for the highlighting part that will come in handy once we add fade-in animations. Since you are moving one tile at a time you don't need to reset all the tiles and then highlight all the tiles again. Imagine this:

A)

. x x x .
. x x x .
. x x x .

where the letter x represents the currently highlighted tiles and the dots are dark tiles. If we move 1 to the right, the new map will become B)

. . x x x
. . x x x
. . x x x

If we take the difference of these set of coordinates once A-B and B-A we will have the sets of cells that we need to turn off and turn on. This is faster then illuminating all the cells.

Animations

Lighting up the tiles is simple, but a bit plain. A great effect to have is fading the tiles to their final illumination value over time.

img1

This makes things a bit more complicated as now there are threads that we need to increase or decrease the illumination value over a certain period of time. This can be implemented with operation queues in the Apple world which provide a nice abstraction or with more lower level constructs on other platforms. The basic idea and the first iteration I have for this is to have a lighting pass before the rendering pass. Here I will get all the light sources on the scene and the cells they would light up and store an operation for each cell that will run the animation. All these animations will run concurrently after the scene has been rendered. The animations need to include a mechanism for cancelling a running animation. This is because the player can move away from a cell that was in the processing of being faded in and now should be dark. So the fade in animation will be cancelled and the new animation (or in my implemnetation an immediate value set) will replace it. This cancallation support was one of the more trickier parts to implement correctly.

Here is the final version:

This as it stands now is a bespoke tile fade in animation and will later need to be incorporated into a wide animation framework.


Metadata

Similar posts

Powered by TF-IDF/Cosine similarity

First published on 2022-06-20

Generated on May 5, 2024, 8:47 PM

Index

Mobile optimized version. Desktop version.