2d game : fire at a moving target by predicting intersection of projectile and unit

后端 未结 11 801
庸人自扰
庸人自扰 2020-11-28 22:26

Okay, this all takes place in a nice and simple 2D world... :)

Suppose I have a static object A at position Apos, and a linearly moving object B at Bpos with bVeloci

11条回答
  •  广开言路
    2020-11-28 22:36

    I wrote an aiming subroutine for xtank a while back. I'll try to lay out how I did it.

    Disclaimer: I may have made one or more silly mistakes anywhere in here; I'm just trying to reconstruct the reasoning with my rusty math skills. However, I'll cut to the chase first, since this is a programming Q&A instead of a math class :-)

    How to do it

    It boils down to solving a quadratic equation of the form:

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

    Note that by sqr I mean square, as opposed to square root. Use the following values:

    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)
    

    Now we can look at the discriminant to determine if we have a possible solution.

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

    If the discriminant is less than 0, forget about hitting your target -- your projectile can never get there in time. Otherwise, look at two candidate solutions:

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

    Note that if disc == 0 then t1 and t2 are equal.

    If there are no other considerations such as intervening obstacles, simply choose the smaller positive value. (Negative t values would require firing backward in time to use!)

    Substitute the chosen t value back into the target's position equations to get the coordinates of the leading point you should be aiming at:

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

    Derivation

    At time T, the projectile must be a (Euclidean) distance from the cannon equal to the elapsed time multiplied by the projectile speed. This gives an equation for a circle, parametric in elapsed time.

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

    Similarly, at time T, the target has moved along its vector by time multiplied by its velocity:

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

    The projectile can hit the target when its distance from the cannon matches the projectile's distance.

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

    Wonderful! Substituting the expressions for target.X and target.Y gives

    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)
    

    Substituting the other side of the equation gives this:

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

    ... subtracting sqr(t * projectile_speed) from both sides and flipping it around:

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

    ... now resolve the results of squaring the subexpressions ...

    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
    

    ... and group similar terms ...

    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
    

    ... then combine them ...

    (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
    

    ... giving a standard quadratic equation in t. Finding the positive real zeros of this equation gives the (zero, one, or two) possible hit locations, which can be done with the quadratic formula:

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

提交回复
热议问题