Throughout this blog, I’m planning on doing a number of posts which outline the mechanics of Where Shadows Slumber, and how they work. Now, there are plenty of very interesting mechanics that I can (and probably will) go over. However, there are probably a lot of blog posts and tutorials out there describing path finding, object pooling, shaders, and everything else. So, the mechanic I’m going to start with is the one that is the most unique to Where Shadows Slumber – the mechanic behind the shadows.
A quick disclaimer on the nature of this post – please note that this is not an in-depth description of the exact code we used for the shadows, nor is it an attempt to tell you the correct way to think about or implement such things. I’m sure a seasoned veteran of coding and game design will be able to point out a few inconsistencies or inefficiencies in our shadow system. Rather, this is meant to be a high-level overview of my thought process while I created this system, and how those thoughts changed throughout the process. Also, since we have been working in Unity for the production of this game, I will probably be mentioning how Unity does stuff pretty often.
When playing Where Shadows Slumber, you may have noticed that sometimes, things change when they’re in shadow. In fact, you shouldn’t have been able to beat the game without figuring that out.
Things change when they’re hidden by shadow and then revealed, but there are two (basic) ways to think about it – they can always change when put into shadow, or they can only change when you move around the object casting the shadow, so you’re on the other side of the casting object, relative to the changing object. One of these methods ended up making more sense to use in the game, but I implemented both of them on the way there, so I think it only makes sense to go over them both (even if it means stretching this post into two parts).
Now, these are both pretty simple problems at their cores, but there are a few interesting lessons. Let’s start with the first type of shadow, using the naive approach, which is exactly where I started a year or so ago.
So when an object is covered in shadow, the next time you see it, it should be different. The steps involved should look something like:
- Detect if the object is in shadow now
- Check if it was in shadow before (last frame)
- If it wasn’t, change it!
Using these steps, the object will change every time it’s put into shadow and then revealed. So, clearly, the most important thing to be able to do is detect when an object is in shadow.
Since the light in the game comes from the player’s lantern, everything that’s in shadow is essentially ‘blocked’ from the player’s view by some obstacle. Thinking about shadows in this way leads to the first pass at detecting when an object is in shadow – basically, just draw a line from the player (or the lantern) to the object. If it hits anything along the way, that means that the light isn’t going to reach the object, which means it’s in shadow.
This process is pretty much the description of Unity’s ray casting: draw a ray from a point until it hits a Collider. So we just add a Collider to any object that might get in the way, and we have a simple ‘IsObjectInShadow’ function!
However, you may have noticed that there are a couple of reasons this won’t work, the most important being that it doesn’t behave correctly. Since casting a ray will only target one point, the shadow detection will only work for the exact position of the object. Anything with any amount of width or depth (so, anything) will say it’s in shadow when the center is in shadow, but the edges are not. This happens when the object is at the edge of the shadow, when it’s only partially shadowed. This is a pretty big problem if we’re going to be changing the object at that time, since the whole point is that the player won’t see the object when the change happens.
So we can detect when a point is in shadow, but just checking the exact position of an object isn’t enough. The easy solution is to simply check more points! By casting two rays, one toward the leftmost side of the object, one toward the rightmost, we can check if both of them are in shadow. If they are, then the whole object is in shadow. This version of our theoretical ‘IsObjectInShadow’ function works much better – as we move a character around a pillar, the object will more accurately determine whether or not it’s in shadow.
We can get the object’s leftmost/rightmost positions (relative to the player/light) by getting the objects dimensions, which we can either get from the Renderer component on the object (Unity will measure the visible object), or simply by placing some restriction on the size of the objects. Since Where Shadows Slumber uses a grid system, everything that changes with shadows is restricted to a 1 x 1 x 1 unit cube, in order to make shadow calculations easier.
Now comes the question – why is this version not good enough? We have to do a little math up front to get the left/right bounds, but after that, we simply have to cast a few rays, and we know whether or not our object is in shadow. Seems pretty good! And it is pretty good. It only works in two dimensions – if you move upward, above the object, the object will still think it’s in shadow – but that’s not too bad, and most level designs don’t involve a lot of vertical movement, so we can ignore it for now. The biggest problem with this version is that we have to add a Unity Collider component to every object that might cast a shadow. Then, every ray cast will test collision against every collider every frame (the reality is not quite this bad, but that’s the idea). This can become a problem in a scene with a lot of shadow objects and a lot of shadow casters, especially if you want different casters to affect different objects. We can improve upon this version with a little math and some clever thinking.
If we think about the way that shadows are cast, we can come up with a better way of determining if something is in shadow:
If we specify that every object is restricted to a 1 x 1 box (in the two non-height dimensions), then we can use some trigonometry to determine whether something is in shadow. I won’t get into the details of the actual math, just the overall conceptual parts. No matter the angle, the radius of the blocker will always be at least r (0.5). In the same way, no matter the angle, the radius of the shadow object will always be at most R (√2). Thus, by taking distance into account, we can always tell if the entirety of the shadow object (everything within the bigger circle) is put into shadow by the blocker (everything in the smaller circle).
By using this method, we can tell if a shadow object is in shadow without ray casts or Colliders, and we can do it for a specific object. In this way, we can specify which blockers affect which shadow objects, which gives us a lot more control over level design.
Now that we’re able to tell if an object is in shadow, the hard part is over. Every frame, we just follow the steps outlined above, for any shadow object. We check if it’s in shadow, then check if it was in shadow (meaning we have to keep track of whether or not it was in the previous frame). We change the object to the next state. The object will only change once, until the user sees it, when it’ll appear differently. Then, if it’s put in shadow again, it’ll change again. This allows us the flexibility and consistency of shadow objects.
As it turns out, this version of the shadow mechanic didn’t end up being the one that we needed for Where Shadows Slumber. The shadow mechanic that we actually did use will have to wait for Part 2. We already did a lot of the heavy lifting this time, so it shouldn’t be too hard to make some small adjustments to the work we did here.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
I hope you enjoyed this first highlight of the shadow mechanic in Where Shadows Slumber. Hopefully it wasn’t too complex, and I was able to explain it well. Let us know if you have any feedback! As always, you can find out more about the game at WhereShadowsSlumber.com, find us on Twitter (@GameRevenant), Facebook, itch.io, or Twitch, and feel free to email us directly with any questions or feedback using contact@GameRevenant.com.
Jack Kelly is the head developer and designer for Where Shadows Slumber.