<Emanuele Feronato/>
home

Understanding raycasting in 2D games, and using it with a free commented open source JavaScript library

Think about a guard in a stealth game. He stands there, turns, and at some point spots you. How does he know?

He does not, really. Something in the code drew an imaginary line from his eyes to your feet and checked whether that line ran into a wall on the way. That line is a ray, and the act of following it until it hits something is raycasting.

Have a look first

Before I explain anything, go and play with it. There are ten interactive examples, and everything reacts live as you drag things around.

https://triqui.github.io/raycast2d/examples/01.html

The vision cone turning red is probably the fastest way to understand what the library is for. The fan of four hundred rays is the prettiest one.

The example that moves a rectangle through a level shows how little code it takes to get a platformer going without a physics engine.

The source is on my GitHub page, commented throughout and MIT licensed, so do what you want with it:

https://github.com/triqui/raycast2d

Now, let’s see why raycasting keeps coming back in game development, and why a library that only knows about axis aligned boxes covers most of what a 2D game needs.

Once you notice it, you start seeing it everywhere.

Fog of war is raycasting. You stand somewhere, look around in every direction, and the cells your lines reach become visible while the ones behind a wall stay dark. Walk down a corridor and the map opens up in front of you, exactly as far as you can actually see.

A platformer is raycasting too, more often than people expect. The character that slides along a wall instead of sinking into it, the one that lands on a platform without sinking half a pixel into it, is usually resolved by firing a row of short rays from the edge that is moving and stopping at the shortest one.

Then there is everything else. Lasers bouncing off mirrors in a puzzle game. Bullets that go where you aimed instead of teleporting to the target. Enemies deciding whether they have a clear shot. A tower checking if a target is behind cover.

All of those are the same question asked in slightly different ways: what does this ray hit?

It is cheap, and that is the point

The reason raycasting shows up so often is that it lets you ask something about the world without simulating the world.

You do not need a physics engine to know whether a guard can see the player. You do not need collision bodies, contact resolution, restitution or any of that machinery. You need one line and one answer. In a game running at sixty frames per second, asking a few hundred of these questions per frame is nothing, and a few hundred is a lot more than most games ever need.

It is also the kind of code that gives you exact answers. Collision systems tend to be approximate, they nudge things apart and hope you do not notice. A ray either hits or it does not, and when it does, it tells you exactly where.

The part where people overcomplicate it

Here is where I want to say something slightly unpopular.

If you go looking for raycasting code, you will find implementations that handle rotated rectangles, circles, convex polygons, capsules, arbitrary meshes. They are impressive. They are also, for most 2D games, solving a problem you do not have.

Look at what your game is actually made of. A tilemap. Some rectangular platforms. Crates and doors and pickups that are, geometrically speaking, boxes. Even when the art is a beautifully drawn tree, the thing the code cares about is the rectangle around its trunk.

Axis aligned means the box sits square with the world, never rotated. Almost everything in a 2D game already is.

So I wrote a library that only knows about two things: a grid of square tiles, and loose rectangles placed anywhere you like. That is it.

What you get by giving things up

Restricting yourself to boxes buys you more than simplicity.

A ray against an axis aligned box comes down to a few divisions and a couple of comparisons. There is no trigonometry involved and nothing to solve iteratively. It is about as fast as geometry gets.

A grid is even better. Instead of testing the ray against every tile in the level, you walk the grid cell by cell along the ray, in order, and stop at the first thing that blocks you. The cost depends on how far the ray travels, not on how big your map is. A level with ten thousand tiles costs the same as one with a hundred, as long as the ray is the same length.

Loose rectangles get a spatial hash, which is a fancy way of saying the library only tests the ones near the ray.

And because a box has flat faces, the normal at the contact point is always one of four directions. That normal is what makes a laser bounce correctly and a box slide along a wall rather than sticking to it, and you get it for free instead of computing it.

When it is not enough

Honesty time. You will hit the limits of this approach if your game has sloped ground, rotated crates, round pillars that the player is supposed to duck behind, or a destructible terrain that is not on a grid.

Those are real games and this is not the tool for them. But notice how specific that list is. Most of the 2D games written would have been perfectly served by boxes.

The library decides nothing

One last thing, and it is the design choice I care about most.

The library answers the question and stops there. It holds no state, moves nothing and has no opinion about what counts as an obstacle.

Whether glass blocks a bullet but not a line of sight, whether a platform stops you only when you land on it from above, whether that door is locked right now: those are rules of your game, and they belong in your game. You pass a filter, the filter reads whatever fields you put on your own tile definitions, and it decides. Three lines of code, written by you, in a place where you can find them again.

That is why the whole thing is one file with no dependencies, commented line by line so you can read it rather than trust it, and why the examples are probably more interesting than the source itself.

If you build something with it, or if it breaks, let me know.

231 games covered