使用Unity Physics进行轨迹预测

在最近的一些原型制作工作中,我们需要显示游戏中弹丸轨迹的预测。 您以前可能已经在许多游戏中看到过类似的内容,例如《愤怒的小鸟》:

我们的原型游戏是在Unity中进行的,而射弹是使用Unity的物理引擎进行设置的。 我们对预测有几个要求:

  • 即时。 播放器的输入可以在不同的帧之间变化,并且预测需要与其保持同步。
  • 准确。 飞行时间可能是几秒钟,并且任何小错误都会累积起来,从而产生明显不正确的结果。
  • 模拟拖动。 我们在刚体上使用了阻力,这是许多解决方案无法解决的。

我以为这种问题经常出现,并在网上搜索以查看流行的实现方式。 他们通常分为三类:

  • 准确,但速度较慢。 这些解决方案将无形的射弹克隆体带入了世界,并沿飞行路径发射,记录了其随时间的运动。 由于无法自己进行Unity物理模拟,因此您必须实时等待此预测。 这意味着三秒钟的飞行需要三秒钟才能完全预测。 这太慢了-预测将始终落后于玩家不断变化的输入。
  • 不包括阻力。 有一些好的,准确的解决方案,但大多数解决方案将专门排除阻力。
  • 不准确 不正确的方程式,假设和近似值的某种组合意味着更长的飞行时间和更大的阻力(或不同的重力)会导致预测错误。

也许对我们来说完美的解决方案已经存在,但是我还没有找到。 通过结合现有的解决方案并运行一些测试,我提出了自己的实现,如下所示。

该函数通过模拟一些FixedUpdate迭代并在每次迭代中返回弹丸的位置来绘制在Unity物理作用下的刚体轨迹。 它使用全局Physics2D.gravity设置,并考虑了刚体阻力和gravityScale。 注意,刚体的质量无关紧要。

  float timestep = Time.fixedDeltaTime / Physics2D.velocityIterations; 

该代码尝试产生与运行常规Unity物理迭代相同的结果。 为此,它还必须作为迭代解决方案运行。 这里常见的错误是假定每个FixedUpdate()运行一次迭代。 而是可以访问和调整要执行的迭代次数-Physics2D.velocityIterations。 这有助于我们计算时间步长。

  Vector2gravityAccel = Physics2D.gravity *刚体.gravityScale * timestep * timestep; 

在计算重力影响时,我们会考虑刚体的gravityScale属性。 我们发现我们希望在每个对象上使用不同的重力和拖动量,因此此按身体的设置确实很有用。

 浮动阻力= 1f-时间步* 

拖动可减少每次迭代中的moveStep。 我们可以预先计算它,然后将其应用于迭代的每个步骤,从而产生累积效果。

  for(int i = 0; i <步骤; ++ i) 
{
moveStep + =重力加速度;
moveStep * =拖动;
pos + = moveStep;
结果[i] = pos;
}

最后是主循环。 每次迭代,您将由于重力而移动,由于阻力而减少运动,然后在结果中累积并存储新位置。

该解决方案对我们来说效果很好。 尽管没有进行详尽的测试,但我们将其用于具有许多不同速度和阻力的弹丸,并且即使在飞行4-5秒后,也证明每次都是准确的。

缺点

长轨迹涉及很多计算。 使用默认设置,您必须为要预测的每秒飞行运行400次循环。 在我们的原型中,我们只有一个弹丸可以预测,因此我们只运行一个预测,这并不会花费太多。 如果您使用它来预测大型游戏中许多不同发射器的大量弹丸,那么这可能对您来说将成为一个问题。

而且,它只是模拟轨迹,而不是实际运行物理引擎或模拟游戏中的其他任何东西。 这意味着它无法预测碰撞或碰撞解决方案。 如果按原样渲染该路径,它将仅穿过墙壁或世界上的其他障碍,这显然不是发射射弹时实际发生的情况。

这些缺点对于我们的原型来说不是问题,因此事实证明这是非常有用的代码。 我们现在分享这一点,希望其他人也面临相同的要求,并且发现它也很有用。

未来可能的升级

我对这种方法的缺点有一些想法。 这是需要改进的主要方面,因为实际的功能还不错。

为了提高性能,没有执行任何性能分析或优化工作。 我已经按照我认为合理的方式进行了布置。 很难猜测优化,但是也许进行一些分析就可以发现一些简单的加速。 更大的步骤是将这些代码推送到本机dll,然后深入研究具体的c ++优化-也许使用SIMD指令。 您无法并行化步骤(循环的每次迭代取决于上一个的结果),但可以并行化多个弹丸预测-例如,如果您有多个弹丸,则并行运行4或8个预测。

另一个重大升级是围绕预测。 对于某些游戏,真正的预测将非常有价值-例如,可视化撞球桌游戏中的碰撞和反应结果。 即使在几次反弹之后,您仍想查看球的预测路径。 如果您具有任何深入的物理特性,那么使用任何简单的模型都不会发生这种情况。 您需要在方法上进行重大转变-自己运行物理引擎。 我会找到一个合适的现有物理引擎并将其构建到游戏/ Unity中,这很遗憾,因为它正在复制Unity已经完成的工作。 但是,这样做之后,您将可以控制物理模拟以及如何对其进行更新。

您将尝试进行一种设置,在该设置中,您可以克隆现有的模拟并运行一些更新滴答-本质上是对未来的展望-您将在假设输入不变的情况下跟踪模拟的未来状态。 这将是一个单独的模拟,因为您不希望更改池表的实际状态,而只是计算预测的未来状态。 仅运行我们上面的基本预测代码,这将更加昂贵-这是完整的物理模拟。