This weekend’s project was learning how to ray cast in 2D space, and effectively using the depth data to some capacity.
What is ray casting?
If you search for ray cast you’ll find the Ray cast, which I think is pretty funny. Unfortunately, there is little to no overlap between Jamie Foxx and actual ray casting. I understand if you stop reading at this point, but here’s an enticing gif to give you a gist of what ray casting can do:
The Ray Who Cast Me
Say you’re building an espionage game, and the goal is to get from point A to point B without alerting any guards. A key mechanic of the game is finding hiding places, and ensuring guards don’t see you.
A naive approach would be to check a radius around each NPC and determine if the player is hidden from the NPC somehow (in a box, maybe). This approach would generally work, though after adding several new items or mechanics, maintaining how NPCs determine player visibility would become complex. Beyond that, using a simple radius-based approach eliminates the ability to work with mechanics like sneaking up behind NPCs. This is merely scratching the surface of problems with this approach. Consider:
- What if there is an obstacle between the player and the NPC? Does the NPC open fire? What if the obstacle is another NPC?
- What if you’d like to add reflective surfaces as a feature, which NPCs could use to spot the player?
- How would you efficiently identify the closest/furthest thing the NPC can see?
Enter Ray Casting
The approach is simple: emit a ray for every angle of the NPC’s field of view, and use what those rays collide with to determine what the NPC can or can’t see. If a ray hits something it can’t pass through, such as a wall, it stops. Is your player hiding behind a wall, or inside a box, or just around the corner? If the NPC’s rays cant hit the player, the NPC can’t see the player.
Now players can sneak behind NPCs with lifelike stealth! With ray casting, NPCs become quite flexible. Perhaps you’d like an enemy with intentional blind spots, or maybe you’d like to change an NPC’s field of view during the game. We also can tap into the distance a ray has travelled.
Let’s say NPCs can see about 10 meters ahead of them before their vision starts failing, and completely falls off after 15m. Your player crosses a NPC’s path about 12m away. With ray casting, your NPC can notice something at the edge of its field of view, use the distance to adjust its perception (“it’s probably nothing” vs “hey that looked like..!”), and respond accordingly.
But wait, there’s more!
The examples so far have been framed around vision and sight, but ray casting is useful far beyond determining visibility. Take projectiles, for instance. A bullet relates quite closely to a ray we would cast. Both travel in a straight line from an origin, both stop after hitting obstacles (maybe bouncing a little), and so on. The biggest difference is that bullets take some time to travel through space, where our casted rays move through space instantaneously.
You could use instant rays for high-velocity weapons like rail guns or sniper rifles, whose projectiles require seemingly no time to travel through the air.
The benefit of using rays in this context is, again, the ability to tap into the distance the ray has travelled. A low-hanging idea is to use the distance travelled to reduce how much damage the bullet does.
Visualizing Ray Casts
A common use case for ray casting is to simulate shadows. Makes sense, light acts in a similar way - a ray of light will travel until it hits something, emitting light onto the surfaces around it, sometimes bouncing around a little.
Note this is a very simplistic light model, but it’s close enough for our needs. For a deep dive into physical rendering, I suggest checking out Computer Graphics: Principles and Practice.
Allowing our rays to affect their environment is relatively straight-forward: for each ‘step’ the ray takes while moving, tell the engine to draw a white pixel (or a white tile, in this case of this demonstration). The white pixel has its opacity based on the ray’s current distance from the emitter, giving the visual effect of light diminishing as it travels further away:
Messing with Physics
The use of this ray-based lighting approach in a tiled environment has an interesting ramification. Simply put, there are more rays than available tiles, so several rays travel over the same tile. As a result, we can control how many rays can affect a tile’s lighting, which ends up with some interesting results. Examine this screenshot where tiles are only allowed to be lit with one ray of light each:
While we are given a pretty accurate representation of the player’s line of sight, notice that there are some areas where shadows are jagged in such a way that doesn’t look realistic. Allowing multiple rays to light multiple tiles presents us with the following:
With multiple rays of light intersecting, we are presented with much more accurate shadows. Greater realism is achieved through the layering of light, and even the illusion of antialiasing gives a sense of gradation between light and dark. The ability to model physics in a simplified environment affords a lot of neat features and possibilities. One could implement light reflection based on collision angles, or diffraction based on the material of obstacle the ray hits, etc.
This demonstrates a few of the concepts listed above: ray casting, ray layering, identifying (and remembering) obstacles within the player’s field of view. Play around with the settings in the control panel to get a sense of how the rays interact.