The CharacterController is a special collider component designed specifically for handling character physics movement. It provides specialized movement algorithms and collision detection, capable of handling complex terrain like steps and slopes, making it particularly suitable for character control in first-person or third-person games.
While both the Character Controller and Dynamic Collider can achieve object movement, they have significant differences:
Feature | Character Controller | Dynamic Collider |
---|---|---|
Movement Method | Direct displacement control via move() method | Movement through forces or impulses |
Collision Response | Manual collision response, automatic step and slope handling | Physics-based collision response |
Gravity Handling | Requires manual gravity implementation | Automatic scene gravity application |
Shape Limitations | Can only use one collision shape | Can use multiple collision shapes |
Use Cases | Character control, first/third-person games | Physics simulation, rigid body motion |
Performance Cost | Lower | Higher |
Recommendations:
Unlike other colliders, the Character Controller can only have one collision shape. It is recommended to use a CapsuleColliderShape as the character's collision shape.
Property | Description |
---|---|
shapes | Collection of collision shapes |
Property | Description | Default |
---|---|---|
stepOffset | Maximum step height the character can automatically climb.
| 0.5 |
slopeLimit | Maximum slope angle (in degrees) the character can walk on.
| 45° |
nonWalkableMode | Defines how to handle unwalkable surfaces.
| PreventClimbing |
upDirection | Defines the character's up direction. Default is (0, 1, 0), which means Y-axis up in world space. Affects movement and collision detection direction | (0, 1, 0) |
Method | Description |
---|---|
addShape | Add a collision shape |
removeShape | Remove a specified collision shape |
clearShapes | Clear all collision shapes |
Method | Description |
---|---|
move | Move the character controller. Returns a collision flag value indicating collision status.
|
The move()
function returns a collision flag value that indicates the character controller's collision status with the environment. These flags can be detected using bitwise AND operations (&):
Flag Name | Value | Description |
---|---|---|
None | 0 | No collision occurred |
Sides | 1 | Collided with sides |
Up | 2 | Collided with the top (e.g., ceiling) |
Down | 4 | Collided with the bottom (e.g., ground) |
// Create character controller
const controller = entity.addComponent(CharacterController);
// Add capsule shape
const capsule = new CapsuleColliderShape();
capsule.radius = 0.5;
capsule.height = 2;
controller.addShape(capsule);
// Configure controller properties
controller.stepOffset = 0.5; // Set step height
controller.slopeLimit = 45; // Set maximum walkable slope angle
controller.upDirection = new Vector3(0, 1, 0); // Set upward direction
class CharacterMovement extends Script {
private _velocity = new Vector3();
onUpdate(deltaTime: number) {
const controller = this.entity.getComponent(CharacterController);
// Create displacement vector
const displacement = new Vector3();
Vector3.scale(this._velocity, deltaTime, displacement);
// Execute movement and get collision flags
// minDist: Minimum movement distance, usually set to 0
// deltaTime: Elapsed time for physics calculations
const collisionFlags = controller.move(displacement, 0, deltaTime);
// Handle collision response
if (collisionFlags & ControllerCollisionFlag.Down) {
// Character is on the ground
}
}
}
Usage example:
const flags = controller.move(displacement, 0, deltaTime);
// Check if touching ground
if (flags & ControllerCollisionFlag.Down) {
// Character is on ground
this._isGrounded = true;
}
// Check if hitting ceiling
if (flags & ControllerCollisionFlag.Up) {
// Character hit ceiling
this._velocity.y = 0;
}
// Check if hitting wall
if (flags & ControllerCollisionFlag.Sides) {
// Character hit wall
this._handleWallCollision();
}
// Can check multiple flags simultaneously
if ((flags & ControllerCollisionFlag.Down) &&
(flags & ControllerCollisionFlag.Sides)) {
// Character is touching both ground and wall
}
// Control walkable slope angle by setting slopeLimit
controller.slopeLimit = 60; // Allow steeper slopes
// Set how to handle unwalkable slopes
controller.nonWalkableMode = ControllerNonWalkableMode.PreventClimbingAndForceSliding; // Will slide down on too steep slopes
// Adjust stepOffset to control climbable step height
controller.stepOffset = 0.3; // Lower steps
controller.stepOffset = 0.5; // Higher steps
The Character Controller itself doesn't include gravity handling; you need to implement gravity effects manually in scripts. Here's a complete gravity handling example:
class CharacterMovement extends Script {
private _controller: CharacterController;
private _velocity = new Vector3();
private _isGrounded = false;
private _moveSpeed = 5;
private _jumpForce = 5;
private _gravity: Vector3;
onAwake() {
this._controller = this.entity.getComponent(CharacterController);
this._gravity = this.scene.physics.gravity;
}
onUpdate(deltaTime: number) {
const inputManager = this.engine.inputManager;
// Get input
const horizontal = inputManager.isKeyHeldDown(Keys.KeyA) ? -1 : inputManager.isKeyHeldDown(Keys.KeyD) ? 1 : 0;
const vertical = inputManager.isKeyHeldDown(Keys.KeyS) ? -1 : inputManager.isKeyHeldDown(Keys.KeyW) ? 1 : 0;
const jump = inputManager.isKeyDown(Keys.Space);
// Calculate movement direction
const moveDirection = new Vector3(horizontal, 0, vertical);
moveDirection.normalize();
// Apply movement speed
this._velocity.x = moveDirection.x * this._moveSpeed;
this._velocity.z = moveDirection.z * this._moveSpeed;
// Apply gravity
if (!this._isGrounded) {
this._velocity.y += this._gravity.y * deltaTime;
}
// Handle jumping
if (this._isGrounded && jump) {
this._velocity.y = this._jumpForce;
this._isGrounded = false;
}
// Execute movement
const displacement = new Vector3();
Vector3.scale(this._velocity, deltaTime, displacement);
const collisionFlags = this._controller.move(displacement, 0, deltaTime);
// Update ground status
this._isGrounded = (collisionFlags & ControllerCollisionFlag.Down) !== 0;
if (this._isGrounded) {
this._velocity.y = 0;
}
}
}
Shape Limitations
Performance Considerations
For a complete example, please refer to: