JavaScript中基于Delta Time的比例平滑算法存在异常,请求技术排查
嘿,我帮你排查了代码里的问题,核心出在时间计算逻辑错误和平滑公式的本质理解偏差上,咱们一步步来解决:
先理清核心逻辑
你绿箱子用的固定帧率平滑公式是对的:v = (v*9 + target*1)/10,本质是每帧保留90%的当前值,叠加10%的目标值。要适配Delta Time,我们需要把“每帧衰减”转换成“基于时间的衰减”——也就是经过deltaTime毫秒后,当前值剩余的比例是0.9^(deltaTime / frame)(frame是60FPS单帧时长,约16.67ms)。正确的公式应该是:
v = target + (v - target) * decayFactor
这个公式的意思是:当前值到目标值的差值,按时间比例衰减,再加上目标值,这样不管帧率波动多大,平滑效果都会和固定60FPS时一致。
你的代码里的两个关键错误
1. 时间计算逻辑混乱
你原来的time.delta计算完全错了,导致delta值根本不是真实的帧间隔时间:
// 错误的代码 time.delta=time.now-time.first-time.since; time.since=time.now-time.first;
正确的做法应该是记录上一帧的时间戳,当前帧时间减去上一帧时间就是真实的deltaTime:
// 初始化时间变量 var time = { last: new Date().getTime(), // 上一帧的毫秒时间戳 delta: 0 }; // 在tickBoxA里更新时间 function tickBoxA() { const now = new Date().getTime(); time.delta = now - time.last; time.last = now; // ... 后续逻辑 }
2. 平滑公式逻辑错误
你之前尝试的所有公式都没抓住“差值衰减”的核心,比如boxA.xSpeed=boxA.xAccel-boxA.xSpeed+(boxA.xSpeed*0.9**(time.delta/frame)),把目标值和当前值的关系搞反了,导致速度要么发散要么收敛异常。
另外还有个小坑:Math.sin(45)用的是弧度,不是角度!45度转弧度应该是Math.sin(45 * Math.PI / 180),不然得到的是完全错误的正弦值(sin(45弧度)≈0.8509,而不是我们要的√2/2≈0.7071)。
修改后的完整tickBoxA函数
把下面的代码替换你原来的tickBoxA,问题就解决了:
function tickBoxA() { // 修正时间计算 const now = new Date().getTime(); time.delta = now - time.last; time.last = now; // 按键检测逻辑不变 if(keysDown.includes(keyBind.upA.key)){ keyBind.upA.on = true; }else{ keyBind.upA.on = false; } if(keysDown.includes(keyBind.leftA.key)){ keyBind.leftA.on = true; }else{ keyBind.leftA.on = false; } if(keysDown.includes(keyBind.downA.key)){ keyBind.downA.on = true; }else{ keyBind.downA.on = false; } if(keysDown.includes(keyBind.rightA.key)){ keyBind.rightA.on = true; }else{ keyBind.rightA.on = false; } // 计算目标速度(原来的xAccel/yAccel),修正sin的弧度问题 const targetXSpeed = (keyBind.rightA.on - keyBind.leftA.on) * 3 * ((keyBind.upA.on - keyBind.downA.on != 0) ? Math.sin(45 * Math.PI / 180) : 1); const targetYSpeed = (keyBind.downA.on - keyBind.upA.on) * 3 * ((keyBind.rightA.on - keyBind.leftA.on != 0) ? Math.sin(45 * Math.PI / 180) : 1); // 计算衰减因子,适配Delta Time const decayFactor = Math.pow(0.9, time.delta / frame); // 正确的平滑速度计算 boxA.xSpeed = targetXSpeed + (boxA.xSpeed - targetXSpeed) * decayFactor; boxA.ySpeed = targetYSpeed + (boxA.ySpeed - targetYSpeed) * decayFactor; // 速度和位置更新逻辑不变 boxA.speed = Math.sqrt((boxA.xSpeed**2)+(boxA.ySpeed**2)); boxA.xPos += boxA.xSpeed * (time.delta/frame); boxA.yPos += boxA.ySpeed * (time.delta/frame); render(); }
测试验证
现在你修改lag的值(比如0、frame、100),红箱子的平滑效果会和绿箱子在60FPS下的表现完全一致,不会再出现异常的速度波动。
内容的提问来源于stack exchange,提问作者FireyDeath4

