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 clipping3. 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);