Categories
AI Development Games

Jumping, launch velocities and rounding errors

The physics behind a jump in a platformer is pretty simple and something I must have written a thousand times, but having an AI player jump and land where it wants to is considerably trickier. My initial attempt was to string a bezier curve between the start and end points and just make it follow that – this is easy to do and very reliable (as it will always get to the exact end point) but produces unconvincing motion. It really needs to use the proper physics otherwise it looks out of place.

So if we’re using the proper physics we want to calculate a launch velocity then just let the simulation do the rest. However finding a suitable launch velocity isn’t easy – there’s lots of unknowns and variables, largely because there’s lots of potential jump arcs which take you between two points. You need to nail down a few of the variables so that ideally only one solution pops out.

After several failed attempts I’ve found that specifying the jump apex gives nice consistent and controllable results. I can pick the apex by applying an offset from the highest of the start and end points then split the jump into three components- the vertically to the apex, vertically from the apex, and horizontally for the whole jump:

ImmutableVector2f startPos = enemy.getPosition();
ImmutableVector2f endPos = nextWaypoint.getPosition();

// First, find jump apex
final float highestY = Math.max(startPos.y(), endPos.y());
final float apexY = highestY + 100;

// Vertically, to apex
// t = sqrt( 2s / a )

final float s1 = apexY - startPos.y();
final float t1 = (float)Math.sqrt( (2 * s1) / -FallBehaviour.GRAVITY);

// What's our launch speed for this section?
final float vy = -FallBehaviour.GRAVITY * t1;

// Vertically from apex

final float s2 = apexY - endPos.y();
final float t2 = (float)Math.sqrt( (2 * s2) / -FallBehaviour.GRAVITY);

// Total time for entire jump
final float tTotal = t1 + t2;

// Horizontally
final float s3 = (endPos.x() - startPos.x());
final float vx = s3 / tTotal;

brain.jump( new Vector2f(vx, vy) );

Unfortunately this has two down sides – the structure of a level means that sometimes there is an interveaning platform that the AI wants to jump “through” but the simulation causes it to land there instead. The other down side is that rounding errors mean you don’t always hit the target exactly, and since my collision is all sub-pixel that means you can miss the edge of the platform by a fraction and end up plummetting to your doom.

The solution is to also calculate the expected time length of the jump and when that time is reached snap the enemy to the target point (and disable all other ground collisions in between). This gives us the best of both approaches – the AI always ends up exactly where it wanted to, but while following a convincing curve. And since we’re doing a proper simulation if something happens during the jump (like we get hit) we can easily turn off the special collision handling and let the physics take over.

On an entirely different note, Deaths (thanks indie gamer) is a really neat idea for a platformer with a twist (and, amusingly, no AI whatsoever). Passive online interaction like this is something I’ve been thinking about for a while but can never manage to come up with an idea where it’s an important part of the gameplay rather than just a novelty – Deaths manages this quite well I think.