Unity C# 向围绕移动轴旋转的目标发射弹丸

Posted

技术标签:

【中文标题】Unity C# 向围绕移动轴旋转的目标发射弹丸【英文标题】:Unity C# Firing a projectile at a target rotating around moving axis 【发布时间】:2018-11-04 01:37:17 【问题描述】:

我正在开发一个 3D 太空游戏,其中相机处于恒定的 2D(自上而下)状态。我能够向以给定速度移动的目标发射速度(s)的射弹,并且每次都击中它。伟大的!好吧,如果那个目标在父母周围有一个角速度呢?我注意到如果目标有一个正在旋转的父对象,我的投影不正确,因为它没有考虑角速度。

我的初始代码是围绕以下假设构建的:

Position_target + Velocity_target * t = Position_shooter + Velocity_shooter * t + Bulletspeed * t

我假设射手是静止的(或可能在移动)并且需要发射一个恒定大小的子弹。

我把上面的简化成这个

Delta_Position = Position_target - Position_shooter
Delta_Velocity = Velocity_target - Velocity_shooter

Delta_Position + Delta_Velocity * t = BulletSpeed * t

对两边求平方,我得到一个二次方程,我可以在给定行列式结果或零的情况下求解 t。这很完美。我返回一个 t 值,然后将目标的位置和当前速度投影到该 t,然后我有炮塔脚本以给定的角速度向该点旋转。如果炮塔说它在所有轴上的 1% 范围内看那个点,它会以速度发射子弹,如果目标不改变其航向或速度,它会 100% 命中。

我开始在我的飞船/小行星上添加组件,这些组件是父对象的子对象,例如连接到飞船的炮塔,炮塔本身就是目标。如果船围绕一个轴(例如 Y 轴)旋转并且炮塔不在 x=0 和 z=0 处,我的投影不再起作用。我认为使用 r * sin ( theta + omega * t) 作为 X 位置的角速度分量和 r * cos ( theta + omega * t) 作为 Z 位置的角速度分量可以工作。 Theta 是当前旋转(相对于世界坐标),而 omega 是围绕 y 轴的 eulerAngle 旋转。

我很快意识到这只适用于绕 y 轴旋转,我不能将 sin 放入二次方程,因为我不能从中提取 t,所以我不能真正正确地投影这个。我尝试使用双曲线,但情况相同。我可以创建一个任意的 t,假设 t=2,然后计算对象在 2 秒内的位置。但我正在努力寻找一种方法来实现子弹速度投影。

Position_targetparent + Velocity_targetparent * t + [ANGULAR VELOCITY COMPONENT] = Position_shooter + Velocity_shooter * t + Bulletspeed * t

Delta_Position_X + Delta_Velocity_X * t + S * t = r * sin (theta + Omegay * t)
Delta_Position_Z + Delta_Velocity_Z * t + S * t = r * cos (theta + Omegay * t)

从这里开始,我一直在不停地转动我的***,试图找出一个可行的解决方案。我将 eulerAngle.y 用于运行良好的欧米茄。最终,我只需要我应该射击的空间中的瞬时点,它是子弹速度和投影距离的乘积,然后我的炮塔瞄准脚本会处理剩下的事情。

我一直在研究基于父母位置(旋转中心)的球坐标系

Vector3 deltaPosition = target.transform.position - target.transform.root.position;
r = deltaPosition .magnitude;
float theta = Mathf.Acos(deltaPosition.z / r);
float phi = Mathf.Atan2(deltaPosition.y,deltaPosition.x);

float xPos = r * Mathf.Sin(theta) * Mathf.Cos(phi)
float yPos = r * Mathf.Sin(theta) * Mathf.Sin(phi)
float zPos = r * Mathf.Cos(theta)

Vector3 currentRotation = transform.root.gameObject.transform.rotation.eulerAngles * Mathf.Deg2Rad;
Vector3 angularVelocity = transform.root.gameObject.GetComponent<Rigidbody>().angularVelocity;

在给定这些角度的情况下,我可以计算出物体的位置……但我正在努力将其转化为可以与 omega * t(角速度)方法一起使用的东西。

我想知道是否有更优雅的方法来解决这个问题,或者是否有人可以指出正确的公式方向来帮助我思考这个问题?我在四元数和 EulerAngles 方面不是最好的,但我正在慢慢学习它们。也许我可以用这些做一些聪明的事情?

【问题讨论】:

