Reminder: in October 2018, I’m pygame.org’s artist in residence. You can ask me questions about Trosnoth, support me in my Trosnoth development, and follow this blog for articles about Trosnoth and what I’m working on.
Intro to platformer physics
Trosnoth is essentially a platform game. Sure, it’s not a traditional, one-player platform game where the main goal is to avoid obstacles and get to the end of the level. But it’s still a 2D side-view side-scroller with platforms and obstacles, so it counts as a platformer in my books.
Platform games have unusual physics. You might think, ‘Can’t you just use real-world physics with maybe a lower gravitational constant?’ But the answer is a resounding, ‘No’. As it turns out, it’s really hard to jump with agility between platforms or dodge bullets in mid-air in real life. The main aim of platformer physics is to feel responsive, not to be realistic.
In this article, I’ll briefly outline the steps in Trosnoth’s player physics calculations. I’ll go into some detail, but because October is nearly over and I said I’d write four blog articles this month, I’ll gloss over a few of the finer details.
How Trosnoth calculates collisions
An important part of game physics is calculating collisions between solid things. In the case of player movement, the important collisions to detect are between players and obstacles (walls, roofs, platforms and floors). Some games store the map as a grid of tiles, but Trosnoth’s approach is to store obstacles as polygons. Each player has an elliptical collision solid, and we calculate collisions using separating axis theorem.1
You may not think you needed to know that, but the following outline assumes you already know we’re using polygons instead of tiles.
Step 1: is the player on the ground?
The first step in Trosnoth’s physics calculations is to work out if the player is touching the ground. Trosnoth’s approach is to ask, ‘If the player moved downwards by a very small amount, would it hit something?’ If the answer is yes, then the player is on the ground.
Side note: one-way platforms
Trosnoth’s approach to one-way platforms is this: if the player is pressing the down key, then one-way platforms are completely intangible to that player. So if the player would otherwise be standing on a one-way platform, but they are holding the down key, then that player is not considered to be on the ground.
Step 2: jumping and dropping
The second step is to check if the player is trying to jump, or to drop from a wall or roof. These checks only apply if the player is on the ground or is hanging from a wall or roof. Dropping is quite straightforward: if the player is pressing the down key, or whichever of the left or right keys which would move the player away from the wall or roof, then the player drops.
Jumping is slightly more involved. If the player is allowed to jump and is pressing the jump key, then their vertical velocity is set to a fixed upward value. That’s the straightforward bit. But we also have two short timers that apply to jumping.
Firstly, there’s a brief window of time after a player has dropped from a roof or wall during which they are still allowed to initiate a jump. This window exists to account for cases where a player is hanging onto a wall and tries to press sideways and jump, but actually presses the sideways key just before the jump key. Without this window, the player would fall from the wall, then because they are in the air the jump key would do nothing. But this window makes the game behave as if they pressed the keys together.
Secondly, during the first few hundred milliseconds of a jump, the vertical velocity stays at a constant upward velocity unless the player releases the jump key first. This allows the player to jump to different heights depending on how long they hold the jump key for. In addition to this, when the player releases the jump key while they’re still moving upwards, their vertical velocity is set to zero. This makes the jump movement feel slightly more responsive: you let go of the jump key, and you immediately stop jumping.
Step 3: acceleration
Trosnoth originally had very retro physics. Think Commander Keen or Cosmo’s Cosmic Adventure. If you were holding down the left or right keys, you moved left or right at a constant horizontal speed. More recently though, we’ve added momentum-based physics to the development branch. Here’s how it works:
Firstly, if a player grabs onto a wall or roof, their velocity is instantly set to zero. Apart from this, there are two possibilities:
Moving along the ground
If a player is on the ground, they’re either running, walking or slowing. They’re running if they’re holding down the left or right key and facing in the same direction. They’re walking if they’re holding down the left or right key and facing in the opposite direction. If they’re not holding the left or right key, the player is slowing.
When a player starts running, they have their velocity in the direction of the ground surface set to a particular minimum run speed. They then accelerate at a constant rate up to a maximum run speed. When a player is walking, they decelerate at a constant rate down to a minimum walk speed. When a player is slowing, they decelerate at the same rate, but decelerate all the way to zero.
Moving through the air
If a player is in the air, their acceleration is determined by whether they are pressing the left or right arrow keys. But if the player is travelling faster than a certain horizontal velocity, pressing the arrow key will not accelerate them any more in the direction they are travelling. But this is not a horizontal terminal velocity: if a player is already travelling faster than that speed (e.g., after speeding themselves up with the grappling hook) then they will not automatically have their horizontal velocity capped to that value.
After horizontal acceleration is applied, gravity is applied. This is just a constant vertical acceleration, capped at a vertical terminal velocity. If the player is moving along the ground, gravity is only applied if their grappling hook is attached somewhere.
Step 4: grappling hook
After we’ve worked out what the player’s velocity should be, we constrain that velocity to certain limits if the grappling hook is attached. In this step we basically work out how much rope is left and ensure that the player’s motion doesn’t exceed the length of the rope during this iteration.
Trosnoth’s grappling hook pulls the player in at a constant minimum rate. The player can move in more quickly than the minimum, e.g. by jumping or running towards the point where the hook is attached.
If the player’s velocity is pulling them away from the grappling hook’s pull by more than a certain amount, then the grappling hook snaps and the player is released. This check was added to make it easier to use the grappling hook to swing horizontally without having all of the player’s momentum lost by the pull of the grappling hook.
Step 5: actually applying the velocity
At this point, the player’s velocity has been calculated. The player’s motion for this iteration is then calculated by multiplying this velocity by the time interval, then we check if they would hit an obstacle. If so, we check if this collision qualifies as grabbing a wall.
To grab a wall, a player either needs to be pulling themselves in by grappling hook and be almost all the way in, or the collision needs to be with a vertical wall and the player needs to be pressing the direction key that would push them further into the wall. If the player has grabbed a wall, then that’s the end of their motion.
If the player has collided with an obstacle but the collision occurred within only a very tiny distance of the player’s original position, then we rerun the calculation once, but only consider the component of the player’s velocity that runs along the surface of the obstacle they collided with. We do this recalculation to avoid cases where a player is at the corner of two obstacles and we detect the wrong collision first.
If the player does collide with an obstacle and it doesn’t qualify as grabbing a wall, then we damp the component of the player’s velocity that is normal to the obstacle.
Step 6: adhering to the ground
If the player started this iteration on the ground, we check if projecting a ray downwards a certain distance from the player would still collide with the ground. If so, we move the player down to on top of the ground. This is to avoid the player leaving the ground (and thereby being unable to jump) with every little bump or corner on the ground.
Step 7: discretisation
As a final step, if the player is holding onto a wall or roof, or they are on the ground and completely still, we look up their rough position in a database of possible stationary positions, and move them to the position in the database. This is not at all necessary for human players, but as I wrote about last week, it’s very helpful to simplify the search space for bot path-finding. And since Trosnoth aims to be scrupulously fair, we do this for human players as well as bots.
1 We calculate collisions mostly using separating axis theorem. We use slightly different calculations for one-way platforms because they aren’t complete solid polygons.