2d游戏:通过预测射弹和单位的交叉点来射击移动目标

时间:2010-02-12 00:41:31

标签: 2d intersection predict

好的,这一切都发生在一个漂亮而简单的2D世界......:)

假设我在位置Apos处有一个静态物体A,在Bpos处有一个带有bVelocity的线性移动物体B,以及一个速度为Avelocity的弹药轮...

考虑到B的线速度和A弹药的速度,我如何找出A必须射击的角度,击中B?

现在目标位于物体的当前位置,这意味着当我的射弹到达那里时,该单位已经转移到更安全的位置:)

11 个答案:

答案 0 :(得分:41)

我前一段时间为xtank写了一个瞄准子程序。我会试着说明我是怎么做到的。

免责声明:我可能在这里的任何地方犯过一个或多个愚蠢的错误;我只是想用我生锈的数学技巧来重建推理。但是,我会先切入追逐,因为这是一个编程Q& A而不是数学类: - )

怎么做

归结为解决形式的二次方程:

a * sqr(x) + b * x + c == 0

请注意,sqr我的意思是方形,而不是平方根。使用以下值:

a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)
b := 2 * (target.velocityX * (target.startX - cannon.X)
          + target.velocityY * (target.startY - cannon.Y))
c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)

现在我们可以查看判别式以确定我们是否有可能的解决方案。

disc := sqr(b) - 4 * a * c

如果判别力小于0,则忘记击中目标 - 你的射弹永远无法及时到达目标。否则,请看两个候选解决方案:

t1 := (-b + sqrt(disc)) / (2 * a)
t2 := (-b - sqrt(disc)) / (2 * a)

请注意,如果disc == 0t1t2相等。

如果没有其他考虑因素,例如干预障碍,只需选择较小的正值即可。 (负 t 值需要及时向后射击才能使用!)

将选定的t值替换回目标的位置方程,以获得您应该瞄准的领先点的坐标:

aim.X := t * target.velocityX + target.startX
aim.Y := t * target.velocityY + target.startY

推导

在时间T,射弹必须是距离炮弹的(欧几里德)距离等于经过的时间乘以射弹速度。这给出了一个圆的等式,以经过时间为参数。

sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
  == sqr(t * projectile_speed)

类似地,在时间T,目标沿着其向量移动时间乘以其速度:

target.X == t * target.velocityX + target.startX
target.Y == t * target.velocityY + target.startY

当炮弹与炮弹的距离与炮弹的距离相匹配时,炮弹可以击中目标。

sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
  == sqr(target.X - cannon.X) + sqr(target.Y - cannon.Y)

奇妙!将表达式替换为target.X和target.Y给出

sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
  == sqr((t * target.velocityX + target.startX) - cannon.X)
   + sqr((t * target.velocityY + target.startY) - cannon.Y)

代替等式的另一面给出了:

sqr(t * projectile_speed)
  == sqr((t * target.velocityX + target.startX) - cannon.X)
   + sqr((t * target.velocityY + target.startY) - cannon.Y)

...从两边减去sqr(t * projectile_speed)并翻转它:

sqr((t * target.velocityX) + (target.startX - cannon.X))
  + sqr((t * target.velocityY) + (target.startY - cannon.Y))
  - sqr(t * projectile_speed)
  == 0

...现在解决了子表达式的平方结果......

sqr(target.velocityX) * sqr(t)
    + 2 * t * target.velocityX * (target.startX - cannon.X)
    + sqr(target.startX - cannon.X)
+ sqr(target.velocityY) * sqr(t)
    + 2 * t * target.velocityY * (target.startY - cannon.Y)
    + sqr(target.startY - cannon.Y)
- sqr(projectile_speed) * sqr(t)
  == 0

......并将相似的术语分组......

sqr(target.velocityX) * sqr(t)
    + sqr(target.velocityY) * sqr(t)
    - sqr(projectile_speed) * sqr(t)
+ 2 * t * target.velocityX * (target.startX - cannon.X)
    + 2 * t * target.velocityY * (target.startY - cannon.Y)
+ sqr(target.startX - cannon.X)
    + sqr(target.startY - cannon.Y)
  == 0

...然后将它们结合起来......

(sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)) * sqr(t)
  + 2 * (target.velocityX * (target.startX - cannon.X)
       + target.velocityY * (target.startY - cannon.Y)) * t
  + sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)
  == 0

...在 t 中给出标准二次方程。找到该等式的正实零给出了(零个,一个或两个)可能的命​​中位置,这可以用二次公式来完成:

a * sqr(x) + b * x + c == 0
x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a)

答案 1 :(得分:21)

