javascript - 随着时间的推移加速

标签 javascript jquery math geometry intersection

情境

我正在研究一种自顶向下 View 的游戏,其中敌人朝着特定位置移动。移向目标的目的地通常会发生巨大变化-有时在敌人仍在朝着先前的目标移动时...

我想实现比线性运动更真实的运动,所以当敌人在目标之间切换时,应该有一些加速和减速。

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

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

由于我们正在考虑这种情况下的实际运动,因此您可能不会对最大速度随时间的变化而感到惊讶。敌人决不能超过自己的最大速度。敌人会将自己的最大速度存储在变量中。

最后要考虑的是,敌人不仅将具有“最大速度”值,而且还将具有“最大加速度”值-这将指导每个敌人对向相反方向移动的反应速度有多快。

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



对于上下文,让我们详细说明一下汽车示例。特定汽车具有:

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

  • 就像我开车时一样,我想所有这些值都存在,但是我无法更改它们。我真正能改变的是,我在加速/刹车上投入了多少。我们称其为“ throttle ”。就像汽车中的加速踏板一样,我可以随时将此值更改为任意值,而无需任何时间。

    我可以将脚踩下( throttle = 1),立即松开踏板( throttle = 0),甚至换成倒车后再次踩脚( throttle = -1)。让我们假设 throttle 的这些变化是即时的(与速度或加速度不同,它们会随着时间的推移而增长/缩小)

    说了这么多,我想我真正需要计算的唯一值就是 throttle 应该是多少,因为那是我在车辆中唯一可以控制的东西。

    那么,我怎么知道在给定的时刻要使用多少 throttle ,以尽可能快地到达目的地(例如一些交通信号灯)而又不会超出目的地?我将需要知道按下加速器多少,根本不加速,然后在接近目的地时会减速多少。

    抢先运动

    该游戏可能会有一个在线组件。话虽如此,玩家将通过套接字连接传输位置...但是,即使是最佳连接也永远无法获得足够频繁的位置发送以实现平滑移动-您需要插值。您需要将“速度”(速度)与坐标一起发送,这样我才能在收到数据包之间的时间中假设将来的位置。

    因此,Tweens不行。无法发送补间,然后准确告知对方当前每个实体在每个补间的哪个点(我的意思是,我想这是可能的,但是非常复杂,可能涉及相当大的数据包发送,并且可能在在线组件方面也很容易被利用),那么您需要考虑在目标更改时中止Tweens等。

    不要误会我的意思,我可能已经可以使用Tweens的缓入/缓出功能对一些非常逼真的 Action 进行建模,它看起来很棒,但是在在线环境中会非常困惑。

    到目前为止看起来如何?

    因此,从本质上讲,我已经确定了需要随时计算的主要方面是要使用多少 throttle 。让我们按照我的逻辑来做...

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

    示例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(随时间变化)而随时间增加。时间只是一次因素,因此一旦到达目的地,我们只需将DY设置为零即可。非常尖锐的不切实际的运动。为了使运动平稳,速度ALSO还需要随时间变化...

    示例2-随时间变化的速度
    throttle = -1
    currentDY += throttle * time;
    currentY += (currentDY * time);
    //Throttle being -1 eventually decelerates the DY over time...
    

    在这里, throttle 为'-1'(最大反向!),所以随着时间的流逝,这将降低速度。这对于实际加速很有用,但不提供实际预期或减速。

    如果这次我们到达目的地,可以将 throttle 设置为“0” ...但是为时已晚,所以制止的敌人将永远一直越过目标。我们可以节流= 1来返回,但最终将永远来回摆动。

    (还请注意,最大加速度和速度甚至还不是一个因素-一定要做到这一点!敌人无法永远不断提高速度;速度增量和加速度增量也必须有限制)。

    综上所述,仅凭时间改变速度是不够的,但是我们还需要能够预测在发生之前要减速多少(即“后退 throttle ”)。这是到目前为止我得到的,我几乎可以肯定这是错误的方法...

    示例3-随着时间的流逝? (我被卡住了)
    guideY = currentY + (currentDY * (timeScale * 3000));
    dist = endY - guideY;
    throttle = Math.max(-1, Math.min(1, dist / 200));
    currentDY += throttle * time;
    currentY += (currentDY * time);
    

    如您所见,这次我们试图通过猜测 future 任意时间(即3秒)内敌人的位置来预测使用多少 throttle 。如果guideY超过了目的地,我们就知道我们必须开始刹车(即降低速度,以停止在目的地的顶部)。多少-取决于敌人的 future 位置有多远(即throttle = dist / 200;)

    这是我放弃的地方。测试此代码并更改值以查看它们是否正确缩放,敌人总是在目标上方摆动,或者花费太长时间无法“封闭”在目标上。

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

    我只是为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甚至是正确的方法吗?非常感谢您的协助。

    决胜局

    好吧,我已经坚持了好几天。这个问题正在引起一些柏柏金赏金。

    如果需要决胜局,获胜者将需要证明数学足够合法以进行反向测试。原因如下:

    由于游戏还包含多人游戏组件,因此敌人将传递其位置和速度。

    作为黑客保护,我最终将需要一种方法来远程“检查”在任意两个采样时间之间的速度和位置是否可能

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

    因此,除了赏金外,您还可以满意地知道您的答案将有助于破坏肮脏的视频游戏作弊者的生活!

    最佳答案

    编辑2 :答案作者添加的小提琴;以防万一其他人发现此问题:http://jsfiddle.net/Inexorably/cstxLjqf/。用法/数学在下面进一步说明。

    编辑1 :为注释中的澄清而重写。

    您应该真正更改实现样式。

    可以说我们有以下变量:currentX,currentY,currentVX,currentVY,currentAX,currentAY, SCSS 。
    currentVX就是您的currentDX。同样,currentAX是增量速度值的x分量(加速度是速度的导数)。

    现在,按照您的样式,我们将有一个guideX和一个guideY。但是,如何执行此操作还有另一个问题:通过在三秒钟内预测目标的位置来找到指南。虽然这是个好主意,但是无论您距离目标多近(无论dist多么小),您都需要花费三秒钟。因此,当精灵距离目标0.5秒时,它仍将朝目标的估计位置移动( future 3秒)。这意味着它实际上将无法达到目标,这似乎是您暗示的问题。

    继续,回想一下我之前提到的变量。这些是当前变量-即,在经过几秒钟后(如您之前所做的那样),将在每次调用时更新它们。您还提到了拥有maxV和maxA的愿望。

    请注意,如果currentVX为5,currentVY为7,则速度为(5 ^ 2 + 7 ^ 2)^ 0.5。因此,每次更新变量的“当前”原型(prototype)时,您要做的是在更新值之前,查看这些变量的大小(例如sqrt(x ^ 2 + y ^ 2))是否我以速度显示)将超过您已设置为常数的相应maxV,maxA或jmax值。

    我还想改善您如何生成指导值。我将假设该指南可以移动。在这种情况下,目标将具有上面列出的值:x,y,vx,vy,ax,ay,jx,jy。您可以随意命名,我将使用targetX,targetY ...等来更好地说明我的观点。

    从这里您应该找到您的指导值。当精灵距离目标3秒钟以上时,您可以在3秒钟内使用目标的位置(注意:我建议将其设置为变量,以便于修改)。对于这种情况:

     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;
     }
    

    注意,在此我们没有考虑目标的加速度和加速度,因此不需要这种简单的近似方法。

    但是,如果精灵离目标的时间比预报时间秒少,该怎么办?在这种情况下,我们要开始逐渐减少我们的projectionTime值。
     else{
          guideX = targetX + remainingTime * targetVX;
          guideY = targetY + remainingTime * targetVY;.
     }
    

    在这里,您可以通过三种选择来找到剩余时间的值(value)。您可以将剩余时间设置为零,使引用线坐标与目标相同。您可以将剩余时间设置为sqrt((targetX-currentX)^ 2 +(targetY-currentY))/(sqrt(currentVX)^ 2 +(currentVY)^ 2),这基本上是2d的距离/时间,便宜且体面的近似。或者,您可以使用前面提到的for循环来模拟一个积分,以说明变化的速度值(如果它们偏离了maxV)。但是,通常您将保持在maxV或接近maxV的位置,因此这并不值得。编辑:另外,如果小于某个值(可能大约为0.5左右),我也建议将剩余时间设置为0。这是因为您不希望出现击中箱问题,因为您的精灵遵循的是带有微小偏移量的引导坐标(以及在圆周方向移动的目标在改变方向时会赋予它更大的速度值/本质上是一种强有力的逃避策略。也许您应该为此专门添加一些东西。

    现在,我们有了guideX和guideY值,并考虑到非常接近移动的目标,这会影响应该将引用坐标放置到目标的距离。现在,我们将进行“当前”值原型(prototype)。

    我们将首先更新最低导数,检查它们是否在最大值的范围内,然后更新下一个最小值,等等。请注意,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));
    

    这将以弧度或度为单位返回方向为 Angular Angular ,具体取决于您的Trig库。这是精灵在引导方向上需要采取的 Angular 。
     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);
    

    在这一点上,我们也具有良好的速度值。如果要装上速度计或检查速度,则对幅值V感兴趣。

    现在,我们对职位也做同样的事情。请注意,您应该检查位置是否合适。
     newX = currentX + currentVX*t;
     newY = currentY + currentVY*t;
    
     //Check if x, y values are good.
     current X = newX; currentY = newY;
    

    现在,所有内容均已更新为具有良好的值,您可以将其写入屏幕。

    关于javascript - 随着时间的推移加速,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38184863/

    相关文章:

    javascript - 为什么 draggable 不适用于动态创建的元素?

    javascript - 使用 jquery 和 tablesorter,更改所选单元格的值

    javascript - Javascript 中的 Excel ROUND 函数

    java - 在 Java/Slick2D 中向鼠标位置射击子弹

    algorithm - 动态堆栈的摊销分析

    javascript - 如何在immutablejs中获取列表 HashMap 的合并集嵌套数组

    javascript - 从对象中删除重复项并在存在重复项时获取键的总和

    javascript - $.post 错误回调中的 ReactDOM.render

    javascript - 如何根据文本框输入动态填充选择框

    javascript - 在一个网页中嵌入多个 sigma.js 图表