在Unity3D中实现3D角色移动与跳跃有多种实现方式,以下是三种基础的主要实现方式的对比分析和推荐方案:


一、组件对比分析

1. Transform组件

  • 特点:直接修改坐标位置,无物理模拟
  • 优点:完全可控的位移,适合简单机制
  • 缺点:需要手动处理碰撞,无法与物理系统互动
  • 适用场景:2D游戏或不需要物理交互的简单移动

2. Rigidbody组件

  • 特点:基于物理引擎的刚体模拟
  • 优点

    • 自动处理碰撞和重力
    • 支持力和速度控制(AddForce
    • 可实现真实的物理效果(如斜坡滑落)
  • 缺点

    • 需要调节质量/阻力参数
    • 可能出现"滑冰感"
  • 适用场景:需要物理交互的3D游戏(如被爆炸冲击波影响)

3. Character Controller

  • 特点:专为角色设计的非物理移动组件
  • 优点

    • 内置斜坡/台阶处理
    • 精确控制移动(Move方法)
    • 自带碰撞检测(isGrounded
  • 缺点:需要手动处理重力与跳跃逻辑
  • 适用场景:平台跳跃类游戏或需要精细控制角色的场景

二、推荐方案

通常来讲,对于无需玩家操控的物体使用Rigidbody,对于要配合动画进行玩家操作的角色,推荐组合使用 Character Controller

1. 移动控制选择

// 使用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. 动画系统实现

  1. Animator Controller设置

    • 创建状态机包含:Idle/Run/Jump/Fall等状态
    • 使用混合树(Blend Tree)处理不同移动速度的动画过渡
    • 添加参数:Speed(Float)、IsGrounded(Bool)、Jump(Trigger)
  2. 动画参数更新
// 在Update中更新动画参数
animator.SetFloat("Speed", controller.velocity.magnitude);
animator.SetBool("IsGrounded", controller.isGrounded);

3. 扩展性设计

  • 模块化设计:将移动、跳跃、动画控制拆分为独立脚本
  • 动作扩展

    // 后续可添加新动作(如翻滚)
    public void PlayRollAnimation() {
      animator.SetTrigger("Roll");
      // 添加位移逻辑...
    }
  • 使用动画层:通过Animator Layer实现不同身体部位的独立动画(如上半身持枪,下半身奔跑)

三、Rigidbody与Character Controller最好只择其一

一、为什么建议「二选一」?

  1. 底层冲突机制

    • CharacterController 通过Move()方法直接修改坐标
    • Rigidbody 通过物理引擎计算位置
    • 同时使用会导致两套系统争夺控制权(例如角色卡顿、穿墙)
  2. 典型冲突表现

    // 错误用法示例:同时操作两种组件
    void Update() {
     controller.Move(moveDir); // 直接修改坐标
     rigidbody.AddForce(force); // 物理引擎修改坐标
    }
  • 可能出现:角色移动抽搐/碰撞失效/重力异常
  1. 官方文档建议

    "CharacterController does not require a Rigidbody. In fact, having both may cause undefined behavior."
    —— Unity官方文档

二、是否完全不能组合使用?

在特定需求下可以有限组合,但需要遵循以下原则:

方案1:Rigidbody作为物理接收器

// 组件配置:
- 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:物理驱动为主

// 组件配置:
- Rigidbody (约束Rotation)
- CustomMovementScript // 通过AddForce实现移动

void FixedUpdate() {
    if (isGrounded) {
        rigidbody.AddForce(moveDir * acceleration);
    }
    rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxSpeed);
}
  • 优势:可实现真实的物理交互(如被车撞飞)
  • 代价:需要处理惯性带来的"滑冰感"

三、CharacterController如何实现重力?

完全不需要Rigidbody,通过手动计算实现:

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倍真实重力)

四、方案选型决策树

graph TD
    A[是否需要与物理对象互动?] --> |是| B[是否要求精确控制?]
    A --> |否| C[使用纯CharacterController]
    B --> |是| D[CharacterController + 事件触发物理]
    B --> |否| E[纯Rigidbody方案]
    D --> F[如爆炸时临时启用Rigidbody]
    E --> G[处理惯性/摩擦力参数]

五、最佳实践建议

  1. 基础移动架构

  2. CharacterController
  3. Animator
  4. CustomMovementScript (无Rigidbody)

    // 移动逻辑:
    void Update() {
    HandleMovement(); // 使用Move()
    HandleGravity(); // 手动重力
    HandleJump(); // 自定义跳跃
    }

  5. 物理交互实现技巧

    • 方案A:通过CharacterController.Move()模拟受力
    // 受击退效果示例
    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;
    }
    }
  • 方案B:临时切换物理控制器

    private bool isPhysicsActive;
    
    void Update() {
    if (!isPhysicsActive) {
        // 正常CharacterController移动
    }
    }
    
    void ActivatePhysics(Vector3 force) {
    isPhysicsActive = true;
    controller.enabled = false;
    rigidbody.isKinematic = false;
    rigidbody.AddForce(force);
    }
  1. 动画融合技巧

    // 在Animator中混合物理动画
    animator.SetFloat("FallSpeed", verticalSpeed);
    
    // 使用动画遮罩层实现上半身不受物理影响
    animator.SetLayerWeight(1, 1.0f); // 第1层控制上半身

六、各方案性能对比

指标纯CharacterController纯Rigidbody混合方案
CPU占用低(无物理计算)中高
内存占用
物理交互复杂度需手动实现自动按需切换
移动精度
适合场景平台跳跃/ACT沙盒/物理游戏动作RPG

总结

  1. 你的原始脚本不需要Rigidbody:之前的重力实现完全可以通过手动计算完成,添加Rigidbody是画蛇添足
  2. 物理需求的正确实现路径

    • 90%的基础功能:纯CharacterController
    • 10%的特殊物理需求:通过协程/临时启用Rigidbody实现
  3. 扩展建议:使用State Pattern管理不同移动状态(行走/跳跃/受击),每个状态独立处理物理需求

四、开发细节

  1. 分离输入与物理响应,详细可以查看后面新出的文档
  2. 物理参数调节

    • 重力建议值:根据需求调整,太小可能会很飘
    • 跳跃力建议值:4-8(根据角色尺寸调整)
    • 移动速度建议值:2-6 m/s
  3. 动画衔接优化

    • 在动画状态过渡中设置合理的Exit Time和Transition Duration
    • 使用动画事件触发脚步声/跳跃音效
  4. 碰撞检测增强

    // 添加底部射线检测提高接地判断精度
    if (Physics.Raycast(transform.position + Vector3.up * 0.1f, Vector3.down, 0.2f)) {
     // 接地处理...
    }
  5. 输入优化

    // 使用Input System Package实现多设备输入支持
    PlayerInput playerInput = GetComponent<PlayerInput>();
    Vector2 input = playerInput.actions["Move"].ReadValue<Vector2>();