随着时间的推移加速

时间:2016-07-04 12:32:50

标签: javascript jquery math geometry intersection

方案

我正在进行一项自上而下的观看游戏,其中敌人会朝某些位置移动。移动的目的地往往会发生剧烈变化 - 有时候敌人仍然朝着前一个目的地运动......

我希望实现比线性运动更逼真的运动,因此当敌人在目的地之间切换时应该有一些加速和减速。

转向(方向)不是一个因素。您可以假设精灵将像气垫船一样移动,在加速和减速方面尽可能快地在目的地之间漂移。

为简单起见 - 我们假设只有一维(Y)而不是X和Y ......运动应该模拟一辆只能向北或向南移动的汽车。

由于我们正在考虑在这种情况下的实际运动,您可能不会感到惊讶的是,随着时间的推移最大速度也是一个考虑因素。敌人永远不应超过它自己的最高速度;敌人在变量中存储自己的最大速度。

最后一个考虑因素是,敌人不仅会拥有最高速度'值,但它也会有一个最大加速度'价值 - 这将指导每个敌人对相反方向移动的反应速度。

为简单起见,假设敌人没有任何运动摩擦......当它停止加速时,它会一直以当前速度巡航。

实施例

有关上下文,请详细说明汽车示例。一辆特定的汽车有:

  • 最高速度:每秒10米
  • 最大加速度:可在2秒内达到最高速度
  • (目的地y-pos,位置y-pos,当前速度等其他因素)

就像我开车时一样,我想所有这些价值都存在,但我无法改变它们。我能真正改变的是我多少把脚放在加速/刹车上。让我们称之为“节流”'。就像汽车中的加速踏板一样,我可以随时将此值更改为任何数量。

我可以将我的脚放下(油门= 1),立即松开踏板(油门= 0),甚至换成反向并再次抬起我的脚(油门= -1)。让我们假设油门的这些变化是瞬间的(不像速度或加速度,它会随着时间的推移而增长/收缩)

所有这一切,我想,我真正需要计算的唯一值是油门应该是什么,因为那是我在车辆中唯一可以控制的。

那么,我怎么知道在任何特定时刻使用多少油门,尽可能快地到达目的地(如某些红绿灯)而不会超出我的目的地?我需要知道按下加速器有多少,根本不会加速,然后在我到达目的地时会有多大的减速速度。

预占运动

这款游戏可能会有一个在线组件。也就是说,玩家将通过套接字连接传输他们的位置......但即使是最好的连接也无法经常发送位置以实现平稳移动 - 您需要插值。你需要发送'速度' (速度)以及坐标,以便我可以在接收数据包之间的时间内假设未来的位置。

出于这个原因,Tweens是不行的。没有办法发送补间,然后准确地通知其他各方当前每个实体的每个补间的哪一点(我的意思是,我认为这是可能的,但可怕的过于复杂,可能涉及相当大的数据包发送,可能在在线组件方面也很容易被利用),那么你需要考虑在目的地改变时中止Tweens等。

不要误会我的意思,我可能已经使用Tweens的轻松/缓出功能模拟了一些非常逼真的动作,它看起来很棒,但是在线设置会是......非常凌乱。

到目前为止看起来如何?

所以,基本上我已经确定了需要随时计算的主要方面是使用多少油门。让我们通过我的逻辑......

想象一下,随着时间的推移,一个非常基本的线性运动公式......它看起来像这样:

示例1 - 随时间变化的位置

currentDY = 5;               // Current 'velocity' (also called Delta Y or 'DY')
currentY += currentDY * time // Current Y pos is increased by movement speed over time.

正如您所看到的,在任何特定时刻,由于“速度”,Y位置会随着时间的推移而增加。或DY(随着时间的推移)。时间只是ONCE的一个因素,所以一旦到达目的地,我们只需将DY设置为零。非常尖锐的不切实际的运动。为了使运动平滑,速度也需要随时间变化......

示例2 - 随时间变化的速度

throttle = -1
currentDY += throttle * time;
currentY += (currentDY * time);
//Throttle being -1 eventually decelerates the DY over time...

这里,油门是' -1' (最大反转!),所以随着时间的推移,这会降低速度。这对于逼真的加速非常有效,但不能提供逼真的预期或减速。

如果我们这次到达目的地,我们可以将油门设置为' 0' ...但是制动时间已经太晚了,所以最终的敌人只会永远地超越目标。我们可以节流=' 1'回去,但我们会永远来回摆动。

(另请注意,最大加速度和速度甚至还不是一个因素 - 它肯定是必须的!敌人不能永远保持其速度;速度增量和ALSO加速增量需要有限制)。

