Physics

Character Controller

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.

Differences from Dynamic Collider

While both the Character Controller and Dynamic Collider can achieve object movement, they have significant differences:

FeatureCharacter ControllerDynamic Collider
Movement MethodDirect displacement control via move() methodMovement through forces or impulses
Collision ResponseManual collision response, automatic step and slope handlingPhysics-based collision response
Gravity HandlingRequires manual gravity implementationAutomatic scene gravity application
Shape LimitationsCan only use one collision shapeCan use multiple collision shapes
Use CasesCharacter control, first/third-person gamesPhysics simulation, rigid body motion
Performance CostLowerHigher

Recommendations:

  • Use Character Controller when precise control over character movement and collision response is needed
  • Use Dynamic Collider when realistic physics simulation is required

Usage

  1. Select the target entity and click the Add Component button in the inspector to add the CharacterController component.
  1. Set the controller's collision shape to match the character's appearance as closely as possible. For detailed information about collision shapes, please refer to the ColliderShape documentation.

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.

  1. Adjust the collider's properties according to your needs to modify the object's physical behavior. The meaning and function of each property are explained below.

Property Description

Properties Inherited from Collider

PropertyDescription
shapesCollection of collision shapes

Specific Properties

PropertyDescriptionDefault
stepOffsetMaximum step height the character can automatically climb.
  • Must be greater than or equal to 0
  • Actual climbable height = stepOffset + contact offset of collision shape
0.5
slopeLimitMaximum slope angle (in degrees) the character can walk on.
  • Surfaces steeper than this angle will be treated as unwalkable walls
  • Affects the character's climbing ability
45°
nonWalkableModeDefines how to handle unwalkable surfaces.
  • PreventClimbing: Prevents the character from climbing unwalkable slopes but doesn't force other movements (default)
  • PreventClimbingAndForceSliding: Prevents the character from climbing unwalkable slopes and forces the character to slide down
PreventClimbing
upDirectionDefines 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)

Public Methods

Methods Inherited from Collider

MethodDescription
addShapeAdd a collision shape
removeShapeRemove a specified collision shape
clearShapesClear all collision shapes

Specific Methods

MethodDescription
moveMove the character controller. Returns a collision flag value indicating collision status.
  • displacement: Movement vector
  • minDist: Minimum movement distance
  • elapsedTime: Elapsed time

Collision Flags

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 NameValueDescription
None0No collision occurred
Sides1Collided with sides
Up2Collided with the top (e.g., ceiling)
Down4Collided with the bottom (e.g., ground)

Script Usage

Basic Configuration

// 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

Using the Move Function

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
}

Slope/Step Walking

  1. Slope Walking
// 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
  1. Step Walking Adjustment
// Adjust stepOffset to control climbable step height
controller.stepOffset = 0.3; // Lower steps
controller.stepOffset = 0.5; // Higher steps

Gravity Handling

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;
    }
  }
}

Limitations and Considerations

  1. Shape Limitations

    • Can only use one collision shape
    • Recommended to use CapsuleColliderShape as the collision shape
  2. Performance Considerations

    • Character Controller has better performance than Dynamic Collider
    • But each Character Controller consumes some physics calculation resources
    • It's recommended to control the number of Character Controllers in the scene

For a complete example, please refer to:

Was this page helpful?