Files
engine/docs/zh/physics/collider/characterController.mdx
2025-06-03 14:37:11 +08:00

249 lines
9.2 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
order: 3
title: 角色控制器
type: 物理
label: Physics
---
角色控制器([CharacterController](/apis/core/#CharacterController))是一种特殊的碰撞器组件,专门用于处理角色的物理运动。它提供了专门的移动算法和碰撞检测,能够处理台阶、斜坡等复杂地形,特别适合第一人称或第三人称游戏中的角色控制。
## 与动态碰撞器的区别
角色控制器和[动态碰撞器](/docs/physics/collider/dynamicCollider)虽然都可以实现物体的移动,但它们有着显著的区别:
| 特性 | 角色控制器 | 动态碰撞器 |
|------|------------|------------|
| 移动方式 | 通过 `move()` 方法直接控制位移 | 通过施加力或冲量来移动 |
| 碰撞响应 | 手动处理碰撞响应,自动处理台阶和斜坡 | 基于物理模拟的碰撞响应 |
| 重力处理 | 需要手动实现重力效果 | 自动应用场景重力 |
| 形状限制 | 只能使用一个碰撞形状 | 可以使用多个碰撞形状 |
| 适用场景 | 角色控制、第一/第三人称游戏 | 物理模拟、刚体运动 |
| 性能开销 | 较低 | 较高 |
<Callout type="positive">
选择建议:
- 如果需要精确控制角色的移动和碰撞响应,使用角色控制器
- 如果需要真实的物理模拟效果,使用动态碰撞器
</Callout>
## 使用方法
1. 选中目标实体,并在检查器中点击添加组件按钮,添加 CharacterController 组件。
<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*nEMXRKiqpy8AAAAAAAAAAAAAesJ_AQ/original" />
2. 设置控制器的碰撞形状,使碰撞形状与角色的外形相尽量匹配。关于碰撞形状的详细说明请参考[碰撞形状](/docs/physics/collider/colliderShape)文档。
<Callout type="positive">
与其他碰撞器不同角色控制器只能添加一个碰撞形状。通常建议使用胶囊体CapsuleColliderShape作为角色的碰撞形状。
</Callout>
<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*4QvUTI4D89EAAAAAAAAAAAAAesJ_AQ/original" />
<Image src="https://mdn.alipayobjects.com/huamei_3zduhr/afts/img/A*aRGqSIMqDmsAAAAAAAAAAAAAesJ_AQ/original" />
3. 根据需要设置碰撞器的属性调整物体的物理行为,各属性的含义和作用请参考下文。
## 属性说明
### 继承自 Collider 的属性
| 属性 | 描述 |
| ----------------------------------------- | ------------ |
| [**shapes**](/apis/core/#Collider-shapes) | 碰撞形状集合 |
### 特有属性
| 属性 | 描述 | 默认值 |
| ---- | ---- | ------ |
| [**stepOffset**](/apis/core/#CharacterController-stepOffset) | 角色可以自动跨越的最大台阶高度。<ul><li>必须大于等于 0</li><li>实际可跨越高度 = stepOffset + 碰撞形状的接触偏移</li></ul> | 0.5 |
| [**slopeLimit**](/apis/core/#CharacterController-slopeLimit) | 角色可以行走的最大斜坡角度(度)。<ul><li>超过此角度的斜面将被视为不可行走的墙壁</li><li>影响角色的爬坡能力</li></ul> | 45° |
| [**nonWalkableMode**](/apis/core/#CharacterController-nonWalkableMode) | 定义如何处理不可行走的表面。<ul><li>PreventClimbing阻止角色攀爬不可行走的斜坡但不会强制其他移动默认</li><li>PreventClimbingAndForceSliding阻止角色攀爬不可行走的斜坡并强制角色沿斜坡滑下</li></ul> | PreventClimbing |
| [**upDirection**](/apis/core/#CharacterController-upDirection) | 定义角色的向上方向。默认为 (0, 1, 0),即世界空间的 Y 轴向上。影响移动和碰撞检测的方向判定 | (0, 1, 0) |
## 公开方法
### 继承自 Collider 的方法
| 方法名 | 描述 |
| --------------------------------------------------- | ---------------- |
| [**addShape**](/apis/core/#Collider-addShape) | 添加碰撞形状 |
| [**removeShape**](/apis/core/#Collider-removeShape) | 移除指定碰撞形状 |
| [**clearShapes**](/apis/core/#Collider-clearShapes) | 清空所有碰撞形状 |
### 特有方法
| 方法名 | 描述 |
| ---- | ---- |
| [**move**](/apis/core/#CharacterController-move) | 移动角色控制器。返回一个碰撞标志值,标识碰撞状态。<ul><li>displacement移动向量</li><li>minDist最小移动距离</li><li>elapsedTime经过的时间</li></ul> |
### 碰撞标志说明
移动函数 `move()` 会返回一个碰撞标志值,用于表示角色控制器与环境的碰撞状态。这些标志可以通过按位与运算(&)来检测:
| 标志名称 | 值 | 说明 |
|---------|----|----|
| None | 0 | 没有发生任何碰撞 |
| Sides | 1 | 与侧面发生碰撞 |
| Up | 2 | 与上方发生碰撞(如天花板) |
| Down | 4 | 与下方发生碰撞(如地面) |
## 脚本使用
### 基础配置
```typescript
// 创建角色控制器
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); // 设置向上方向
```
### 使用移动函数
```typescript
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) {
// 角色接触地面
}
}
}
```
使用示例:
```typescript
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. **斜坡行走**
```typescript
// 通过设置 slopeLimit 控制可行走的斜坡角度
controller.slopeLimit = 60; // 允许更陡的斜坡
// 设置不可行走斜面的处理方式
controller.nonWalkableMode = ControllerNonWalkableMode.PreventClimbingAndForceSliding; // 在太陡的斜面上会滑下
```
2. **台阶行走调整**
```typescript
// 调整 stepOffset 来控制可跨越的台阶高度
controller.stepOffset = 0.3; // 较低的台阶
controller.stepOffset = 0.5; // 较高的台阶
```
### 重力处理
角色控制器本身不包含重力处理,需要在脚本中手动实现重力效果。以下是一个完整的重力处理示例:
```typescript
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. **性能考虑**
- 角色控制器比动态碰撞器性能更好
- 但每个角色控制器都会占用一定的物理计算资源
- 建议在场景中控制角色控制器的数量