9

I saw this question: Predicting enemy position in order to have an object lead its target. My situation is a little different though.

My target moves, and the shooter moves. Also, the shooter's velocity is added to the bullets' velocities, i.e. bullets fired while sliding to the right will have a greater velocity toward the right.

What I'm trying to do is to get the enemy to be able to determine where they need to shoot in order to hit the player. Using the linked SO solution, unless the player and enemy are stationary, the velocity difference will cause a miss. How can I prevent that?


Here is the solution presented from the stack overflow answer. 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.

Azaral
  • 376
  • 3
  • 9
  • 3
    can you please attempt to condense your question. I know you think you need to give really detailed explanations, but really I got about half way through, and felt that I was reading a text book – gardian06 May 03 '12 at 07:15
  • 1
    Can you please use something like sq() instead of sqr()? It makes it really confusing to read. – sam hocevar May 03 '12 at 08:49
  • @gardian06 The code without any explanation involved is at the end of the question. – Azaral May 03 '12 at 10:41
  • @Sam Hocevar I replaced all of my variable * variable with std::pow(variable,2). – Azaral May 03 '12 at 10:41
  • 1
    If it works when you are stationary and the target moves, but not when you both move, then subtract your velocity from the target's one to get the relative velocity and use that. – George Duckett May 03 '12 at 11:55
  • @GeorgeDuckett it doesn't work period. I'm already doing as you say. If both targets are stationary it will shoot of in a random direction that is close to being + 90 degrees of where the target actually is.

    Example: playerVelocityX - enemyVelocityX is used whenever the original answer calls for the target's velocity.

    – Azaral May 03 '12 at 12:03
  • is there any way you could give us something that compiles that obays http://sscce.org/. even with just console output? – gardian06 May 03 '12 at 15:19
  • @gardian06 If you mean the source code, I could do that. It is not very large. I think that would be easiest for me to do. I can set it up to take you straight into the meat of the game facing the enemy that is suppose to shoot at you (already tested this). If you want to compile it you would need SDL 1.2 and SDL_Image extension. I am also using OpenGL. I would also point out the function(s) in question. – Azaral May 03 '12 at 16:20
  • I mean an example program that uses the method in question, and still demonstrates the behavior. if you think the problem is mathematical then give the problem to us without graphics. just printing values to the console (showing specific calculations that might be the cause) sometimes looking at the values you can see what is going on that graphics just covers up. – gardian06 May 03 '12 at 16:48
  • OK. I'll see about that, I'm fiddling with my code right now to see if I can figure it out. – Azaral May 03 '12 at 17:00
  • Why are you writing so cryptic code and don't just use some great vector/matrix library like glm? -1 Sorry. – arul May 03 '12 at 17:20
  • @arul Sorry I'm not using something I've never heard of and not doing things the way you want them done. My motto is get things working, then clean it up. I'd rather not have to spend time learning another library for math which would take up time getting my game playable. Once I get it playable I'll worry about cleaning things up. – Azaral May 03 '12 at 17:34
  • where do you address the limited range (mathematical) of atan(), and correct for the missing segments of that range? this can account for as much as being off by anywhere from 90 to 180 degrees – gardian06 May 03 '12 at 17:37
  • @gardian06 I'm using atan2, doesn't that already account for that? It works for all my other vector-to-degree calculations just fine. – Azaral May 03 '12 at 17:47
  • @gardian I made what you recommend and I must thank you. There are some weird things going on now that I can see all of the values. – Azaral May 03 '12 at 18:04
  • Which of the objects would be effected by gravity? – aaaaaaaaaaaa May 03 '12 at 18:14
  • @eBusiness There is no gravity, this is in space. – Azaral May 03 '12 at 18:16
  • It would be both much easier to get done and to read and analyze ... consider the verbosity of std::sqrt(std::pow(bulletVelocityX,2) + std::pow(bulletVelocityY, 2); compared to just length(bulletVelocity) ? – arul May 03 '12 at 18:38
  • @arul It would be easier, but that still doesn't preclude the fact that I can't use something I've never heard of.

    And if I sound aggressive, I apologize. This problem is really irritating me.

    – Azaral May 03 '12 at 18:40
  • OK, I'm getting no where with this. I made a simple console output program that has all the math I'm using. If anyone wants to look at it and point out what I'm doing wrong you're more than welcome to the code. I need to walk away from this or I'm going to break things that should not be broken. – Azaral May 03 '12 at 18:49
  • @SamHocevar I've implemented your answer, which I'm sure should work, however, inside my game it's not. I've updated my question to reflect my new code inside my game. – Azaral May 05 '12 at 12:33
  • The only thing that appears wrong is that shouldShoot = true is outside the t0 >= 0 test. I think it's now beyond the scope of this specific question and you're up for some step-by-step or printf-debugging. – sam hocevar May 05 '12 at 13:54
  • @SamHocevar OK. I have the shouldShoot outside the test for testing. I wanted to see if it was even going past disc >= 0. Thanks for looking at it again though, I really appreciate your time and effort! I'll not bring this up again here because I agree the original problem is solved. – Azaral May 05 '12 at 14:01
  • @SamHocevar One last update and I leave you alone. I got it to work and it works perfectly. I had set a value incorrectly earlier in my code that then gets passed later to the ship AI routine that had the ship thinking the player's velocity was equal to its position....

    Thanks again, really appreciate your help.

    – Azaral May 05 '12 at 14:23

5 Answers5

5

Okay, let's put some sanity into this. I am afraid you are not making it easy at all, your code does not compile, is inconsistent with regards to variable names (playerVelocityX becomes playerXvelocity after a few lines? what is xVelocity?) and is too verbose. It is basically impossible to debug lest you put considerable effort into it.

So, here are the things to fix:

Bullet speed

The bullet speed must be 30, period. There is no need for the computations you are doing: the change of the frame of reference is precisely there to avoid the complexity. You only add the enemy's velocity after you found a solution, when you go back to the main reference frame.

Solution validity

You are not checking that the time solution is positive.

Numerous coding errors

You are testing time1 and time2 but always using time1 in the results.

You do playerXvelocity - yVelocity which is inconsistent.

You are doing / 2 * a instead of / (2.f * a). This is the worst error and it's why everything is going wrong.

You compute shootx and shooty as the final position of the bullet, whereas what you are looking for is the velocity of the bullet.

Fixed code

float const bulletSpeed = 30.f;
/* Relative player position */
float const dx = playerX - enemyX;
float const dy = playerY - enemyY;
/* Relative player velocity */
float const vx = playerVelocityX - enemyVelocityX;
float const vy = playerVelocityY - enemyVelocityY;

float const a = vx * vx + vy * vy - bulletSpeed * bulletSpeed;
float const b = 2.f * (vx * dx + vy * dy);
float const c = dx * dx + dy * dy;
float const disc = b * b - 4.f * a * c;

shouldShoot = false;

if (disc >= 0.f)
{
    float t0 = (-b - std::sqrt(disc)) / (2.f * a);
    float t1 = (-b + std::sqrt(disc)) / (2.f * a);
    /* If t0 is negative, or t1 is a better solution, use t1 */
    if (t0 < 0.f || (t1 < t0 && t1 >= 0.f))
        t0 = t1;
    if (t0 >= 0.f)
    {
        /* Compute the ship's heading */
        shootx = vx + dx / t0;
        shooty = vy + dy / t0;
        heading = std::atan2(shooty, shootx) * RAD2DEGREE;
        /* Compute the bullet's velocity by adding the enemy's velocity */
        bulletVelocityX = shootx + enemyVelocityX;
        bulletVelocityY = shooty + enemyVelocityY;

        shouldShoot = true;
    }
}
sam hocevar
  • 23,811
  • 2
  • 63
  • 95
  • yeah i retyped my code and noticed i messed a few things up. It isn't that inconsistent in my actual code I promise lol! I did notice the "You are doing / 2 * a instead of / (2.f * a)." last night and fixed that. I am going to give your code a try.

    I came here after about four hours of trying to figure this out and was in quite an irritated state and I think that is why I typed the code so horribly.

    – Azaral May 04 '12 at 12:42
  • I tried this in a simple console output and it isn't working. I copy and pasted it into a fresh project. I added the player and enemy variables that are not declared from your code. Inside the if(t0>=0.f) area I added calculating player x,y and bullet x,y after the line should shoot = true;

    playerX += playerVelocityX*t0; playerY += playerVelocityY*t0; float bulletX = ((cos(heading * DEGREE2RAD)*bulletSpeed + enemyVelocityX) * t0) + enemyX; float bulletY = ((sin(heading * DEGREE2RAD) * bulletSpeed + enemyVelocityY) * t0) + enemyY;

    Output is P=(84.8069,93.0386) E=(88.3793,105.132)

    – Azaral May 04 '12 at 13:20
  • At this point I wouldn't be surprised if I'm just an idiot. I can accept that. – Azaral May 04 '12 at 13:21
  • In the above example the original data is: playerX = 50, playerY = 100, playerVelocityX = 20, playerVelocityY = -4,

    enemyX = 145, enemyY = 67, enemyVelocityX = -5, enemyVelocityY = 10.

    – Azaral May 04 '12 at 13:22
  • Do not use bulletSpeed at this point. Replace cos(heading * DEGREE2RAD) * bulletSpeed with shootx. Same for shooty. The reason is because the enemy's velocity is added to the bullet's velocity, its speed is no longer exactly bulletSpeed. In fact you do not need the heading variable at all, it's just helpful for debugging. – sam hocevar May 04 '12 at 13:32
  • @Azaral check out this code I used, with the values you gave above: http://ideone.com/6E9KU The resulting values are printed at the bottom. – sam hocevar May 04 '12 at 13:36
  • OK, doing as you said works. I had to replace cos(heading*DEGREE2RAD) * bulletSpeed + enemyVelocityX with just startx, and likewise in the y's. I actually need the heading variable. What happens is this part determines where I need to point the ship in order to hit the target. Later in the code if the enemy thinks it should fire, it fires of a shot at the current heading. When the weapon is fired, it's x and y velocities are calculated, it's origin is set, and it flies away. It's x velocity is cos(heading * DEGREE2RAD) * speed + enemyXvel. Origin is the enemy's location. – Azaral May 04 '12 at 13:53
  • @Azaral: then you should compute heading without adding enemyVelocityX. It is important to understand that the heading of the ship and the direction of the bullet will be slightly different. I updated the answer to reflect that. Note also if an angle is computed through a=atan2(Y,X) then there is no need to compute cos(a)*length and sin(a)*length since that's precisely what X and Y are. – sam hocevar May 04 '12 at 14:03
  • OK, your new solution works with my firing method. I'm going to test this in the game now. I GREATLY appreciate your help. You have no idea the mental anguish this has caused me lol. – Azaral May 04 '12 at 14:14
  • The heading is passed to the weapon's shoot function. The weapons take care of shooting themselves when they are told to shoot. I pass them the current heading, the current x and y velocity, and the current x,y position. It calculates the projectiles x,y velocity based on the heading value and the weapons force vs the projectiles mass and then adds the ships x and y velocity and sets the projectile's x,y position to the values of the ship. The projectiles then take care of updating them selves by adding their x,y velocity to their x,y position every update.

    Heading is used for rendering too

    – Azaral May 04 '12 at 14:38
3

Having a moving shooter is identical to having a stationary shooter. Simply subtract the shooters movement vector from the targets movement vector.

Target [-5,0]
Shooter [4,1]
Target - Shooter = [-5,0] - [4,1] = [-9,-1]

Calculate the firing vector/initial angle, then add the targets movement vector to the bullet like normal.

Daniel Carlsson
  • 2,371
  • 12
  • 17
  • 1
    If you read my question and looked at the code, you would see I'm already doing this. – Azaral May 03 '12 at 16:18
  • @azaral yes, and no. you are doing this to get one of your values, and then doing stuff with velocity – gardian06 May 03 '12 at 16:49
  • @gardian06 can you be more specific? – Azaral May 03 '12 at 16:59
  • @Azaral I was under the impression that you had a working algorithm for when the shooter was stationary, as was described in the link you provided. If its not working in that case there is a bug in that algorithm that needs sorting before you need to worry about a moving shooter (not that the difference is big, but simpler is easier to debug) – Daniel Carlsson May 03 '12 at 21:59
  • @DanielCarlsson you know that's a good point I never thought of. I just assumed the algorithm worked. – Azaral May 03 '12 at 22:59
3

Here's an example where I devised and implemented a solution to the problem of predictive targeting using a recursive algorithm: http://www.newarteest.com/flash/targeting.html (I had a stationary shooter but the same approach would work for a moving shooter)

I'll have to try out some of the other solutions presented because it seems more efficient to calculate it in one step, but the solution I came up with was to estimate the target position and feed that result back into the algorithm to make a new more accurate estimate, repeating several times.

For the first estimate I "fire" at the target's current position and then use trigonometry to determine where the target will be when the shot reaches the position fired at. Then in the next iteration I "fire" at that new position and determine where the target will be this time. After about a few repeats I get within a pixel of accuracy.


because browsers no longer support Flash player, I ported my example to Unity WebGL: https://jhocking.itch.io/moving-target-aiming

The core algorithm of interest from that demo is:

var target = missile.position;
for (var i = 0; i < 3; i++) {
    target = CalculateRefinedTarget(target);
}

...

private Vector3 CalculateRefinedTarget(Vector3 curTarget) {
// calculate how long the shot takes
var shotDist = Vector3.Distance(cannon.position, curTarget);
var time = shotDist / shotSpeed;

// calculate where the missile will have moved to
var missileDist = time * missileSpeed;
var radians = missile.eulerAngles.z * Mathf.PI / 180;
var dX = missileDist * Mathf.Sin(radians);
var dY = missileDist * Mathf.Cos(radians);
var newTarget = missile.position + new Vector3(-dX, dY, 0);

return newTarget;

}

jhocking
  • 15,786
  • 2
  • 43
  • 59
1

As you are only working with 2D physics (no Z velocity), this problem can be greatly simplified. The easy way to do this is to stop thinking about both source and target moving relative to world co-ordinates and to just think of the target moving relative to the source (and keep the source stationary).

Vector TargetInitialPosition = new Vector ( target.X - source.X ,
                                            target.Y - source.Y );
Vector TargetApparentVelocity = new Vector( target.velocityX - source.velocityX ,
                                            target.velocityY - source.velocityY );

Normally, a bullet's velocity would be much higher than the shooter's velocity so it is usually assumed that the bullet is independent but there are occasions where this is not true, such as firing out of a helicopter or fighter jet.

Then we need to work out the bullet velocity:

// Your directional vector MUST be normalized...
Vector BulletVelocity = new Vector( source.directionX * Bullet::StaticSpeed + source.velocityX ,
                                     source.directionY * Bullet::StaticSpeed + source.velocityY );

The problem you're having is that the target has moved by the time the bullet reaches them.

TargetPosition = TargetInitialPosition + TargetApparentVelocity * t
BulletPosition = BulletInitialPosition + BulletVelocity * t
               = BulletVelocity * t

and solve for TargetPosition == BulletPosition because then the bullet would have hit the target. Now you have three unknowns and only two equations. We can remove 't' by taking the first order derivative:

TargetInitialPosition + ( TargetApparentVelocity - BulletVelocity ) * t == 0
dV / dt = TargetApparentVelocity - BulletVelocity

Now to hit the target, you'd want dV/dt == -TargetInitialPosition * k. The constant has to be the same in the X and Y coordinates and is the number of seconds the bullet will take to hit the target.

TargetApparentVelocity.X - BulletVelocity.X == k * -TargetInitialPosition.X
k = ( BulletVelocity.X - TargetApparentVelocity.X ) / TargetInitialPosition.X
----------------------
TargetApparentVelocity.Y - BulletVelocity.Y == k * -TargetInitialPosition.Y
k = ( BulletVelocity.Y - TargetApparentVelocity.Y ) / TargetInitialPosition.Y

make them equal:

( BulletVelocity.X - TargetApparentVelocity.X ) / TargetInitialPosition.X
= ( BulletVelocity.Y - TargetApparentVelocity.Y ) / TargetInitialPosition.Y

or to expand the variables:

( source.directionX * Bullet::StaticSpeed + source.velocityX - target.velocityX + source.velocityX ) / ( target.X - source.X )
 == ( source.directionY * Bullet::StaticSpeed + source.velocityY - target.velocityY + source.velocityY ) / ( target.Y - source.Y )

Then algebra gives you your final equation:

source.directionY = ( target.velocityY * ( source.X - target.X ) - 2 * source.velocityY * ( source.X - target.X ) + ( Bullet::Speed * source.directionX + 2 * source.velocityX - target.velocityX ) * ( source.Y - target.Y ) ) / ( Bullet::Speed * ( source.X - target.X ) )

The next part is messy and it's up to you how you want to implement it in your code, but we just substitute this in and normalize the vector.

sqrt( source.directionX ^ 2 + source.directionY ^ 2 ) == 1

You end up with an equation with just one unknown (source.directionX), and you can solve it for directionX then substitute back in to get directionY.

I haven't tested any of this code and feel free to point out any methematical misstakes I've made, but the theory should be sound :).

