A common requirement in games is to make a body move at a constant speed. This could be a player character in a platform game, a spaceship or car, etc. Depending on the game, sometimes a body should gain speed gradually, in other situations you might want it to start and stop instantaneously. It is very tempting to use the b2Body_SetLinearVelocity function to explicitly set the velocity for a body to accomplish this, and indeed it does get the job done, but this approach has its drawbacks. While it often looks fine on the screen, setting the velocity directly means the body is not correctly participating in the physics simulation. Let's see how we can use the more realistic forces and impulses to move a body at a desired speed.

We'll look at two situations, one where the body should immediately start moving at the desired speed, and one where it should accelerate gradually until it reaches a specified top speed. To start with we'll need a scene with one dynamic body, and we'll make some static body walls to fence it in. This fence will come in handy in some of the upcoming topics too. To keep track of what the user wants to do, we'll have a variable to store the last received input.

// Enumeration of possible input states
const MoveState = {
    STOP: 0,
    LEFT: 1, 
    RIGHT: 2
};

// Create the world, and let's use the Phaser helper this time
const world = CreateWorld();
const worldId = world.worldId;

// Create a box
const box = CreateBoxPolygon({ worldId: worldId, position: new b2Vec2(0, 0), type: b2BodyType.b2_dynamicBody, size: 1, density: 1, color: b2HexColor.b2_colorGold });
const bodyId = box.bodyId;
const shapeId = box.shapeId;

// Create static body for walls
const bodyDef = b2DefaultBodyDef();
bodyDef.type = b2BodyType.b2_staticBody;
bodyDef.position = new b2Vec2(0, 0);
const staticBodyId = b2CreateBody(worldId, bodyDef);

const shapeDef = b2DefaultShapeDef();

// Add four walls to the static body
const ground = b2MakeOffsetBox(20, 1, new b2Vec2(0, 0), 0);
b2CreatePolygonShape(staticBodyId, shapeDef, ground);

const ceiling = b2MakeOffsetBox(20, 1, new b2Vec2(0, 40), 0);
b2CreatePolygonShape(staticBodyId, shapeDef, ceiling);

const leftWall = b2MakeOffsetBox(1, 20, new b2Vec2(-20, 20), 0);
b2CreatePolygonShape(staticBodyId, shapeDef, leftWall);

const rightWall = b2MakeOffsetBox(1, 20, new b2Vec2(20, 20), 0);
b2CreatePolygonShape(staticBodyId, shapeDef, rightWall);

let moveState = MoveState.STOP;

We'll need a keyboard handler for input:

function handleKeyboard(key) {
    switch(key.toLowerCase()) {
        case 'q': // move left
            moveState = MoveState.LEFT;
            break;
        case 'w': // stop
            moveState = MoveState.STOP;
            break;
        case 'e': // move right
            moveState = MoveState.RIGHT;
            break;
        default:
            // run default behavior
            break;
    }
}

// Add keyboard event listener for browser (or use Phaser.input as previously)
document.addEventListener('keydown', (event) => {
    handleKeyboard(event.key);
});

From now, all further changes will be made in the step function to implement the movement behaviour depending on this input.

Setting velocity directly

Before we get started on the force/impulse methods, let's see how b2Body_SetLinearVelocity works to directly specify the velocity of the body. For many applications this may be good enough. Inside the step function, we will take whatever action is required each time step:

// inside step function
const vel = b2Body_GetLinearVelocity(bodyId);
switch (moveState) {
    case MoveState.LEFT:  vel.x = -5; break;
    case MoveState.STOP:  vel.x = 0; break;
    case MoveState.RIGHT: vel.x = 5; break;
}
b2Body_SetLinearVelocity(bodyId, vel);

Here we are getting the current velocity and leaving the vertical component unchanged, and feeding it back because we only want to affect the horizontal velocity of this body.

Trying this code in the testbed you'll see that this setup gives us the instantaneous speed scenario. To implement a gradual acceleration up to a maximum speed, you could do something like this instead:

