引言
在Unity3D中实现3D角色移动与跳跃有多种实现方式,以下是三种基础的主要实现方式的对比分析和推荐方案:
一、组件对比分析
1. Transform组件
- 特点:直接修改坐标位置,无物理模拟
- 优点:完全可控的位移,适合简单机制
- 缺点:需要手动处理碰撞,无法与物理系统互动
- 适用场景:2D游戏或不需要物理交互的简单移动
2. Rigidbody组件
- 特点:基于物理引擎的刚体模拟
优点:
- 自动处理碰撞和重力
- 支持力和速度控制(
AddForce) - 可实现真实的物理效果(如斜坡滑落)
缺点:
- 适用场景:需要物理交互的3D游戏(如被爆炸冲击波影响)
3. Character Controller
- 特点:专为角色设计的非物理移动组件
优点:
- 内置斜坡/台阶处理
- 精确控制移动(
Move方法) - 自带碰撞检测(
isGrounded)
- 缺点:需要手动处理重力与跳跃逻辑
- 适用场景:平台跳跃类游戏或需要精细控制角色的场景
二、推荐方案
通常来讲,对于无需玩家操控的物体使用Rigidbody,对于要配合动画进行玩家操作的角色,推荐组合使用 Character Controller
1. 移动控制选择
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // 使用Character Controller示例代码 public class PlayerController : MonoBehaviour { private CharacterController controller; private float verticalVelocity; private bool isJumping; void Start() { controller = GetComponent<CharacterController>(); } void Update() { // 水平移动 Vector3 moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); moveDirection = transform.TransformDirection(moveDirection); moveDirection *= moveSpeed; // 跳跃逻辑 if (controller.isGrounded) { verticalVelocity = -1f; // 微小向下的力保证接地检测 if (Input.GetButtonDown("Jump")) { verticalVelocity = jumpForce; isJumping = true; animator.SetTrigger("Jump"); // 触发跳跃动画 } } else { verticalVelocity -= gravity * Time.deltaTime; isJumping = false; } moveDirection.y = verticalVelocity; controller.Move(moveDirection * Time.deltaTime); } } |
2. 动画系统实现
Animator Controller设置:
- 创建状态机包含:Idle/Run/Jump/Fall等状态
- 使用混合树(Blend Tree)处理不同移动速度的动画过渡
- 添加参数:
Speed(Float)、IsGrounded(Bool)、Jump(Trigger)
- 动画参数更新:
1 2 3 | // 在Update中更新动画参数 animator.SetFloat("Speed", controller.velocity.magnitude); animator.SetBool("IsGrounded", controller.isGrounded); |
3. 扩展性设计
- 模块化设计:将移动、跳跃、动画控制拆分为独立脚本
- 动作扩展:
1 2 3 4 5 | // 后续可添加新动作(如翻滚) public void PlayRollAnimation() { animator.SetTrigger("Roll"); // 添加位移逻辑... } |
- 使用动画层:通过Animator Layer实现不同身体部位的独立动画(如上半身持枪,下半身奔跑)
三、Rigidbody与Character Controller最好只择其一
一、为什么建议「二选一」?
底层冲突机制:
CharacterController 通过Move()方法直接修改坐标Rigidbody 通过物理引擎计算位置- 同时使用会导致两套系统争夺控制权(例如角色卡顿、穿墙)
- 典型冲突表现:
1 2 3 4 5 | // 错误用法示例:同时操作两种组件 void Update() { controller.Move(moveDir); // 直接修改坐标 rigidbody.AddForce(force); // 物理引擎修改坐标 } |
官方文档建议:
"CharacterController does not require a Rigidbody. In fact, having both may cause undefined behavior."
—— Unity官方文档
二、是否完全不能组合使用?
在特定需求下可以有限组合,但需要遵循以下原则:
方案1:Rigidbody作为物理接收器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 组件配置: - CharacterController (主移动) - Rigidbody (IsKinematic = true) // 关闭物理位置计算 // 脚本示例: void OnCollisionEnter(Collision other) { if (other.gameObject.CompareTag("Explosion")) { Vector3 force = CalculateExplosionForce(); rigidbody.isKinematic = false; // 临时启用物理 rigidbody.AddForce(force, ForceMode.Impulse); StartCoroutine(ResetKinematic()); } } IEnumerator ResetKinematic() { yield return new WaitForSeconds(1f); rigidbody.isKinematic = true; // 恢复控制器主导 } |
适用场景:需要临时受外力影响的剧情事件
方案2:物理驱动为主
1 2 3 4 5 6 7 8 9 10 | // 组件配置: - Rigidbody (约束Rotation) - CustomMovementScript // 通过AddForce实现移动 void FixedUpdate() { if (isGrounded) { rigidbody.AddForce(moveDir * acceleration); } rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxSpeed); } |
- 优势:可实现真实的物理交互(如被车撞飞)
- 代价:需要处理惯性带来的"滑冰感"
三、CharacterController如何实现重力?
完全不需要Rigidbody,通过手动计算实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class GravityHandler : MonoBehaviour { private float verticalSpeed; private const float GRAVITY = 9.81f; void Update() { if (controller.isGrounded) { verticalSpeed = -0.5f; // 微小向下力维持接地状态 } else { verticalSpeed -= GRAVITY * Time.deltaTime; } Vector3 move = new Vector3(0, verticalSpeed, 0); controller.Move(move * Time.deltaTime); } } |
关键点:
- 接地检测依赖
controller.isGrounded - 重力加速度需手动模拟(建议值:2倍真实重力)
四、方案选型决策树
1 2 3 4 5 6 7 | graph TD A[是否需要与物理对象互动?] --> |是| B[是否要求精确控制?] A --> |否| C[使用纯CharacterController] B --> |是| D[CharacterController + 事件触发物理] B --> |否| E[纯Rigidbody方案] D --> F[如爆炸时临时启用Rigidbody] E --> G[处理惯性/摩擦力参数] |
五、最佳实践建议
基础移动架构:
- CharacterController
- Animator
CustomMovementScript (无Rigidbody)
// 移动逻辑:
void Update() {
HandleMovement(); // 使用Move()
HandleGravity(); // 手动重力
HandleJump(); // 自定义跳跃
}
物理交互实现技巧:
- 方案A:通过
CharacterController.Move()模拟受力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 受击退效果示例 public void ApplyKnockback(Vector3 force) { StartCoroutine(KnockbackRoutine(force)); } IEnumerator KnockbackRoutine(Vector3 force) { float duration = 0.5f; while (duration > 0) { controller.Move(force * Time.deltaTime); force = Vector3.Lerp(force, Vector3.zero, 0.1f); duration -= Time.deltaTime; yield return null; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private bool isPhysicsActive; void Update() { if (!isPhysicsActive) { // 正常CharacterController移动 } } void ActivatePhysics(Vector3 force) { isPhysicsActive = true; controller.enabled = false; rigidbody.isKinematic = false; rigidbody.AddForce(force); } |
- 动画融合技巧:
1 2 3 4 5 | // 在Animator中混合物理动画 animator.SetFloat("FallSpeed", verticalSpeed); // 使用动画遮罩层实现上半身不受物理影响 animator.SetLayerWeight(1, 1.0f); // 第1层控制上半身 |
六、各方案性能对比
|指标|纯CharacterController|纯Rigidbody|混合方案|
|-|-|-|-|
|CPU占用|低(无物理计算)|中高|中|
|内存占用|低|中|中|
|物理交互复杂度|需手动实现|自动|按需切换|
|移动精度|高|中|高|
|适合场景|平台跳跃/ACT|沙盒/物理游戏|动作RPG|
总结
- 你的原始脚本不需要Rigidbody:之前的重力实现完全可以通过手动计算完成,添加Rigidbody是画蛇添足
物理需求的正确实现路径:
- 90%的基础功能:纯CharacterController
- 10%的特殊物理需求:通过协程/临时启用Rigidbody实现
- 扩展建议:使用State Pattern管理不同移动状态(行走/跳跃/受击),每个状态独立处理物理需求
四、开发细节
- 分离输入与物理响应,详细可以查看后面新出的文档
物理参数调节:
- 重力建议值:根据需求调整,太小可能会很飘
- 跳跃力建议值:4-8(根据角色尺寸调整)
- 移动速度建议值:2-6 m/s
动画衔接优化:
- 在动画状态过渡中设置合理的Exit Time和Transition Duration
- 使用动画事件触发脚步声/跳跃音效
- 碰撞检测增强:
1 2 3 4 | // 添加底部射线检测提高接地判断精度 if (Physics.Raycast(transform.position + Vector3.up * 0.1f, Vector3.down, 0.2f)) { // 接地处理... } |
- 输入优化:
1 2 3 | // 使用Input System Package实现多设备输入支持 PlayerInput playerInput = GetComponent<PlayerInput>(); Vector2 input = playerInput.actions["Move"].ReadValue<Vector2>(); |