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
相关产品推荐
相关产品推荐

