Javascript 将基于帧的运动转换为基于时间的运动

Javascript 将基于帧的运动转换为基于时间的运动,javascript,algorithm,math,timedelta,Javascript,Algorithm,Math,Timedelta,这个问题与Javascript无关,但它是最容易展示我的问题的 我有一个屏幕上的物体,按一下键,它就会朝着一个特定的方向移动。因为 一个持续的运动看起来有点无聊,我加了一些放松-所以在它稳定在新的位置之前它会反弹一点 使用以下几行计算缓和度: a.x = 1.2; v.x = v.x * 0.8 - p.x * 0.05; p.x += v.x + a.x * 0.5; v.x += a.x; 其中,a.x是速度,v.x是速度,p.x是结果位置,然后乘以屏幕上的对象 这在固定帧

这个问题与Javascript无关,但它是最容易展示我的问题的

我有一个屏幕上的物体,按一下键,它就会朝着一个特定的方向移动。因为 一个持续的运动看起来有点无聊,我加了一些放松-所以在它稳定在新的位置之前它会反弹一点

使用以下几行计算缓和度:

  a.x = 1.2;
  v.x = v.x * 0.8 - p.x * 0.05;
  p.x += v.x + a.x * 0.5;
  v.x += a.x;
其中,
a.x
是速度,
v.x
是速度,
p.x
是结果位置,然后乘以屏幕上的对象

这在固定帧速率下非常有效

为了补偿可变帧速率,我添加了一个deltatime(基于目标固定帧速率),它应该改变速度

例如,如果目标帧速率为40,而实际帧速率仅为20,则上述
a.x
的值为2.4,或者,如果帧速率为80,则为0.6

不幸的是,这样做只适用于线性运动,所以我只是得到奇怪的运动

为了让它形象化,我在下面写了一个简短的片段。红色的正方形是它应该是什么样子,使用
update()
函数不计算deltatime,绿色的正方形是使用
updateWithTimeStep()函数计算deltatime得到的

我该怎么做才能使速度发挥作用,并最终获得恒定的帧速率独立运动

var canvas=document.getElementById(“canvas”);
var context=canvas.getContext(“2d”);
类向量{
构造函数(x,y,z){
这个.x=x;
这个。y=y;
这个。z=z;
}
}
var speedX=1.2;
var p=新向量(0,0);
var v=新向量(0,0);
var a=新向量(0,0);
var p2=新向量(0,0);
var v2=新向量(0,0);
var a2=新向量(0,0);
var centerX=canvas.width/2;
var centerY=canvas.height/2;
var edgeLength=20;
var帧时;
var targetFrameRate=40;
var requiredFrameTime=1000/目标帧速率;
var速度因子=0;
var prevFrameTime=新日期().getTime();
函数绘图(){
context.save();
context.fillStyle=“#000000”;
context.fillRect(0,0,canvas.width,canvas.height);
context.fillStyle=“#ff0000”;
上下文填充(centerX-边缘长度/2-p.x*8,centerY-边缘长度/2,边缘长度,边缘长度);
restore();
context.fillStyle=“#00ff00”;
context.fillRect(centerX-edgeLength/2-p2.x*8,centerY-edgeLength/2+40,edgeLength,edgeLength);
restore();
}
函数更新(){
a、 x=速度x;
v、 x=v.x*0.8-p.x*0.05;
p、 x+=v.x+a.x*0.5;
v、 x+=a.x;
}
函数updateWithTimeStep(){
frameTime=新日期().getTime();
deltaTime=(帧时间-帧时间之前);
速度系数=延迟时间/所需帧时间;
a2.x=速度x*速度系数;
v2.x=v2.x*0.8-p2.x*0.05;
p2.x+=v2.x+a2.x*0.5;
v2.x+=a2.x;
}
函数循环(){
更新();
updateWithTimeStep()
draw();
prevFrameTime=帧时间;
}
设置间隔(循环,16);
document.getElementById(“resetButton”).addEventListener(“单击”,函数)(){
p、 x=0;
p、 y=0;
a、 x=0;
a、 y=0;
v、 x=0;
v、 y=0;
p2.x=0;
p2.y=0;
a2.x=0;
a2.y=0;
v2.x=0;
v2.y=0;
})

重置

如果希望反弹物理对时间增量不敏感,则需要将整个更新扩展到增量,而不仅仅是部分更新

由于您的更新是在多个阶段中完成的,这有点尴尬,但每个阶段都需要缩放到时间增量,而不仅仅是位置更新。如果将所有阶段都视为加法,则更容易看出这一点:

v.x += -0.2 * v.x - 0.05 * p.x;
p.x += v.x + a.x * 0.5;
v.x += a.x;

===>

v.x += speedFactor * (- 0.2 * v.x - 0.05 * p.x);
p.x += speedFactor * (v.x + a.x * 0.5);
v.x += speedFactor * a.x;
请注意,应进行此缩放,而不是通过
speedFactor
缩放
a.x



此外,如果你想让这个系统在帧速率非常慢的情况下保持稳定,你应该将
速度因子
的最大值限制在
2.0左右。

如果你想制作平滑的动画,那么你根本不应该依赖固定的时间步长。相反,无论何时渲染帧,都会获得当前时间,并在该确切时间的适当位置绘制内容

这样,即使以不均匀的间隔绘制帧(这肯定会发生),动画也会变得平滑

要做到这一点,您需要能够随时计算正确的位置

对于某些D和F,您的增量更新最终使用以下公式确定位置:

x=e-Dt*(cos(DFt)+sin(DFt)/F)

有一个衰减分量(D代表衰减),乘以一个正弦分量(F代表频率)

通过增加D使块移动得更快。通过增加F使块在底部更具弹性

我已经修复了您的代码片段,在
updateSmooth
中使用此计算,使绿色块比红色块稍微慢一点,具有大致相同的弹性。注意我们如何准确地说出它的开始和停止位置,您可以轻松直观地调整D和F参数。单击“重置”时,我们只需重置开始时间即可重新开始动画。无需记住
a
v

var canvas=document.getElementById(“canvas”);
var context=canvas.getContext(“2d”);
类向量{
构造函数(x,y,z){
这个.x=x;
这个。y=y;
这个。z=z;
}
}
var speedX=1.2;
var p=新向量(0,0);
var v=新向量(0,0);
var a=新向量(0,0);
var p2=新向量(0,0);
var centerX=canvas.width/2;
var centerY=canvas.height/2;
var edgeLength=20;
var帧时;
var targetFrameRate=40;
var requiredFrameTime=1000/目标帧速率;
var速度因子=0;
var prevFrameTime=新日期().getTime();
函数绘图(){
context.save();
context.fillStyle=“#000000”;
context.fillRect(0,0,canvas.width,canvas.height);
context.fillStyle=“#ff0000”;
context.fillRect(centerX-边缘长度/2-p.x*8,centerY-边缘长度/2,边缘长度,边缘长度