Good luck.

  • Yes it is only 2D. Reading now. – Azaral May 03 '12 at 22:59
  • Why are you adding the source velocity to the bullet's velocity? – sam hocevar May 03 '12 at 23:23
  • @SamHocevar That's how physics works. A bullet fired from a gun starts out with a velocity equal to the gun that fired it. It then adds the vector generated by the firing action to this vector. The action is taking place in space so there is no air to change the bullet's velocity. If you fire a gun in space while moving to your right, it will always be in front of you until you change your velocity. – Azaral May 03 '12 at 23:31
  • OK, I'm failing to make sense of this to be able to put it into code. I feel like such an idiot right now lol. – Azaral May 04 '12 at 01:08
  • 1
    Sorry, it works with examples, it's just hard to abstract it to the general case. Everything down to the (dV / dt = TargetApparentVelocity - BulletVelocity) is theory and the only code you'll need is this final few boxes. The algebra I came up with is way to complicated, but Mathematica was able to solve it. The reason we take the first order derivative is because you don't care how long it takes for the bullet to hit, you just want it to hit. – tyler.daniels May 04 '12 at 02:22
  • 1
    @Azaral that is not how physics work. In a uniformly moving reference frame you need to subtract the source velocity from all velocities, including the bullet's. If you use the apparent velocity for the target, you need to use the apparent velocity for the bullet, too. – sam hocevar May 04 '12 at 07:18
  • the final equation how do I get (source.directionX) if I don't know what it is? that sqrt to equal 1 below doesn't seem to help anything I can't plug in source.directionX or source.directionY since I don't know either of them at this point – SSpoke Jan 03 '14 at 05:34