+1杰弗里汉廷在这里的优秀答案。我google了一下,找到了解决方案,这些解决方案太复杂或者没有特别关注我感兴趣的情况(2D空间中的简单恒速射弹。)他正是我需要在下面生成自包含的JavaScript解决方案。

我要补充的一点是,除了判别为否定之外,还有一些特殊情况需要注意:

  • “a == 0”:如果目标和射弹以相同的速度行进,则会发生。 (解是线性的,而不是二次的)
  • “a == 0和b == 0”:如果目标和射弹都是静止的。 (除非c == 0,否则没有解决方案,即src& dst是相同的点。)

代码:

/**
 * Return the firing solution for a projectile starting at 'src' with
 * velocity 'v', to hit a target, 'dst'.
 *
 * @param Object src position of shooter
 * @param Object dst position & velocity of target
 * @param Number v   speed of projectile
 * @return Object Coordinate at which to fire (and where intercept occurs)
 *
 * E.g.
 * >>> intercept({x:2, y:4}, {x:5, y:7, vx: 2, vy:1}, 5)
 * = {x: 8, y: 8.5}
 */
function intercept(src, dst, v) {
  var tx = dst.x - src.x,
      ty = dst.y - src.y,
      tvx = dst.vx,
      tvy = dst.vy;

  // Get quadratic equation components
  var a = tvx*tvx + tvy*tvy - v*v;
  var b = 2 * (tvx * tx + tvy * ty);
  var c = tx*tx + ty*ty;    

  // Solve quadratic
  var ts = quad(a, b, c); // See quad(), below

  // Find smallest positive solution
  var sol = null;
  if (ts) {
    var t0 = ts[0], t1 = ts[1];
    var t = Math.min(t0, t1);
    if (t < 0) t = Math.max(t0, t1);    
    if (t > 0) {
      sol = {
        x: dst.x + dst.vx*t,
        y: dst.y + dst.vy*t
      };
    }
  }

  return sol;
}


/**
 * Return solutions for quadratic
 */
function quad(a,b,c) {
  var sol = null;
  if (Math.abs(a) < 1e-6) {
    if (Math.abs(b) < 1e-6) {
      sol = Math.abs(c) < 1e-6 ? [0,0] : null;
    } else {
      sol = [-c/b, -c/b];
    }
  } else {
    var disc = b*b - 4*a*c;
    if (disc >= 0) {
      disc = Math.sqrt(disc);
      a = 2*a;
      sol = [(-b-disc)/a, (-b+disc)/a];
    }
  }
  return sol;
}

答案 2 :(得分:11)

首先旋转轴使AB垂直(通过旋转)

现在,将B的速度矢量分成x和y分量(比如Bx和By)。您可以使用它来计算您需要拍摄的矢量的x和y分量。

B --> Bx
|
|
V

By


Vy
^
|
|
A ---> Vx

您需要Vx = BxSqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo

这应该为您提供新系统中所需的矢量。转换回旧系统并完成(通过向另一个方向旋转)。

答案 3 :(得分:5)

Jeffrey Hantin has a nice solution for this problem, though his derivation is overly complicated. Here's a cleaner way of deriving it with some of the resultant code at the bottom.

I'll be using x.y to represent vector dot product, and if a vector quantity is squared, it means I am dotting it with itself.

origpos = initial position of shooter
origvel = initial velocity of shooter

targpos = initial position of target
targvel = initial velocity of target

projvel = velocity of the projectile relative to the origin (cause ur shooting from there)
speed   = the magnitude of projvel
t       = time

We know that the position of the projectile and target with respect to t time can be described with some equations.

curprojpos(t) = origpos + t*origvel + t*projvel
curtargpos(t) = targpos + t*targvel

We want these to be equal to each other at some point (the point of intersection), so let's set them equal to each other and solve for the free variable, projvel.

origpos + t*origvel + t*projvel = targpos + t*targvel
    turns into ->
projvel = (targpos - origpos)/t + targvel - origvel

Let's forget about the notion of origin and target position/velocity. Instead, let's work in relative terms since motion of one thing is relative to another. In this case, what we now have is relpos = targetpos - originpos and relvel = targetvel - originvel

projvel = relpos/t + relvel

We don't know what projvel is, but we do know that we want projvel.projvel to be equal to speed^2, so we'll square both sides and we get

projvel^2 = (relpos/t + relvel)^2
    expands into ->
speed^2 = relvel.relvel + 2*relpos.relvel/t + relpos.relpos/t^2

We can now see that the only free variable is time, t, and then we'll use t to solve for projvel. We'll solve for t with the quadratic formula. First separate it out into a, b and c, then solve for the roots.

