Shooting At Stuff
You never shoot directly at a moving target. You shoot at where the target is going to be when your shot gets there. Millions of years of biology make it easy for predators. Turns out it's pretty easy for computers as well...if you know the math.
- Non-Rotating Shooter
- Rotating Shooter
- The position of the shooter when the projectile will be launched: \(\vec{P_s}\)
- The position of the target when the shooter will launch the projectile (i.e. now): \(\vec{P_T^0}\)
- The speed at which your projectiles travel: \(S_b\)
- The velocity of the target, \(\vec{v_T}\)
The Problem Space
Consider the image below:
(1) \(\vec{P_T^1} = \vec{P_T^0} + \vec{v_T} * t_B\)
Since the projectile will have traveled for the same amount of time, it will have moved from \(\vec{P_s}\) to \(\vec{P_T^1}\) as well. In that time, it will have moved a distance of \(S_B x t_B\). Since we are talking about vector quantities here, we can write this as:
\(\mid\vec{P_T^1}-\vec{P_s}\mid = S_b * t_B\)
If we square both sides and break it into components to get rid of the absolute value:
(2) \((P_{Tx}^1 - P_{Sx})^2 +(P_{Ty}^1 - P_{Sy})^2 = S_b^2 * t_B^2\)
Breaking (1) into components as well and substituting back into (2) for the value of \(P_{Tx}^1\) and \(P_{Ty}^1\), we get the following:
\((P_{T0x} - P_{Sx} + v_{Tx}t_B)^2 + (P_{T0y} - P_{Sy} + v_{Ty}t_B)^2 = S_b^2 * t_B^2\)
For the sake of simplicity, we going to redefine:
\(P_T^0 - P_s = R \)(this is a constant)
After some algebra, this gives us the final equation:
\(t_B^2(v_{Tx}^2 + v_{Ty}^2-S_B^2) + t_B(2*R_x*v_{Tx} + 2*R_y*v_{Ty}) + (R_x^2 + R_y^2) = 0\)
This is a quadratic in \(t_B\):
\(t_b = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}\)
where:\( a =v_{Tx}^2 + v_y^2-S_B^2\)
\( b =2(R_x*v_{Tx} + R_y*v_{Ty})\)
\( c = R_x^2 + R_y^2\)
You can test the discriminant, \(b^2-4ac\):
< 0 \(\Rightarrow\) No Solution.
= 0 \(\Rightarrow\) One solution.
> 0 \(\Rightarrow\) Two solutions, pick the lowest positive value of \(t_B\).
Once you have solved the quadratic for \(t_B\), you can then substitute it back into (1) and calculate the intercept position, \(\vec{P_T^1}\).
The Code
Putting this together and covering some edge cases: /* Calculate the future position of a moving target so that * a projectile launched immediately can intercept (collide) * with it. * * Some situations where this might be useful for an AI to * make this calculation. * * 1. Shooting a projectile at a moving target. * 2. Launching a football or soccer ball to a player. * 3. Figuring out the best position to jump towards in * a platform game. * * * The output value, solution, is the position that the * intercept will occur at and the location that the * projectile should be launched towards. * * The function will return false if a solution cannot * be found. Consider the case of a target moving away * from the shooter faster than the speed of the * projectile and you will see at least one case where * this calculation may fail. */ bool CalculateInterceptShotPosition(const Vec2& pShooter, const Vec2& pTarget0, const Vec2& vTarget, float64 sProjectile, Vec2& solution ) { // This formulation uses the quadratic equation to solve // the intercept position. Vec2 R = pTarget0 - pShooter; float64 a = vTarget.x*vTarget.x + vTarget.y*vTarget.y - sProjectile*sProjectile; float64 b = 2*(R.x*vTarget.x + R.y*vTarget.y); float64 c = R.x*R.x + R.y*R.y; float64 tBullet = 0; // If the target and the shooter have already collided, don't bother. if(R.LengthSquared() < 2*DBL_MIN) { return false; } // If the squared velocity of the target and the bullet are the same, the equation // collapses to tBullet*b = -c. If they are REALLY close to each other (float tol), // you could get some weirdness here. Do some "is it close" checking? if(fabs(a) < 2*DBL_MIN) { // If the b value is 0, we can't get a solution. if(fabs(b) < 2*DBL_MIN) { return false; } tBullet = -c/b; } else { // Calculate the discriminant to figure out how many solutions there are. float64 discriminant = b*b - 4 * a * c; if(discriminant < 0) { // All solutions are complex. return false; } if (discriminant > 0) { // Two solutions. Pick the smaller one. // Calculate the quadratic. float64 quad = sqrt(discriminant); float64 tBullet1 = (-b + quad)/(2*a); float64 tBullet2 = (-b - quad)/(2*a); if((tBullet1 < 0) && (tBullet2 < 0)) { // This would be really odd. // Both times are negative. return false; } else if(tBullet2 < 0 && tBullet1 >= 0) { // One negative, one positive. tBullet = tBullet1; } else if(tBullet1 < 0 && tBullet2 >= 0) { // One negative, one positive. tBullet = tBullet2; } else if(tBullet1 < tBullet2) { // First less than second tBullet = tBullet1; } else { // Only choice left tBullet = tBullet2; } } else { tBullet = -b / (2*a); } } // If the time is negative, we can't get there from here. if(tBullet < 0) { return false; } // Calculate the intercept position. solution = pTarget0 + tBullet*vTarget; return true; } I have posted a working solution, with a simulation of using the above function and which you can tinker with, on github. Article Update Log
17 Jan 2015: Fixed hole in pos/neg check. 3 Nov 2014: Cleaned up some text. 1 Nov 2014: Added code example. 1 Nov 2014: Update discriminant description. 28 Oct 2014: Initial ReleaseRelated Tutorials
A Day As A Game Designer
The fabled game designer. The man, the myth, the legend behind the game. An inspiration to young creatives who wish to …
Kai Wüest
So You Want to be a Game Developer?
Battletech tools developer Chris Eck describes his journey and advice for getting a job in the industry.
A Quick Guide to Gamification and Gamification Careers
If you find yourself slightly stumped over the ever-growing buzz around gamification, and unsure whether there is any o…
Sophie Jackson
Raph Koster's Postmortems: Una Carrera
This excerpt from Raph Koster's Postmortems is an adaptation from a speech he delivered at GameDay Peru in early 2015. …
Raph Koster
Negotiating Sign-On Bonuses in the Games Industry
A quick blurb on signing bonuses in the games industry and how to approach asking for one.
A LinkedIn Profile for Job Hunting and Networking
Marc Mencher is founder and CEO of GameRecruiter and author of Get in the Game!, an instructional book on building a ca…
Discussion
More from James Wucher
Visual Tools For Debugging Games
How much of your time do you spend writing code? How much of your time you spend fixing code?Which would you rather be…
Estimating Effort for Small Projects
How much work will it take? How much have you finished? When will it be done? If you have to answer these questions,…
PID Control of Physics Bodies
Once you bite the bullet and decide to use a physics engine for your game pieces, you are going to have to come to term…
Discussion