Apply Gravity to Ammo.js Kinematic Character Controller

This article explains how to manually calculate and apply gravity to a kinematic character controller in Ammo.js. While the btKinematicCharacterController has a built-in gravity system, handling gravity manually gives you precise control over jump curves, terminal velocity, and variable gravity directions. We will cover disabling the default gravity, calculating the fall velocity, and applying the final movement vector in your physics update loop.

1. Disable Built-in Gravity

To take manual control, you must first set the controller’s internal gravity to zero. This prevents Ammo.js from applying its own automatic downward force to the character.

// Disable default gravity
characterController.setGravity(0);

2. Track Vertical Velocity

You need to maintain a state for your character’s vertical velocity. Define variables for gravity, terminal velocity, and the character’s current vertical speed in your setup code.

let verticalVelocity = 0;
const gravity = -9.81; // Acceleration in m/s^2
const terminalVelocity = -50; // Cap falling speed to prevent clipping

3. Calculate Gravity in the Update Loop

In your simulation step or game loop, calculate the gravity over the elapsed frame time (deltaTime). If the character is on the ground, reset the vertical velocity to zero (or a tiny negative value) to stop downward acceleration from building up.

// Check if the character is touching the ground
const isGrounded = characterController.onGround();

if (isGrounded) {
    // Reset vertical velocity when grounded
    verticalVelocity = -0.1; 
} else {
    // Apply gravity over time
    verticalVelocity += gravity * deltaTime;

    // Clamp to terminal velocity
    if (verticalVelocity < terminalVelocity) {
        verticalVelocity = terminalVelocity;
    }
}

4. Combine and Apply Movement

Combine your horizontal movement input with your calculated vertical velocity. To move the kinematic character, translate the velocities into a displacement vector and pass it to setWalkDirection.

// Calculate horizontal movement (example inputs)
let moveX = input.directionX * moveSpeed;
let moveZ = input.directionZ * moveSpeed;

// Scale horizontal movement by deltaTime to get displacement
let displacementX = moveX * deltaTime;
let displacementZ = moveZ * deltaTime;

// Scale vertical movement by deltaTime to get displacement
let displacementY = verticalVelocity * deltaTime;

// Create Ammo vector
let walkDirection = new Ammo.btVector3(displacementX, displacementY, displacementZ);

// Apply displacement to the character controller
characterController.setWalkDirection(walkDirection);

// Clean up Ammo memory to prevent leaks
Ammo.destroy(walkDirection);