0

This is just a 3D geometry problem.

First you need the relative position and the relative velocity of the shooter and the target:

Pos = TargetPos - ShooterPos
Vel = TargetVel - ShooterVel

Then you need to solve the equation:

Pos + t * Vel = t * FireSpeed * [x , +-sqrt(1-x^2)]

For t and x.

That makes:

x = ( PosX + t * VelX ) / ( t * FireSpeed )

( PosY + t * VelY ) / ( t * FireSpeed ) = +-sqrt(1 - (( PosX + t * VelX ) / ( t * FireSpeed ))^2 )
=>
( PosY + t * VelY )^2 / ( t * FireSpeed )^2 = 1 - (( PosX + t * VelX ) / ( t * FireSpeed ))^2
=>
( PosY + t * VelY )^2 + ( PosX + t * VelX )^2 = ( t * FireSpeed )^2
<=>
( Dot(Vel,Vel) - FireSpeed^2 ) * t^2 + 2 * Dot(Vel,Pos) * t + Dot(Pos,Pos) = 0

The last of those equations is a simple quadratic equation, which you should solve. For each positive result you insert the result into the equation:

FireVector = Vel + Pos / t

This should give you every possible fire vector, where t is the time the shot will take.

Note that if FireSpeed is greater than the magnitude of Vel there will be just one solution, but if FireSpeed is smaller there may be two solutions or none at all, or in special cases just a single double solution.

Edit: Better do the maths right or this answer won't be much good.

aaaaaaaaaaaa
  • 8,892
  • 1
  • 21
  • 35