A Simple 2D Physics System For Platform Games

Games From Earth
8 min readNov 26, 2020

--

Because so many of you (two people!) asked me, in this tutorial I will explain how the physics in Sunblaze work. This tutorial will outline the concepts I used, but it doesn’t have any code. There’s a couple of reasons for this :

  • My current implementation has a lot of stuff added to it that would confuse things.
  • I want this tutorial to be language and engine agnostic.

If there’s enough interest I might follow up with a tutorial that includes some code, but for now I just wanted to talk about the concepts.

This tutorial may not be very beginner-friendly, but should be fine for anyone with some experience.

Why Custom Physics?

Mostly personal preference. I’ve made quite a number of platform game engines using “real” physics engines, mostly using raycasts, and while this is certainly a viable option, I find that it’s overly complex and sometimes hard to get right. I often found myself fighting the physics engine to get exactly what I wanted.

Questions or Feedback?

If you have any questions about this tutorial, hop on over to our Discord, where there’s a dedicated #game-dev-advice channel : https://discord.gg/ge7pRtynBG

Limitations

The method that I’m outlining is designed for a pixel-perfect system (so integer values), using Axis-Aligned Bounding Boxes (AABBs). This means only rectangular shapes without rotations. It’s certainly possible to extend this method to work with other shapes and slopes, but that’s beyond the scope of this tutorial.

Sectors

Most platform games store their tile data in a grid as an optimization, since it’s a lot cheaper to check whether something is colliding with a tile in a grid versus looping through all tiles and seeing if it collides with anything.

In Sunblaze, the tiles themselves can move during transitions, so a rigid grid system wouldn’t work. Instead of this, I divide all entities (both solids and actors) into sectors of 4x4 tiles. It’s possible for an entity to reside in more than 1 tile.

When something moves, it is removed from sectors it’s no longer inside of, and added to new ones. When they are disabled they are removed from all the sectors they were in, and when they are enabled again they are added to the relevant sectors.

This way, to figure out what an object is colliding with, I only had to check a limited amount of other objects, based on what sectors the current object is overlapping with. This is cheap enough that you should be able to use a system like this over a grid system in most cases, and it simplifies things a bit since you don’t have to treat moving platforms differently from tiles.

Movement

To keep things simple, let’s assume for now that we’re working in a 1D game that only has movement over the horizontal axis. And to make it even simpler, for now we’re only moving in one direction (right).

The basic idea behind these physics is that we move an actor in smaller steps — the maximum size of these steps being such that the character never clips past any solid — and if we encounter a collision, we move back as much as needed.

So first we need to determine how much we can move an actor without clipping past any solid. For this we need to know the size of our actor, and what our smallest possible solid is.

Let’s assume we have a character that’s 4px wide, and our smallest solids are tiles that are 8px wide. Let’s also assume the character is standing right next to a tile.

A 4x4 character next to an 8x8 tile

We’re not looking for collisions yet, we’re only trying to figure out by how much we can move an actor without clipping past a tile.

As you can see, we can safely move it by the size of our tile (8px) without going past it:

But if we also move it by the size of our actor (4px), we clip right past the tile :

As you can see, we should have moved 1px less to still catch the collision with this tile :

So the maximum amount we can move an actor without clipping past a tile is (tileSize+actorSize-1), or in the case of our example 8+4–1, or 11px.

Knowing this number, we split our movement up in steps with a maximum of 11px. In reality, it would be rare for a character to move more than the size of a tile per frame, but we want our engine to be accurate even at very high speeds.

So let’s assume our character is moving at a speed of 25px this frame. We split this movement up into 3 steps so that the steps are never larger than 11px. In this case, that would result in 11px, 11px and 3px.

For each step, we check if the character would overlap with anything, keeping in mind the sectors it would be at for that position so we don’t have to check every single solid in the level. If there’s no overlap, it’s safe to move the character by that amount.

If there’s an overlap, we loop through all the solids it’s overlapping with and figure out the leftmost edge (when moving to the right).

Then all we need to do is move the player by the amount needed so its rightmost edge would touch that leftmost edge, and stop moving.

Let’s visualize this process. Here’s a starting position, with the player 2px from the left edge of the tile.

Let’s say the player is moving at 15px per frame. First, we check if there’s any overlap when we move it by 11px :

Blimey, there is! Let’s find the leftmost edge of all the tiles we’re overlapping with (in this case there’s only 1 tile) :

So now we need to figure out how to move the player from the starting position of this step so its right edge touches this leftmost edge.

The distance between these two edges is 2px, so we now know we can move by 2px without overlapping anything. Hooray!

Floating Point Movement

Our movement function should probably accept floating point vales. When you try to move 10.4px, you actually move 10px and carry over the .4 for the next frame. If on the next frame you move 5.8px, you add the remainder to that to get 6.2px. You then move by 6px, and carry over the .2px.

Whenever you hit a wall, or when the character doesn’t move for a frame, you can discard the remainder.

Get On All Fours

So now that we can move in one direction, how do we modify that for all 4 directions?

Moving left is basically the same as moving right, but with negative values. Naming variables can become a little tricky. Earlier we were looking for the leftmost edge of tiles we’re colliding with, and that becomes the rightmost edge with opposite movement. So I call this the “closestEdge”, but there might be a better name for it.

For supporting vertical movement as well, there are two options. You can copy and paste your MoveX function and slightly tweak it, which works well enough but it makes it harder to update things.

So I opted for passing an axis parameter (which is an enum containing Axis.Horizontal and Axis.Vertical), and having the function behave differently based on that. This does make the movement function a little harder to read, but I preferred it over having two mostly identical functions.

Then I have a Move(float x, float y) function which calls MoveAxis(Axis.Horizontal, x) and MoveAxis(Axis.Vertical, y) in succession.

Returning Collisions

My movement functions also receive an optional results list in which they would place any solids we’re touching after moving. This is a filtered list of the overlaps from the previous step, containing only the solids that share an edge with the leftmost edge (assuming we’re moving right).

What About Gravity?

I decided to keep gravity out of my physics engine. This seems counter-intuitive, and it might seem weird to even talk about a physics engine if there’s no gravity involved. But I prefer handling gravity directly from the gameplay code.

The reason for this is that I didn’t want to enforce how a game deals with timesteps. For my game, I wanted the physics to be tied to the rendering instead of having a fixed update loop. This was important for my game, but probably not for others.

So maybe a better word for this is a Collision Engine?

In Action

A bit of a shameless plug, but I wanted to show what this system is capable of :

Other Resources

A good read is Maddy Thorson’s tutorial : https://maddythorson.medium.com/celeste-and-towerfall-physics-d24bd2ae0fc5

In their system, they use pixel-by-pixel movement. It’s a bit different from the way I handle movement, but some of the concepts they outline are compatible with what I’m doing. I highly recommend reading it, especially for a good way on how to deal with moving solids.

That’s It

I hope this tutorial is useful to someone. If you have any questions, please join our discord, or ask in the comment section.

If you’re interested in finding out why I decided to make Sunblaze, a Celeste-inspired game, check out this article : https://gamesfromearth.medium.com/why-im-making-sunblaze-a-celeste-inspired-game-b7b1dde27c41

--

--