作为参考,您可以使用编辑器中的图片上传按钮添加图片。 AFAIK,你不能嵌入它,直到你有 10 个代表,但任何拥有超过 10 个代表的编辑器都可以在编辑中做到这一点。 最简单的方法是让你的子弹快速,如果你想让子弹确定命中,使用lookat让你的子弹总是指向目标。 我同意@TylerS.Loeper。您必须知道子弹击中前的时间(t1)。那么你必须找出它在 t1 处的 (p1) 命中位置并瞄准它,然后 t1 可能会变为 t2 并导致 p1 变为 p2 等等。 你需要为此求解一个动态系统方程! @TylerS.Loeper 正确,我在最初的帖子中简要添加了这一点。 Ps+Vs * t + B*t = Pt + Vt * t。我只知道在我错过的目标一侧有一个角度分量。把它带到数学方面也是个好主意,我会这样做! 【参考方案1】:

虽然数学可能仍然很困难,但我怀疑您可以通过让“目标”计算其在本地空间中的未来位置来大大简化数学。然后让它将该位置调用给它的父级,让它在本地空间中计算它,依此类推,直到你到达世界空间。一旦你确定了它在世界空间中的未来位置,你就可以将你的炮塔瞄准那个目标。

例如,轨道飞船应该能够轻松计算其未来的轨道。这是一个椭圆的方程。然后它可以将该本地位置发送到它的父(行星),它可能也在绕轨道运行并计算相对于自身的位置。然后行星会将这个本地位置发送给它自己的父(星)等等。直到你到达世界空间。

您可以通过使子弹的行进时间恒定(灵活的速度)来进一步简化此数学运算,这样您就可以简化计算特定时间未来位置的过程。根据您的游戏规模,实际的速度差异可能不会那么不同。

另一个想法:您可以及时“模拟”目标对象,而不是通过蛮力进行所有计算。确保所有影响位置的代码都可以与您的实际更新循环分开运行。只需将时钟向前推进,无需实际移动即可查看其未来位置。然后回到现在,在未来的位置开枪。

【讨论】:

【参考方案2】:

我建议大致解决这个问题。 如果您可以通过函数 f(t) 随时间推移来描述目标的位置,那么您可以使用 divide and conquer strategy 来近似它,如下所示:

算法(伪代码):f(t:float):Vector3 为计算目标在时间 t 的位置的函数 让 g(p:Vector3):float 成为计算子弹需要多长时间才能到达 p

的函数
float begin = 0    // Lower bound of bullet travel time to hit the target
float end = g(target.position)    // Upper bound

// Find an upper bound so that the bullet can hit the target between begin and end time
while g(f(end)) > end:
    begin = end
    end = end * 2    // Exponential growth for fast convergence
    // Add break condition in case the target can't be hit (faster than bullet)
end    

// Narrow down the possible aim target, doubling the precision in every step
for i = 1...[precision]:
    float center = begin + (end - begin) / 2
    float travelTime = g(f(center))

    if travelTime > center:    // Bullet can't reach target
        begin = center
    else    // Bullet overtook target
        end = center
    end
end

float finalTravelTime = begin + (end - begin) / 2
Vector3 aimPosition = f(finalTravelTime)    // You should aim here...

您需要试验 [precision] 的值。它应该尽可能小,但要大到足以让子弹始终击中目标。 您还可以使用其他中断条件,例如限制绝对误差(在 finalTravelTime 时子弹到目标的距离)。 万一目标可以跑得比子弹快,就需要在上界循环上加一个break条件,否则会变成死循环。

为什么有用: 不用计算复杂的等式函数来确定撞击时间,您可以用一个相当简单的位置函数和这种方法来近似它。 该算法独立于实际位置函数,因此适用于各种敌人的运动,只要可以计算出未来的位置即可。

缺点: 此函数多次计算 f(t),对于复杂的 f(t),这可能会占用大量 CPU。 而且它只是一个近似值,结果的精度越差,行程时间越长。

注意: 我从头顶写了这个算法。 我不保证伪代码的正确性,但算法应该可以工作。

【讨论】:

以上是关于Unity C# 向围绕移动轴旋转的目标发射弹丸的主要内容,如果未能解决你的问题,请参考以下文章

在 C++/OpenGL 中投影一个围绕另一个对象旋转的对象

unity PC和移动端 摄像机围绕目标物体transform组件 旋转 缩放(改变相机position)

three.js:结合 tween.js 围绕世界轴旋转对象

unity3d一个物体围绕另一个物体旋转

带有 Unity 3D 的 C#:当用户移动鼠标时,如何使相机围绕对象移动

Unity中的旋转方式