How to build a platformer AI - Part 1: Basic movement

A line drawing showing a human character jumping with a parabolic trajectory.
A jump trajectory with slow-rise and fast-fall.
This is part 1 of 5 in a series describing how to implement 2D-platformer AIpathfinding, and character movement. You can see a working example of these techniques in the Surfacer framework for Godot.

Part 1: Basic movement

The animation frame loop and physics engine

Let's start with the basics. All video games have an animation loop, and most games have some sort of physics engine. These form the foundations of platformer movement.

The animation loop gives us a chance to update state and redraw things each frame.

The physics engine makes it easier for us to move things around based on things like gravity and current velocities. Instead of explicitly redefining the position each frame, we can instead apply forces or acceleration as needed, and the physics engine will in-turn update velocities and positions each frame.

Also, the physics engine includes collision-handling, which lets us know when and where things collide with each other. The collision system also lets us determine how things should react when they collide, such as whether they should bounce or slide, and what their new speed, direction, and rotation should be.

Basic platformer movement

Next, we build my own movement logic on top of the out-of-the-box physics engine.

General platformer mechanics

You might think that all platformers are pretty much the same. You jump from one platform to the next. Aside from jump height and movement speed, there's not much difference, right? No! It's actually incredible how many different knobs we can adjust when creating the movement mechanics in a platformer game. And it's far more of an art than a science.

Here are some examples of some of the more popular decisions we can make when designing platformer mechanics.

Variable-height jumps

  • In most platformers, it's important that the player can jump higher and farther if they hold the jump button longer.
  • This gives the player a lot more control over accurately landing where they want to be.

Slow-rise and fast-fall

  • With this technique, we can apply a weaker gravity value while the character is still ascending in their jump, and then we can apply a stronger gravity value while the character is descending in their jump (or after they've released the jump button).
  • The player is usually planning their trajectory and landing during the ascent. But by the time they start descending, they usually don't need to alter the trajectory, and are just waiting for the jump to be done. So reducing this "down-time" (get it?), can help to keep the player engaged and less bored or frustrated.
  • You might not think it would, but this has a big impact on how "responsive" the game feels and how fun it is to play.

Climbing on non-floor surfaces

  • Should the player be able to climb on walls? Or ceilings?
  • What about the enemies?
  • E.g., Classic Mario can only walk on floors, but the spiky-red-shell enemies—"Spinies"—can usually climb along walls and ceilings.
  • And, if the player can climb on walls, how do jump velocities differ from a floor or a wall?

Horizontal acceleration

  • Sounds simple, but most platformers use a constant horizontal speed (or two discrete speeds, one for walking and one for running) rather than supporting acceleration and variable speeds.
  • This has a big impact on jump distance.
  • Also, what about in-air horizontal acceleration? This is not-at-all realistic, but most platformers let the player change their horizontal speed while air-borne the same as if they were walking.

Surface friction

  • Ever play a platformer game with icy surfaces that you slip across? Suuuuuper frustrating right?? But this does make the game a lot more interesting to play.
  • Even subtle changes in surface friction can have a big impact on how things "feel" to the player, and can make a big difference in terms of how easy it is to move and jump around.

In-air friction

  • The value of this might be less intuitive than surface friction, but it's probably a lot more important!
  • The underlying problem is that players often aren't actually very good at predicting trajectories. So when they've released the sideways button, they usually don't want to keep moving horizontally as much as they are! This makes players overshoot their targets and get quickly frustrated with how hard the game is to control.
  • In-air friction is most powerful when only conditionally applied while the player has released the sideways movement.
  • This lets the player still have a great deal of control for moving as far as they need when pressing sideways. But this then lets the player easily stop sideways movement at the right time once they've released the sideways button.

Min and max speeds

  • These are pretty standard in platformers.
  • We need to have some sort of cap for how fast the player can move. This cap is usually slower for horizontal movement and faster for vertical movement.
  • Also, we usually need a minimum cut-off speed, below which, the player just stops.

Coyote time and early-jump correction

  • Remember the Wile E. Coyote and Road Runner cartoons? After running off a cliff, the coyote always spends an extra second or two floating in air before realizing his mistake and falling to his doom.
  • "Coyote time" is what we call it when a platformer allows the player to be a little slow in pressing the jump button. Even if the character has technically already passed the edge of the surface, we can still trigger the jump as though they were still standing on solid ground.
  • "Early-jump correction" works similarly. When the player presses the jump button a little too soon, before landing on a surface, we can wait until they have landed and then trigger the jump as though they had pressed it at the right time.
  • These two techniques can make a platformer more forgiving for less skilled players.
  • Also, these can make a platformer feel "more responsive." This is because a player might otherwise think that the game just didn't notice their button click. This can be especially true if the game isn't running at a consistently high framerate.

Surfacer's character action handlers

In order to implement all of the low-level movement mechanics, like those discussed above, Surfacer uses an extensible character action-handler system (source code). An "action handler" is called each frame and defines some basic rule for how character movement should be updated depending on the current context.

Here are some examples:

  • The floor-friction action handler offsets the horizontal velocity depending on what surface the character is standing on (source code).
  • The in-air default action handler offsets the velocity according to either slow-rise or fast-fall gravity (source code).
  • The wall-jump action handler sets the velocity for a new jump (source code).

Each action handler is specific to a certain surface type—floor, wall, ceiling, in-air—and only the action-handlers for the current surface type are triggered each frame.

The benefit of this system is that it is highly decoupled, so it is very easy to make changes or add new mechanics. When creating a platformer game, these atomic movement mechanics tend to be very hard to keep separate. Consequently, the core movement logic for most platformers is a brittle mess of spaghetti code.


Comments