Before solving, though, remember that we want the best solution where t is smallest, but we need to make sure that t is not negative (you can't hit something in the past)

a  = relvel.relvel - speed^2
b  = 2*relpos.relvel
c  = relpos.relpos

h  = -b/(2*a)
k2  = h*h - c/a

if k2 < 0, then there are no roots and there is no solution
if k2 = 0, then there is one root at h
    if 0 < h then t = h
    else, no solution
if k2 > 0, then there are two roots at h - k and h + k, we also know r0 is less than r1.
    k  = sqrt(k2)
    r0 = h - k
    r1 = h + k
    we have the roots, we must now solve for the smallest positive one
    if 0<r0 then t = r0
    elseif 0<r1 then t = r1
    else, no solution

Now, if we have a t value, we can plug t back into the original equation and solve for the projvel

 projvel = relpos/t + relvel

Now, to the shoot the projectile, the resultant global position and velocity for the projectile is

globalpos = origpos
globalvel = origvel + projvel

And you're done!

My implementation of my solution in Lua, where vec*vec represents vector dot product:

local function lineartrajectory(origpos,origvel,speed,targpos,targvel)
    local relpos=targpos-origpos
    local relvel=targvel-origvel
    local a=relvel*relvel-speed*speed
    local b=2*relpos*relvel
    local c=relpos*relpos
    if a*a<1e-32 then--code translation for a==0
        if b*b<1e-32 then
            return false,"no solution"
        else
            local h=-c/b
            if 0<h then
                return origpos,relpos/h+targvel,h
            else
                return false,"no solution"
            end
        end
    else
        local h=-b/(2*a)
        local k2=h*h-c/a
        if k2<-1e-16 then
            return false,"no solution"
        elseif k2<1e-16 then--code translation for k2==0
            if 0<h then
                return origpos,relpos/h+targvel,h
            else
                return false,"no solution"
            end
        else
            local k=k2^0.5
            if k<h then
                return origpos,relpos/(h-k)+targvel,h-k
            elseif -k<h then
                return origpos,relpos/(h+k)+targvel,h+k
            else
                return false,"no solution"
            end
        end
    end
end

答案 4 :(得分:2)

以下是基于极坐标的C ++中的瞄准代码。

要使用直角坐标,您需要先将目标相对坐标转换为角度/距离,然后将目标x / y速度转换为角度/速度。

“速度”输入是射弹的速度。速度和targetSpeed的单位是无关紧要的,因为在计算中仅使用速度比。输出是射弹应该发射的角度以及到碰撞点的距离。

该算法源自http://www.turtlewar.org/提供的源代码。


// C++
static const double pi = 3.14159265358979323846;
inline double Sin(double a) { return sin(a*(pi/180)); }
inline double Asin(double y) { return asin(y)*(180/pi); }

bool/*ok*/ Rendezvous(double speed,double targetAngle,double targetRange,
   double targetDirection,double targetSpeed,double* courseAngle,
   double* courseRange)
{
   // Use trig to calculate coordinate of future collision with target.
   //             c
   //
   //       B        A
   //
   // a        C        b
   //
   // Known:
   //    C = distance to target
   //    b = direction of target travel, relative to it's coordinate
   //    A/B = ratio of speed and target speed
   //
   // Use rule of sines to find unknowns.
   //  sin(a)/A = sin(b)/B = sin(c)/C
   //
   //  a = asin((A/B)*sin(b))
   //  c = 180-a-b
   //  B = C*(sin(b)/sin(c))

   bool ok = 0;
   double b = 180-(targetDirection-targetAngle);
   double A_div_B = targetSpeed/speed;
   double C = targetRange;
   double sin_b = Sin(b);
   double sin_a = A_div_B*sin_b;
   // If sin of a is greater than one it means a triangle cannot be
   // constructed with the given angles that have sides with the given
   // ratio.
   if(fabs(sin_a) <= 1)
   {
      double a = Asin(sin_a);
      double c = 180-a-b;
      double sin_c = Sin(c);
      double B;
      if(fabs(sin_c) > .0001)
      {
         B = C*(sin_b/sin_c);
      }
      else
      {
         // Sin of small angles approach zero causing overflow in
         // calculation. For nearly flat triangles just treat as
         // flat.
         B = C/(A_div_B+1);
      }
      // double A = C*(sin_a/sin_c);
      ok = 1;
      *courseAngle = targetAngle+a;
      *courseRange = B;
   }
   return ok;
}

答案 5 :(得分:1)

以下是我使用递归算法设计并实现预测定位问题解决方案的示例:http://www.newarteest.com/flash/targeting.html

我将不得不尝试一些其他解决方案,因为在一步计算它似乎更有效,但我想出的解决方案是估计目标位置并将结果反馈到算法中做一个新的更准确的估计,重复几次。

对于第一次估计,我“射击”目标的当前位置,然后使用三角法确定射击到达射击位置时目标的位置。然后在下一次迭代中,我在那个新位置“开火”并确定目标在这个时间的位置。经过大约4次重复后,我得到一个准确的像素。

答案 6 :(得分:1)

我刚刚攻击了这个版本以瞄准2d空间,我没有彻底测试它但它似乎工作。背后的想法是:

创建垂直于从枪口指向目标的矢量的矢量。 为了发生碰撞,目标和沿该向量(轴)的射弹的速度应该相同! 使用相当简单的余弦函数我得到了这段代码:

private Vector3 CalculateProjectileDirection(Vector3 a_MuzzlePosition, float a_ProjectileSpeed, Vector3 a_TargetPosition, Vector3 a_TargetVelocity)
{
    // make sure it's all in the horizontal plane:
    a_TargetPosition.y = 0.0f;
    a_MuzzlePosition.y = 0.0f;
    a_TargetVelocity.y = 0.0f;

    // create a normalized vector that is perpendicular to the vector pointing from the muzzle to the target's current position (a localized x-axis):
    Vector3 perpendicularVector = Vector3.Cross(a_TargetPosition - a_MuzzlePosition, -Vector3.up).normalized;

    // project the target's velocity vector onto that localized x-axis:
    Vector3 projectedTargetVelocity = Vector3.Project(a_TargetVelocity, perpendicularVector);

    // calculate the angle that the projectile velocity should make with the localized x-axis using the consine:
    float angle = Mathf.Acos(projectedTargetVelocity.magnitude / a_ProjectileSpeed) / Mathf.PI * 180;

    if (Vector3.Angle(perpendicularVector, a_TargetVelocity) > 90.0f)
    {
        angle = 180.0f - angle;
    }

    // rotate the x-axis so that is points in the desired velocity direction of the projectile:
    Vector3 returnValue = Quaternion.AngleAxis(angle, -Vector3.up) * perpendicularVector;

    // give the projectile the correct speed:
    returnValue *= a_ProjectileSpeed;

    return returnValue;
}

答案 7 :(得分:0)

我已经看到很多方法可以用数学方法解决这个问题,但这是一个与我的课程在高中时需要做的项目相关的组件,并不是这个编程课程中的每个人都有微积分的背景,甚至是向量就此而言,我创造了一种通过更多编程方法来解决这个问题的方法。交点将是准确的,尽管它可能比数学计算晚1帧。

考虑:

S = shooterPos, E = enemyPos, T = targetPos, Sr = shooter range, D = enemyDir
V = distance from E to T, P = projectile speed, Es = enemy speed

在这个问题的标准实现中[S,E,P,Es,D]都是给定的,你要解决的是找到T或射击的角度,以便你在适当的时间点击T. / p>

解决问题的方法的主要方面是将射手的范围视为包含可在任何给定时间拍摄的所有可能点的圆。该圆的半径等于:

Sr = P*time

其中时间计算为循环的迭代。

因此,为了找到敌人在给定时间迭代时行进的距离,我们创建了向量:

V = D*Es*time

现在,为了实际解决这个问题,我们想要找到一个点,在该点上,从目标(T)到射手(S)的距离小于我们的射手(Sr)的射程。这里有一些伪代码实现这个等式。

iteration = 0;
while(TargetPoint.hasNotPassedShooter)
{
    TargetPoint = EnemyPos + (EnemyMovementVector)
    if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange))
        return TargetPoint;
    iteration++
}

