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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:18:42