Character Controllers
Implement physics-based character movement:
// Create a character with capsule physics
const characterHeight = 3;
const characterRadius = 0.5;
// Create body parts
const body = BABYLON.MeshBuilder.CreateCylinder("body", {
height: characterHeight - 2 * characterRadius,
diameter: characterRadius * 2
}, scene);
const head = BABYLON.MeshBuilder.CreateSphere("head", {
diameter: characterRadius * 2
}, scene);
head.position.y = (characterHeight - 2 * characterRadius) / 2;
head.parent = body;
const feet = BABYLON.MeshBuilder.CreateSphere("feet", {
diameter: characterRadius * 2
}, scene);
feet.position.y = -(characterHeight - 2 * characterRadius) / 2;
feet.parent = body;
// Position the character
body.position.y = characterHeight / 2;
// Add physics with capsule shape (cylinder + sphere caps)
body.physicsImpostor = new BABYLON.PhysicsImpostor(
body,
BABYLON.PhysicsImpostor.CylinderImpostor,
{ mass: 1, friction: 0.5, restitution: 0.3 },
scene
);
// Variables for movement control
const moveDirection = new BABYLON.Vector3();
let isGrounded = false;
const jumpForce = 10;
const moveSpeed = 0.5;
// Input handling for character movement
const keyStatus = {};
scene.onKeyboardObservable.add((kbInfo) => {
const key = kbInfo.event.key.toLowerCase();
if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
keyStatus[key] = true;
} else if (kbInfo.type === BABYLON.KeyboardEventTypes.KEYUP) {
keyStatus[key] = false;
}
});
// Ground detection using raycasting
function checkGrounded() {
const origin = body.getAbsolutePosition();
const direction = new BABYLON.Vector3(0, -1, 0);
const length = characterHeight / 2 + 0.1; // Slightly longer than character radius
const raycastResult = scene.getPhysicsEngine().raycast(
origin,
direction,
length
);
return raycastResult.hasHit;
}
// Character controller update
scene.onBeforeRenderObservable.add(() => {
// Check if character is on ground
isGrounded = checkGrounded();
// Reset movement direction
moveDirection.setAll(0);
// Calculate move direction from input
if (keyStatus["w"]) moveDirection.z += 1;
if (keyStatus["s"]) moveDirection.z -= 1;
if (keyStatus["a"]) moveDirection.x -= 1;
if (keyStatus["d"]) moveDirection.x += 1;
// Jump if on ground
if (keyStatus[" "] && isGrounded) {
body.physicsImpostor.applyImpulse(
new BABYLON.Vector3(0, jumpForce, 0),
body.getAbsolutePosition()
);
}
// Normalize and apply movement
if (moveDirection.length() > 0) {
moveDirection.normalize();
// Align with camera direction
const cameraDirection = camera.getTarget().subtract(camera.position);
cameraDirection.y = 0;
cameraDirection.normalize();
// Create rotation matrix from camera direction
const rotationMatrix = BABYLON.Matrix.RotationY(
Math.atan2(cameraDirection.x, cameraDirection.z)
);
// Transform movement direction relative to camera
const transformedDirection = BABYLON.Vector3.TransformNormal(
moveDirection,
rotationMatrix
);
// Apply movement as impulse (only when on ground)
if (isGrounded) {
body.physicsImpostor.applyImpulse(
transformedDirection.scale(moveSpeed),
body.getAbsolutePosition()
);
}
}
});