答案 8 :(得分:0)

我在这里创建了一个公共领域Unity C#功能:
http://ringofblades.com/Blades/Code/PredictiveAim.cs

它适用于3D,但您可以通过将Vector3替换为Vector2s并使用您选择的向下轴来重力修改这个2D,如果有重力的话。

如果理论让你感兴趣,我会在这里逐步推导数学:
http://www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php

答案 9 :(得分:0)

基本上,这里并不需要交叉概念,只要您使用射弹运动,您只需要以特定角度击中并在拍摄时进行实例化,这样您就可以获得目标的准确距离。来源,然后一旦你有距离,你可以计算出它应该射击的适当速度,以达到目标。

以下链接使概念明确并被认为有用,可能有所帮助: Projectile motion to always hit a moving target

答案 10 :(得分:0)

我从这里抓住了其中一个解决方案,但没有一个考虑到射手的动作。如果您的射手正在移动,您可能需要考虑到这一点(因为射击者的速度应该在你射击时加到子弹的速度上)。真正需要做的就是从目标的速度中减去射手的速度。因此,如果您使用上面的broofa代码(我建议使用),请更改行

  tvx = dst.vx;
  tvy = dst.vy;

  tvx = dst.vx - shooter.vx;
  tvy = dst.vy - shooter.vy;

你应该全力以赴。