所有这一切都说,仅仅随着时间的推移改变速度是不够的,但我们还需要能够预测在发生之前减速多少(即向前踩油门#39;)。这是我到目前为止所得到的,我几乎可以肯定这是错误的做法......

示例3 - 随着时间的推移节流? (我被困了)

guideY = currentY + (currentDY * (timeScale * 3000));
dist = endY - guideY;
throttle = Math.max(-1, Math.min(1, dist / 200));
currentDY += throttle * time;
currentY += (currentDY * time);

正如你所看到的,这一次我们试图通过猜测来预测节流的使用量,其中敌人位置将在未来的任意时间内(即3秒)。如果guideY越过目的地,我们就知道我们必须开始制动(即降低速度以停在目的地之上)。多少 - 取决于敌人未来位置的距离(即throttle = dist / 200;

这是我放弃的地方。测试此代码并更改值以查看它们是否正确缩放,敌人总是在目的地上方摆动,或者需要太长时间才能关闭。在目的地。

我觉得这是错误的方法,我需要更准确的东西。感觉我需要一个交叉点来正确预测未来的位置。

我只是为3 secondsdist / 200使用了错误的值,还是我没有在这里实施完全有效的解决方案?

目前,与线性运动相比,在目标位置到达总是需要花费8倍的时间。我还没有达到实现DeltaVelocity或DeltaAcceleration的最大值的程度 - 接受的解决方案必须考虑这些值,尽管我的JSFiddle 中没有出现......

测试我的逻辑

我已将所有示例放在正常工作的JSFiddle中。

Screenshot of JS fiddle and code (for context)

JSFiddle working testbed (单击canvas下面的' ms'按钮来模拟时间的推移。单击按钮然后按+保持Return以便快速重复)

精灵最初是在错误的情况下进行的。方向 - 这是为了测试稳健性 - 它假设一个想象的场景,我们刚刚尽快移动到屏幕上远低的旧目的地,现在我们需要突然开始向上移动......

正如您所看到的,我的第三个示例(请参阅update函数),精灵的定时时间为'在理想的位置需要比它应该更长的时间。我的数学错了。我无法理解这里需要的东西。

throttle在任何时候应该怎样?使用throttle甚至是正确的方法吗?非常感谢您的协助。

决胜局

好吧,我已经被困在这几天了。这个问题正在为一些phat赏金。

如果需要决胜局,获胜者需要证明数学足够合理,可以反向测试。这就是原因:

由于游戏还包含多人游戏组件,敌人将传输他们的位置和速度。

作为黑客保护,我最终需要一种方法来远程检查'如果可以在任何两个采样时间之间的速度和位置

如果基于最大速度和加速度的移动太快,则会调查帐户等。您可以假设游戏将提前知道敌人的真实最大加速度和速度值。

所以,除了赏金之外,你也可以满意地知道你的答案会毁掉肮脏的电子游戏骗子的生活!

1 个答案:

答案 0 :(得分:2)

编辑2 :小提琴由答案作者添加;如果其他人发现这个问题,请加入他的回答:http://jsfiddle.net/Inexorably/cstxLjqf/。用法/数学在下面进一步解释。

修改1 :重写以便在评论中收到澄清。

你应该真正改变你的实施风格。

假设我们有以下变量:currentX,currentY,currentVX,currentVY,currentAX,currentAY,jerk。 currentVX就是你当前的DC。同样,currentAX是delta速度值的x分量(accel是速度的导数)。

现在,按照你的风格,我们将有一个guideX和guideY。但是,您如何做到这一点还有另一个问题:您通过在三秒内预测目标的位置来找到指南。虽然这是一个好主意,但无论你与目标有多接近,你都会使用三秒钟(无论距离多么小)。因此,当精灵离目标0.5秒时,它仍然会朝着目标的估计位置(未来三秒)移动。这意味着它实际上无法击中目标,这似乎是您所暗示的问题。

继续,回想一下我之前提到的变量。这些是当前的变量 - 也就是说,它们会在经过几秒钟后的每次调用时更新(就像你以前一样)。你还提到了拥有maxV和maxA的愿望。

请注意,如果currentVX为5且currentVY为7,则速度为(5 ^ 2 + 7 ^ 2)^ 0.5。那么,每当您更新“当前”时,您将要做的事情是什么?变量的原型是在更新值之前,看看那些变量(如我用速度显示的那些变量的sqrt(x ^ 2 + y ^ 2))是否会超过您设置为的相应maxV,maxA或jmax值常数。

我还希望改进您​​生成指南值的方式。我将假设指南可以移动。在这种情况下,目标将具有上面列出的值:x,y,vx,vy,ax,ay,jx,jy。您可以将这些命名为,但是我想要使用targetX,targetY等来更好地说明我的观点。

从这里你应该找到你的指导价值。虽然精灵距离目标超过三秒,但您可以在三秒钟内使用目标位置(注意:我建议将其设置为变量,以便修改起来很简单)。对于这种情况:

 predictionTime = 3000*timescale; //You can set this to however many seconds you want to be predicting the position from.

如果您真的想要,可以使用积分函数或循环为曲线平滑曲线,以便从目标值获得更准确的指导值。但是,这不是一个好主意,因为如果您实现多个目标/等,它可能会对性能产生负面影响。因此,我们将使用非常简单的估算,这种估算非常准确,因为它具有如此低的成本。

 if (sprite is more than predictionTime seconds away from the target){
      guideX = targetX + predictionTime * targetVX;
      guideY = targetY + predictionTime * targetVY;
 }

请注意,我们没有考虑目标的加速度和加速度,这种简单的近似不需要它。

然而,如果精灵比预测时间秒远离目标,该怎么办?在这种情况下,我们希望开始越来越多地减少我们的predictionTime值。

 else{
      guideX = targetX + remainingTime * targetVX;
      guideY = targetY + remainingTime * targetVY;.
 }

这里有三种选择,可以找到剩余时间的值。您可以将remainingTime设置为零,使指南坐标与目标相同。你可以将remainingTime设置为sqrt((targetX-currentX)^ 2 +(targetY-currentY))/(sqrt(currentVX)^ 2 +(currentVY)^ 2),这基本上是2d的距离/时间,便宜且体面的近似。或者您可以使用前面提到的for循环来模拟积分以考虑变化的速度值(如果它们偏离maxV)。但是,通常你会保持或接近maxV,所以这不值得花费。编辑:另外,我还建议将remainingTime设置为0,如果它小于某个值(可能大约0.5左右。这是因为你不想要hitbox问题,因为你的精灵正在遵循指南坐标有一个轻微的偏移(以及在圆圈中移动的目标会给它一个更大的速度值,同时改变方向/基本上是一个强有力的逃避策略。也许你应该专门添加一些东西。

我们现在有了guideX和guideY值,并且考虑到非常接近移动目标,这会影响指南坐标距离目标的距离。我们现在将做现在的'价值原型。

我们将首先更新最低导数值,检查它们是否在我们的最大值的范围内,然后更新下一个最低值等。请注意,JX和JY如前所述,以允许非恒定加速。

 //You will have to choose the jerk factor -- the rate at which acceleration changes with respect to time.    

 //We need to figure out what direction we're going in first.  Note that the arc tangent function could be atan or whatever your library uses.
 dir = arctan((guideY-currentY)/(guideX-currentX));

这将以弧度或度数的形式返回方向,具体取决于您的trig库。这是你的精灵在指导方向上需要采取的角度。

 t = time; //For ease of writing.
 newAX = currentAX + jerk*t*cos(dir);
 newAY = currentAY + jerk*t*sin(dir);

您可能想知道newAx值将如何降低。如果是这样,请注意,如果指南位于目标的左侧,则cos(dir)将返回负数,并且如果精灵需要向下,则sin(dir)将返回负值。因此,还要注意,如果指南直接位于精灵下方,则newAx将为0,newAY将为负值,因为它会下降,但加速度的大小,换句话说,与maxA相比,将是积极的 - 即使精灵向下移动,它也不会以负速度移动。

请注意,因为cos和sin大概与atan相同,所以单位将是相同的(所有度数或所有弧度)。我们有一个最大加速度值。因此,我们会检查以确保我们没有超过它。

 magnitudeA = sqrt(newAX^2+newAY^2);
 if (magnitudeA > maxA){
      currentAX = maxA * cos(dir);
      currentAY = maxA * sin(dir);
 }

所以在这一点上,我们要么限制加速度,要么具有令人满意的加速度分量,其幅度小于maxA。让我们对速度做同样的事情。

 newVX = currentVX + currentAX*t;
 newVY = currentVY + magnitudeA*t*sin(dir);

请注意,我在此处提供了两种查找速度分量的方法。任何一个都有效,我建议选择一个并将其用于x和y速度值以简化。我只是想强调加速度的概念。

 magnitudeV = sqrt(newVX^2+newVY^2);
 if (magnitudeV > maxV){
      currentVX = maxV * cos(dir);
      currentVY = maxV * sin(dir);
 }

我们也想停止在我们的目标周围进行游戏。但是,我们并不想像你在JSFiddle中那样放慢速度,因为如果目标正在移动它将会逃脱(笑)。因此,我建议您检查一下您的距离,如果您处于某个接近范围内,请使用距离以及目标速度的偏移线性降低您的速度。因此,将closeTime设置为小到0.3或者游戏中感觉良好的东西。

 if (remainingTime < closeTime){
      //We are very close to the target and will stop the boomerang effect.  Note we add the target velocity so we don't stall while it's moving.
      //Edit: We should have a min speed for the enemy so that it doesn't slow down too much as it gets close.  Lets call this min speed, relative to the target.
      currentVX = targetVX + currentVX * (closeTime - remainingTime);
      currentVY = targetVY + currentVY * (closeTime - remainingTime);
      if (minSpeed > sqrt(currentVX^2+currentVY^2) - sqqrt(targetVX^2-targetVY^2)){
           currentVX = minSpeed * cos(dir);
           currentVY = minSpeed * sin(dir);
      }

 }
 magnitudeV = sqrt(currentVX^2+currentVY^2);

此时我们也有很好的速度值。如果您要使用速度表或检查速度,您对magnitudeV感兴趣。

现在我们对位置做同样的事情。请注意,您应该包括检查位置是否良好。

 newX = currentX + currentVX*t;
 newY = currentY + currentVY*t;

 //Check if x, y values are good.
 current X = newX; currentY = newY;

现在所有内容都已使用好的值更新,您可以写入屏幕。