You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity使用CharacterController时如何正确检测玩家是否移动

问题根因

你的位置对比逻辑有三个硬伤,必然拿不到正确结果:

  • 赋值顺序完全写反:每帧Update开头就把当前位置存到prev_pos,帧尾又把prev_pos赋值给curr_pos,两个变量存的永远是同一帧的同一个坐标,对比结果永远是相等。
  • 变量作用域错误:你在if/else块内部声明的bool moving是局部变量,出了判断块就会被销毁,后面计算冲刺速度的代码根本读不到这个值,等于移动判定的结果完全没生效。
  • 采样时机不对:CharacterController的坐标只有在调用Move()方法后才会更新,如果你把位置采样写在Move()调用之前,拿到的永远是移动前的旧坐标,和历史值对比自然不会有差异。

另外你试的按键检测方案本身就有逻辑缺陷:玩家按方向键可能被障碍物卡住不动,也可能站在移动平台上没按键却被带着位移,纯输入检测不可能准确。

正确实现方案

两种方案都不需要依赖刚体速度,适配CharacterController的所有移动场景:

方案1:修正位置对比逻辑(完全匹配你不使用物理参数的要求)

核心调整是把上一帧位置的记录放在帧尾,当前位置采样放在帧头,加浮点数对比容错避免精度误差导致的误判,同时把移动状态变量提到方法作用域。
可直接复用的代码:

public Vector3 lastFramePos;
public bool isMoving;
public float tolerance = 0.001f; // 浮点数判定容错阈值
private CharacterController controller;

void Awake()
{
    controller = GetComponent<CharacterController>();
    lastFramePos = transform.position; // 初始化首帧坐标
}

void Update()
{
    // 先做位置对比,只计算水平方向位移,忽略下落、弹跳的垂直抖动
    Vector3 currentPos = transform.position;
    Vector3 horizontalDelta = new Vector3(
        currentPos.x - lastFramePos.x, 
        0, 
        currentPos.z - lastFramePos.z
    );
    isMoving = horizontalDelta.sqrMagnitude > tolerance * tolerance;

    // 原有移动逻辑
    if (controller.isGrounded) 
    {
        moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        moveDirection = transform.TransformDirection(moveDirection);
        // 直接用正确的isMoving状态计算速度
        if(sprinting && !crouching && isMoving){
            speed = sprint_speed;
        }
        moveDirection *= speed;
        if(Input.GetButton("Jump")){
            moveDirection.y = jumpSpeed;
        }
    }
    // 必须调用Move才会触发CharacterController坐标更新
    controller.Move(moveDirection * Time.deltaTime);

    // 帧尾记录当前坐标,作为下一帧的对比基准
    lastFramePos = transform.position;
}

该方案可以正确识别站在移动平台上的被动移动、卡墙静止等特殊场景,不需要额外补逻辑。

方案2:读取CharacterController自带速度属性(性能更好,更稳定)

你之前测速度类方案无效,大概率是误读了刚体的velocity字段——CharacterController本身不依赖刚体,有独立的velocity属性,直接读取这个值做判定即可,不需要手动记录坐标:

// 放在Update中调用Move之后即可
Vector3 horizontalVel = new Vector3(controller.velocity.x, 0, controller.velocity.z);
isMoving = horizontalVel.sqrMagnitude > tolerance * tolerance;

这个值是CharacterController每帧移动完成后自动计算的,卡墙、被动移动、惯性滑动的状态都能准确识别,比手动对比坐标更省心。

之前尝试的方案失效原因
  • 不管在Update还是FixedUpdate里写对比,只要赋值顺序颠倒,两个变量存的永远是同一个值,不可能对比出差异。
  • 逐分量构造新Vector3存坐标和直接赋值transform.position效果完全一致,都是值拷贝,解决不了顺序错误的核心问题。
  • 纯按键检测无法覆盖卡墙、被动位移、惯性移动等场景,判定结果和实际移动状态不匹配。

内容的提问来源于stack exchange,提问作者Zabie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:42:14