switch (moveState) {
    case MoveState.LEFT:  vel.x = Math.max(vel.x - 0.1, -5.0); break;
    case MoveState.STOP:  vel.x *= 0.98; break;
    case MoveState.RIGHT: vel.x = Math.min(vel.x + 0.1, 5.0); break;
}

This will increase the velocity linearly by 0.1 per time step to a maximum of 5 in the direction of travel - with a standard framerate of 60fps the body will take 50 frames or just under a second to reach top speed. When coming to a stop the speed is reduced to 98% of the previous frame's speed, which comes to about 0.98^60 = a factor of about 0.3 per second. An advantage of this method is that these acceleration characteristics can be easily tuned.

Using forces

Forces are more suited to the gradual acceleration to top speed scenario, so let's try that first:

const vel = b2Body_GetLinearVelocity(bodyId);
let force = 0;
switch (moveState) {
    case MoveState.LEFT:  if (vel.x > -5) force = -50; break;
    case MoveState.STOP:  force = vel.x * -10; break;
    case MoveState.RIGHT: if (vel.x < 5) force = 50; break;
}
b2Body_ApplyForceToCenter(bodyId, new b2Vec2(force, 0), true);

This is similar to the above in that the acceleration is linear and the braking is non-linear. For this example we have a pretty basic logic which simply applies the maximum force in every time step where the body is moving too slow. You will likely want to adjust this for the application you are making eg. a car might accelerate quickly at low speeds, but as it nears the maximum speed its rate of acceleration decreases. For this you could just look at the difference between the current speed and the maximum speed and scale back the force as appropriate.

Remembering from the previous topic that forces act gradually, it might seem unlikely that we could use them to implement an instantaneous speed change. However, if we make the time span very short and the force very large, we can get the same effect as an impulse. First we need to do a little math...

The relationship between force and acceleration is f = ma where m is the mass of the body we're moving, a is acceleration which is measured in "units per second per second", and f is the force we want to calculate. The acceleration could also be called "velocity per second", since velocity and "units per second" are the same thing. So we could write this as f = mv/t where t is the length of time the force will be applied.

We can get m by using the body's b2Body_GetMass function. v will be the change in velocity we desire which is the difference between the maximum speed and the current speed. To get an instantaneous speed change effect, we would be applying the force for one time step or 1/60th of a second if using the default testbed framerate. Now we know everything except f, so we do something like this:

const vel = b2Body_GetLinearVelocity(bodyId);
let desiredVel = 0;
switch (moveState) {
    case MoveState.LEFT:  desiredVel = -5; break;
    case MoveState.STOP:  desiredVel = 0; break;
    case MoveState.RIGHT: desiredVel = 5; break;
}
const velChange = desiredVel - vel.x;
const force = b2Body_GetMass(bodyId) * velChange / (1/60.0); // f = mv/t
b2Body_ApplyForceToCenter(bodyId, new b2Vec2(force, 0), true);

This should give you the same behaviour as the b2Body_SetLinearVelocity did, while still remaining a realistic physics scenario.

Using impulses

Astute readers will notice that the code immediately above is basically simulating an impulse. However since impulses already take into account the length of the simulation timestep, we can just take the time part out and get the same effect with b2Body_ApplyLinearImpulseToCenter:

const vel = b2Body_GetLinearVelocity(bodyId);
let desiredVel = 0;
switch (moveState) {
    case MoveState.LEFT:  desiredVel = -5; break;
    case MoveState.STOP:  desiredVel = 0; break;
    case MoveState.RIGHT: desiredVel = 5; break;
}
const velChange = desiredVel - vel.x;
const impulse = b2Body_GetMass(bodyId) * velChange; // disregard time factor
b2Body_ApplyLinearImpulseToCenter(bodyId, new b2Vec2(impulse, 0), true);

For a gradual acceleration, just adjust the desired change in velocity as appropriate.

switch (moveState) {
    case MS_LEFT:
        desiredVel = Math.max(vel.x - 0.1, -5.0);
        break;
    case MS_STOP:
        desiredVel = vel.x * 0.98;
        break;
    case MS_RIGHT:
        desiredVel = Math.min(vel.x + 0.1, 5.0);
        break;
}

Credits

This tutorial is adapted from an original piece of work created by Chris Campbell and is used under license.