物理

角色控制器

角色控制器(CharacterController)是一种特殊的碰撞器组件,专门用于处理角色的物理运动。它提供了专门的移动算法和碰撞检测,能够处理台阶、斜坡等复杂地形,特别适合第一人称或第三人称游戏中的角色控制。

与动态碰撞器的区别

角色控制器和动态碰撞器虽然都可以实现物体的移动,但它们有着显著的区别:

特性角色控制器动态碰撞器
移动方式通过 move() 方法直接控制位移通过施加力或冲量来移动
碰撞响应手动处理碰撞响应,自动处理台阶和斜坡基于物理模拟的碰撞响应
重力处理需要手动实现重力效果自动应用场景重力
形状限制只能使用一个碰撞形状可以使用多个碰撞形状
适用场景角色控制、第一/第三人称游戏物理模拟、刚体运动
性能开销较低较高

选择建议:

  • 如果需要精确控制角色的移动和碰撞响应,使用角色控制器
  • 如果需要真实的物理模拟效果,使用动态碰撞器

使用方法

  1. 选中目标实体,并在检查器中点击添加组件按钮,添加 CharacterController 组件。
  1. 设置控制器的碰撞形状,使碰撞形状与角色的外形相尽量匹配。关于碰撞形状的详细说明请参考碰撞形状文档。

与其他碰撞器不同,角色控制器只能添加一个碰撞形状。通常建议使用胶囊体(CapsuleColliderShape)作为角色的碰撞形状。

  1. 根据需要设置碰撞器的属性调整物体的物理行为,各属性的含义和作用请参考下文。

属性说明

继承自 Collider 的属性

属性描述
shapes碰撞形状集合

特有属性

属性描述默认值
stepOffset角色可以自动跨越的最大台阶高度。
  • 必须大于等于 0
  • 实际可跨越高度 = stepOffset + 碰撞形状的接触偏移
0.5
slopeLimit角色可以行走的最大斜坡角度(度)。
  • 超过此角度的斜面将被视为不可行走的墙壁
  • 影响角色的爬坡能力
45°
nonWalkableMode定义如何处理不可行走的表面。
  • PreventClimbing:阻止角色攀爬不可行走的斜坡,但不会强制其他移动(默认)
  • PreventClimbingAndForceSliding:阻止角色攀爬不可行走的斜坡,并强制角色沿斜坡滑下
PreventClimbing
upDirection定义角色的向上方向。默认为 (0, 1, 0),即世界空间的 Y 轴向上。影响移动和碰撞检测的方向判定(0, 1, 0)

公开方法

继承自 Collider 的方法

方法名描述
addShape添加碰撞形状
removeShape移除指定碰撞形状
clearShapes清空所有碰撞形状

特有方法

方法名描述
move移动角色控制器。返回一个碰撞标志值,标识碰撞状态。
  • displacement:移动向量
  • minDist:最小移动距离
  • elapsedTime:经过的时间

碰撞标志说明

移动函数 move() 会返回一个碰撞标志值,用于表示角色控制器与环境的碰撞状态。这些标志可以通过按位与运算(&)来检测:

标志名称说明
None0没有发生任何碰撞
Sides1与侧面发生碰撞
Up2与上方发生碰撞(如天花板)
Down4与下方发生碰撞(如地面)

脚本使用

基础配置

// 创建角色控制器
const controller = entity.addComponent(CharacterController);
 
// 添加胶囊体形状
const capsule = new CapsuleColliderShape();
capsule.radius = 0.5;
capsule.height = 2;
controller.addShape(capsule);
 
// 配置控制器属性
controller.stepOffset = 0.5;      // 设置台阶高度
controller.slopeLimit = 45;       // 设置最大可行走斜坡角度
controller.upDirection = new Vector3(0, 1, 0); // 设置向上方向

使用移动函数

class CharacterMovement extends Script {
  private _velocity = new Vector3();
  
  onUpdate(deltaTime: number) {
    const controller = this.entity.getComponent(CharacterController);
 
    // 创建位移向量
    const displacement = new Vector3();
    Vector3.scale(this._velocity, deltaTime, displacement);
 
    // 执行移动并获取碰撞标志
    // minDist: 最小移动距离,通常设为0
    // deltaTime: 经过的时间,用于物理计算
    const collisionFlags = controller.move(displacement, 0, deltaTime);
    
    // 处理碰撞响应
    if (collisionFlags & ControllerCollisionFlag.Down) {
      // 角色接触地面
    }
  }
}

使用示例:

const flags = controller.move(displacement, 0, deltaTime);
 
// 检查是否接触地面
if (flags & ControllerCollisionFlag.Down) {
    // 角色在地面上
    this._isGrounded = true;
}
 
// 检查是否撞到天花板
if (flags & ControllerCollisionFlag.Up) {
    // 角色撞到头部
    this._velocity.y = 0;
}
 
// 检查是否撞到墙壁
if (flags & ControllerCollisionFlag.Sides) {
    // 角色撞到墙壁
    this._handleWallCollision();
}
 
// 可以同时检查多个标志
if ((flags & ControllerCollisionFlag.Down) && 
    (flags & ControllerCollisionFlag.Sides)) {
    // 角色同时接触地面和墙壁
}

斜坡/台阶行走

  1. 斜坡行走
// 通过设置 slopeLimit 控制可行走的斜坡角度
controller.slopeLimit = 60; // 允许更陡的斜坡
 
// 设置不可行走斜面的处理方式
controller.nonWalkableMode = ControllerNonWalkableMode.PreventClimbingAndForceSliding; // 在太陡的斜面上会滑下
  1. 台阶行走调整
// 调整 stepOffset 来控制可跨越的台阶高度
controller.stepOffset = 0.3; // 较低的台阶
controller.stepOffset = 0.5; // 较高的台阶

重力处理

角色控制器本身不包含重力处理,需要在脚本中手动实现重力效果。以下是一个完整的重力处理示例:

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;
 
    // 获取输入
    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);
    // 计算移动方向
    const moveDirection = new Vector3(horizontal, 0, vertical);
    moveDirection.normalize();
 
    // 应用移动速度
    this._velocity.x = moveDirection.x * this._moveSpeed;
    this._velocity.z = moveDirection.z * this._moveSpeed;
 
    // 应用重力
    if (!this._isGrounded) {
      this._velocity.y += this._gravity.y * deltaTime;
    }
 
    // 处理跳跃
    if (this._isGrounded && jump) {
      this._velocity.y = this._jumpForce;
      this._isGrounded = false;
    }
 
    // 执行移动
    const displacement = new Vector3();
    Vector3.scale(this._velocity, deltaTime, displacement);
    const collisionFlags = this._controller.move(displacement, 0, deltaTime);
 
    // 更新地面状态
    this._isGrounded = (collisionFlags & ControllerCollisionFlag.Down) !== 0;
    if (this._isGrounded) {
      this._velocity.y = 0;
    }
  }
}

限制和注意事项

  1. 形状限制

    • 只能使用一个碰撞形状
    • 建议使用胶囊体(CapsuleColliderShape)作为碰撞形状
  2. 性能考虑

    • 角色控制器比动态碰撞器性能更好
    • 但每个角色控制器都会占用一定的物理计算资源
    • 建议在场景中控制角色控制器的数量

这篇文档对您有帮助吗?