Applying shadows using BlendmodeIn Part One we drew out some plain black shadows, but we want to use these shadow shapes for something more than drawing black areas on the screen. As everyone* knows, shadows aren't black but just darken whatever would normally be there. Flash can do this nice and speedily by using BlendModes.
Very briefly, the BlendMode of a display object decides how it is drawn on top of other objects. By default everything has BlendMode.NORMAL which just draws objects over one-another. But there are a wide variety of BlendModes available which vary how the colours of overlapping objects will be mixed or combined to produce the finished image.
We are interested in using BlendMode.HARDLIGHT. A display object set to HARDLIGHT will have its colours altered as if it were illuminated by whatever colours lie "beneath" it. Stick a picture of a nebula over the demo from Part One, and set that picture's blendmode to HARDLIGHT, and you get:
Easy. To be more exact on how this BlendMode works: Any colour value 128 (80 in hexadecimal) or over will cause the corresponding colour value in the image on top to be increased, colour values below that will cause a decrease. It's easiest to understand thinking of a greyscale "shadow map" placed under your pretty image. Dark areas on the shadow map will darken the image, light areas will lighten it. It's an extreme effect, with any black areas on the shadow map resulting in a black area on the finished image and white areas producing pure white. Generally your shadow map should vary between 0x303030 for dark but visible areas and 0x7f7f7f for fully lit areas.
Limited range lightsIn Part One we dealt with just drawing shadows in what was assumed to be an infinitely large pool of light. It is very simple to adapt this to drawing an illuminated area that contains shadows.
Each frame, instead of just clearing the area you draw the shadows to instead fill it with a dark "ambient light" colour. Now draw on a circle (or cone, or whatever torchbeam-like shape you like) of light colour. Then just draw on your shadows as normal in the dark ambient light colour.
You now only need to draw the shadows of objects which lie within your circle of light. For instance box2D's World class provides a Query function which will allow you to find all the objects that fall within a specified area.
Extending shadowsDepending on the effect you wish to create, you may prefer your shadows to project out to the edge of your lit area. Sadly I have been unable to find a neat and tidy way of doing this.
It is possible to project each point of the shadow you are drawing to the edge of the ring of light by changing your projectPoint function to:
private function projectPoint(point_:b2Vec2, light_:b2Vec2, lightRadius_:Number):b2Vec2
{
var lightToPoint:b2Vec2 = point_.Copy();
lightToPoint.Subtract(light_);
var extraLengthNeededToReachRadius:Number = lightRadius_ - lightToPoint.Length();
var vectorToAdd:b2Vec2 = lightToPoint.Copy();
vectorToAdd.Normalize();
vectorToAdd.Multiply(extraLengthNeededToReachRadius);
var projectedPoint:b2Vec2 = point_.Copy();
projectedPoint.Add(vectorToAdd);
return projectedPoint;
}
However as this projects the points that mark out the corners of the shadow shape that is then drawn using straight lines, there will be small segments of the light circle left unshadowed which should be in shadow.
My best solution so far is to fluff it and just add "a bit" on to the length of the projection vector so that the shadow shape is pushed far enough out. I'm still working on other possibilities though.
Tile-based shadowsSticking games in tiles is very popular, because it makes most things easier. Good news is it makes shadows a little easier too!
Let's assume you have a tiled world something like this:
The red tiles are solid walls, which we'll assume block all light. We can make it work by just considering each wall tile as a square which will cast a shadow just like any other polygon. But there's plenty of opportunity for speeding things up!
Firstly we know that the edges of the wall tiles are always going to be straight along either the X or Y axis. This means we can replace the fiddling about with vectors that was needed for polygons to determine if each edge should cast a shadow with some even simpler less-than and greater-than comparisons.
The biggest optimisation will come from being able to ignore some edges entirely. This image shows all the wall tile edges present in our example:
That's a lot of edges. By running the map through a simple trimming algorithm, a very large number of them can be ignored. For each wall tile, check if the neighbour to its right also blocks light; if it does, then the wall tile's right edge can be ignored. Repeat that for all the wall tiles and for all their edges and you'll end up with:
Depending on the size of your map this can take a while (it quickly becomes a huge number of checks to perform once your map is of a reasonable size.) But the operation only needs to be performed when the map is first generated/designed. Should your game involve the creation or destruction of walls, then you'll need to repeat the check on the neighbours of any tile that is changed.
You could further reduce the number of edges by joining adjacent edges to make a few long continuous straight edges. I prefer not to do this, and instead keep things on a per-tile basis so that I can more easily limit what section of the map the game is having to deal with at any one time.
A further optimisation would be to perform LoS checks on tiles, so that those which are completely hidden from the lightsource by other tiles will not have their shadows drawn. As I use quite large tile sizes, I found that this check would rarely eliminate more than a couple of tiles on the screen so I felt that it wasn't worth pursuing. However if you use a smaller tile size, and tend to have certain types of level design then this check would become much more worth while. The classic Rogue style levels of large open rooms connected by narrow passages would particularly benefit from LoS checking on tiles as you could often safely exclude whole rooms.
Visual consistencyThis is a big topic. Visual consistency deals with making sure your shadows make sense to the player.
In 3D graphics it's easy to make shadows make sense as they just need to appear as they do in real life or as close to that as you can manage with your technology. The point is that there's an obvious end goal - to get as close to how the shadow really looks.
In 2D graphics making shadows look "right" depends a great deal on other aspects of the game's visual design. The main problem is very few games are really showing a 2D world. Instead they show a 2D view of a strangely flattened 3D world. A good example to pull out at this moment are most the 2D Zelda games:
(image copyright Nintendo)The walls look fine, until you think about them too much. This room would need to have some deeply odd geometry for the view seen in the screenshot to be actually possible. Try projecting realistic shadows over it and you'll just highlight how the graphical presentation shows a world that doesn't make sense.
Know what you're dealing withIt's important to have a clear understanding of what assumptions you're making about the world you're depicting in your game; otherwise you risk going against those assumptions when you add shadows and producing something that feels wrong to the player.
Let's take the example shadow caster from Part One and see what the shadows are telling the player about the world:
The shadow from each object has an end. This means that the light source must be positioned higher (closer to the player's viewpoint) than the tops of the objects.
The shadows from all the objects are the same length when they're the same distance from the light. This means all the objects are exactly the same height as one-another.
The shadows start at the base of the objects. This means that the objects are sat on the surface that they're casting shadows on.
The shape of the shadows is determined only by the relative position of the light compared to the objects. This means that the surface the shadows are being cast on is perfectly flat.
If this shadow caster was used in a game which broke any of these assumptions, the player will notice. They may not realise what they're noticing but they'll almost always get a "this isn't quite right" feeling. A huge slab of the human brain is dedicated to visual processing, and extracting information from things like shadows is something we're very good at - even if we're not aware of it.
Shadows on shadow castersThis is a problem I've wrestled with a great deal. To know what I'm talking about, let's consider the walls I talked about in the tile-based section:
Wall A has a shadow being cast upon it, so shouldn't it be dark? But how much of it should be dark? Consider also how the wall which is casting the shadow should be shaded. The edges facing the light source should be lit up, but the shadow casting sides should be in shadow. Often the tops of walls are drawn on in these kinds of top-down views, but how should that be illuminated? If we're being realistic about it, no light from below the wall's level would ever reach it and so it would be in total darkness.
These are difficult problems that need to be carefully considered, and your solutions to them are likely to have wide-reaching implications within your game.
Thankfully there is a cop-out that generally looks okay: Simply have all shadow casters be unaffected by shadows. It's surprising how little this impacts the perceived quality of the finished image. But do take care to have the colours of the shadow-casters be fairly muted, else they'll stand out too much from the rest of the shaded world. Similarly avoid the use of very deep shadow else they will look out of place.
Here's a graphical Rogue-like prototype making use of the cop-out:
In this case the walls should have been a darker colour so that they do not stand out so clearly against the shadowed floor areas.
Avoiding the cop-outCop-outs are useful, but let's be awesome instead.
Choosing the right graphic style can make incorporating shadows much easier. For this example I am using a pixel-art "2.5 topdown" view. This means that it's kind of a top-down view, but one of the vertical sides of objects are visible to the player.
Here's shadow casting in the same prototype without the cop-out:
Not bad really, but there's a couple of problem areas. Area A shows where a shadow overlaps the vertical section of a wall. Area A doesn't look too shocking, but it is wrong - a shadow will not form the same shape on a vertical section of wall as it would on the horizontal floor. Area B is more of a problem; here a wall casts a shadow but the wall on the shadowed side is still brightly illuminated.
The good news is we can fix both areas with the same technique. We will simply project the shadow as it appears at the base of the wall up the full height of the wall. Just loop along the length of the wall, reading the value of the shadow map at the base (using BitmapData's getPixel function) and fill in the row of pixels directly above it. It's faster to use fillRect to do that rather than multiple setPixel calls. Hey, I actually mentioned some Flash stuff!
As if by magic:
It isn't perfect of course. It should be possible to position the player so that her shadow ends half way up the wall, but that will never happen using this system. To get that effect you would need to work with the relative heights of the light source and shadow casting object, as well as volume information on the form of the shadow caster. But that's just making a 3D game, which is cheating.
If you have vertical parts of the wall visible on all four sides of a tile (or just two sides, as in an isometric view) then you can use just the same technique for projecting shadows up those walls. I made a prototype for a "all four sides visible" top-down view but wasn't quite satisfied with the results. The technique should work quite nicely with an isometric view.
Not shadowing the characterHaving shadows cast by an irregularly shaped object such as the player character not overlap with the character sprite itself is a fiddly business.
This problem doesn't occur in the case of walls or polygons, as the shadows only start at the far edge of the shape. However the character is casting a shadow as if she were a simple polygon, when she is actually a quite different animated image.
My solution is that when the shadows from the character are drawn, the sprite is then drawn over the shadow using BlendMode.ERASE. This effectively takes a character-shaped bite out of the shadow being cast by her.
It is possible for this to backfire, with this hole in the shadow being visible even if the character is actually hidden behind another object. Sadly I haven't been able to find a more satisfactory solution.
Matching your graphicsYou may notice that in the screenshots above the shadows all have rough edges rather than the nice anti-aliased lines of the demo from Part One. Similarly there is a uniform pool of light, rather than a more realistic gradient fading out as it moves from the light source. These are both efforts to keep the shadows fitting in with the visual style of the rest of the game. Just because you're doing clever programming doesn't mean you can ignore the fact that gradients and pixel art don't mix, or that aliased and anti-aliased lines don't mix.
Similarly, you will need to keep in mind the impact that covering much of your graphics with light and shadow effects is going to have on their appearance. Similar colours will look all the more similar when seen in shade, which may cause you to lose details - make sure those details aren't important to the player. If you light a dungeon with moody orange light from candles, then everything will take on that orangey tone. These can be great at building atmosphere, but you need to keep the effect of lighting in mind when you're making your assets.
Multiple lightsWhat's better than one pretty light effect? TWO pretty light effects!
This is two identical candles sat next to one-another. We want areas where light from both reach to be brighter than areas which get light from only one. To achieve this, we first draw out the light/shadow effect for each candle separately:
And add them together!
Oh dear. The problem here is that the sum of the light from two candles is actually over 100% illumination. Being able to create over 100% illumination is useful for special effects (nuclear blasts, flashbang grenades, or walking from indoors to out in a 'next gen' game.)
With a bit of clever use of layering and blendmodes, we can cap the brightness of the light map:
var lightMapLimiter:Bitmap = new Bitmap(new BitmapData(vWidth, vHeight, false, 0x7f7f7f));
var shadowLayer:Sprite = new Sprite();
shadowLayer.addChild(lightMapLimiter);
shadowLayer.addChild(lightMap);
lightMap.blendMode = BlendMode.DARKEN;
addChild(gameLayer);
addChild(shadowLayer);
addChild(HUDLayer);
shadow.blendMode = BlendMode.HARDLIGHT;
Here lightMap is the end product of drawing all the scene's lights using blendMode.ADD, gameLayer is where the walls, player and everything else in the game world is drawn. lightmapLimiter is just a big block of colour the size of the screen - whatever colour it is will be the cap for lightMap.
And something to play withFinally you can wander around that rogue-like prototype if you like, though there's nothing to do but walk around looking at shadows. Press space to make your faerie companion light up.
Click hereI feel like this post was a bit wandering, and long. Hopefully there's useful parts in it for some people.
Further readingSome discussion and further explanation of this tutorial.A fine person made a shadow caster with a little help from this tutorial using HTML5's Canvas thing!