Unity3D CharacterController动量惯性实现异常问题排查
Unity CharacterController 动量惯性效果异常修复
问题根因
- 向量方向判断逻辑失效:
Vector3.Equals()和==运算符对浮点数做的是精确位匹配,而Input轴返回值、CharacterController计算出的velocity都存在浮点精度误差,哪怕Debug.Log打印出的数值看起来完全一致,小数后几位的微小差值也会导致判断结果不符合预期,分支逻辑乱跳。 - 按键松开瞬停:原代码中只要判定新输入方向和动量方向一致,就直接把momentum设为
Vector3.zero,没有做平滑减速逻辑,速度直接清零就会出现撞墙式骤停。 - 按键按下角色飞出去:原代码每帧直接取
characterController.velocity赋值给momentum,再把momentum叠加到Move的入参里,相当于每帧都把上一帧的移动速度重复累加,形成正反馈循环,速度会指数级暴涨;再加上单轴直接清零动量的错误逻辑,动量分量计算完全混乱,进一步放大了异常位移。

修复后实现代码
// 类内可配置参数,可根据手感调整 public float moveSpeed = 6f; public float inertiaDecayRate = 3f; // 惯性衰减速率,值越大滑行距离越短 public float turnSmoothRate = 8f; // 转向时反向动量抵消速度 private Vector3 momentum; private Vector3 verticalVelocity; // 处理重力、跳跃的竖直速度变量 void Update() { // 获取输入 float horizontalInput = Input.GetAxis("Horizontal"); float verticalInput = Input.GetAxis("Vertical"); Vector3 inputDir = transform.right * horizontalInput + transform.forward * verticalInput; inputDir.y = 0; if (inputDir.sqrMagnitude < 0.01f) { // 无输入时,动量逐帧平滑衰减,实现滑行效果 momentum = Vector3.MoveTowards(momentum, Vector3.zero, inertiaDecayRate * Time.deltaTime); } else { // 有输入时用点乘判断方向相似度,阈值0.99f留浮点容错 float dotResult = Vector3.Dot(inputDir.normalized, momentum.normalized); if (dotResult < 0.99f) { // 方向不一致时,平滑抵消垂直于输入方向的反向动量,不要直接清零单轴 momentum = Vector3.Lerp(momentum, Vector3.ProjectOnPlane(momentum, inputDir.normalized), turnSmoothRate * Time.deltaTime); } // 叠加输入加速度,限制最大速度避免超速 momentum += inputDir.normalized * moveSpeed * Time.deltaTime; momentum = Vector3.ClampMagnitude(momentum, moveSpeed); } // 执行移动,禁止直接叠加characterController.velocity避免速度重复计算 characterController.Move((momentum + verticalVelocity) * Time.deltaTime); }
关键注意事项
- 所有涉及浮点数、Unity Vector3/Quaternion等数值类型的相等判断,禁止直接使用
==或Equals(),必须预留精度容错:判断方向用向量点乘、判断向量长度用sqrMagnitude和阈值对比、判断数值相等用Mathf.Abs(a-b) < 容错阈值。 CharacterController.velocity是引擎上一帧移动的计算结果,绝对不能直接作为下一帧Move()的入参累加,否则必然出现速度爆炸的问题。- 自然的惯性效果依赖逐帧平滑插值,直接清零速度永远做不出滑行手感,用
MoveTowards、Lerp这类插值函数逐帧调整动量值才能得到流畅的效果。
内容的提问来源于stack exchange,提问作者Not_GG_Gamer
相关产品推荐
相关产品推荐

