in my 2d game I would like to create an intelligent bot who can \"shoot\" to the player. Suppose I can pass to my bot:
actual xEnemy, yEnemy
also enemy speed a
Notation: I write vectors in capital letters, scalars in lower case, and ∠V for the angle that the vector V makes with the x-axis. (Which you can compute with the function atan2
in many languages.)
The simplest case is a stationary shooter which can rotate instantly.
Let the target be at the position A and moving with velocity VA, and the shooter be stationary at the position B and can fire bullets with speed s. Let the shooter fire at time 0. The bullet hits at time t such that |A − B + t VA| = t s. This is a straightforward quadratic equation in t, which you should be easily able to solve (or determine that there is no solution). Having determined t, you can now work out the firing angle, which is just ∠(A − B + t VA).
Now suppose that the shooter is not stationary but has constant velocity VB. (I'm supposing Newtonian relativity here, i.e. the bullet velocity is added to the shooter's velocity.)
It's still a straightforward quadratic equation to work out the time to hit: |A − B + t(VA − VB)| = t s. In this case the firing angle is ∠(A − B + t (VA − VB)).
What if the shooter waits until time u before firing? Then the bullet hits the target when |A − B + t(VA − VB)| = (t − u) s. The firing angle is still ∠(A − B + t(VA − VB)).
Now for your problem. Suppose that the shooter can complete a half rotation in time r. Then it can certainly fire at time r. (Basically: work out the necessary firing angle, if any, for a shot at time r, as described above, rotate to that angle, stop, wait until time r, then fire.)
But you probably want to know the earliest time at which the shooter can fire. Here's where you probably want to use successive approximation to find it. (Sketch of algorithm: Can you fire at time 0? No. Can you fire at time r? Yes. Can you fire at time ½ r? No. etc.)
What you're asking is non-trivial. I'll start with the simplest solution and expand on that.
First, assume that both you and your enemy are stationary. You need to compute the angle between you and your enemy, rotate your weapon to point at the enemy, and then fire. Use your favorite search engine to find a description of how to find the angle between two points on a plane (you did say 2D).
Once you write code that can do the above, move on to:
Your enemy is moving in a constant direction at a constant velocity. You are still stationary. This is a surprisingly difficult problem. To simplify, we'll assume that you can aim your weapon instantaneously.
If you know where you and the enemy are, and the speed and direction of the enemy, then you can determine the enemy's position (and, consequently, his distance and direction from you) at any time.
You know how fast your projectile can travel. So if you draw a line from your current position to intercept any position on the enemy's expected path of travel, you can determine how long it will take your projectile to hit the enemy. The key, then, is to find the point on the enemy's path that, were you to fire the projectile immediately, it would intersect the enemy at the proper time. This typically requires successive approximations.
If you can't rotate your weapon instantaneously, then the problem becomes more difficult because the time it takes to rotate your weapon to point at the enemy depends on how fast and in what direction the enemy is traveling. More approximations are required.
Things get even more involved when you and the enemy are both moving, although it's possible to construct the math so that you "hold yourself still". That is, do a transformation on the enemy's velocity and trajectory to reflect how the enemy is moving in relation to you. The math then becomes identical to the case of you being stationary.
The math itself is at most elementary trigonometry. You need to know how to compute the distance between two points, the distance between a line and a point, the angle between two points, compute points on a line given a starting point and direction, and how to rotate about an arbitrary point. All of those are well-known problems that have many good examples online. You'll have to do a little research to find them, though.
Probably your best bet is to find a good computer graphics tutorial.
The solution to this problem for the "simple" case, where the gun does NOT need to rotate has been asked and answered several times. I found the best answer to be in this post (Jeffrey Hantin has a good explanation).
As Jim Mischel said, the answer to the rotating case is not even a little trivial. This is because the amount of rotation you have to do is a function of the intercept position of the target (where the collision happens). I do not know if there is a clean closed form solution to this (seems unlikely given the formulas), but I was able to solve it by working backwards and iterating to a solution.
The basic idea works like this:
Assume your "entity" will rotate from its current facing position to face the intercept position and then fire immediately.
In the simple case, you could know from the discriminant of the quadratic if you had 0, 1, or 2 solutions and pick the best one. I don't think you can guarantee that here, but you can bound the time range you are willing to search over and how many iterations you will search. This works very well in practice.
Since I could not find the solution to this on the web, I am going to post mine. I wrote a function to handle this problem specifically. I have a blog entry on the entire calculation here. There is also a nice video showing it here.
The Code:
/* Calculate the future position of a moving target so that
* a turret can turn to face the position and fire a projectile.
*
* This algorithm works by "guessing" an intial time of impact
* for the projectile 0.5*(tMin + tMax). It then calculates
* the position of the target at that time and computes what the
* time for the turret to rotate to that position (tRot0) and
* the flight time of the projectile (tFlight). The algorithms
* drives the difference between tImpact and (tFlight + tRot) to
* zero using a binary search.
*
* The "solution" returned by the algorithm is the impact
* location. The shooter should rotate towards this
* position and fire immediately.
*
* The algorithm will fail (and return false) under the
* following conditions:
* 1. The target is out of range. It is possible that the
* target is out of range only for a short time but in
* range the rest of the time, but this seems like an
* unnecessary edge case. The turret is assumed to
* "react" by checking range first, then plot to shoot.
* 2. The target is heading away from the shooter too fast
* for the projectile to reach it before tMax.
* 3. The solution cannot be reached in the number of steps
* allocated to the algorithm. This seems very unlikely
* since the default value is 40 steps.
*
* This algorithm uses a call to sqrt and atan2, so it
* should NOT be run continuously.
*
* On the other hand, nominal runs show convergence usually
* in about 7 steps, so this may be a good 'do a step per
* frame' calculation target.
*
*/
bool CalculateInterceptShotPosition(const Vec2& pShooter,
const Vec2& vShooter,
const Vec2& pSFacing0,
const Vec2& pTarget0,
const Vec2& vTarget,
float64 sProjectile,
float64 wShooter,
float64 maxDist,
Vec2& solution,
float64 tMax = 4.0,
float64 tMin = 0.0
)
{
cout << "----------------------------------------------" << endl;
cout << " Starting Calculation [" << tMin << "," << tMax << "]" << endl;
cout << "----------------------------------------------" << endl;
float64 tImpact = (tMin + tMax)/2;
float64 tImpactLast = tImpact;
// Tolerance in seconds
float64 SOLUTION_TOLERANCE_SECONDS = 0.01;
const int MAX_STEPS = 40;
for(int idx = 0; idx < MAX_STEPS; idx++)
{
// Calculate the position of the target at time tImpact.
Vec2 pTarget = pTarget0 + tImpact*vTarget;
// Calulate the angle between the shooter and the target
// when the impact occurs.
Vec2 toTarget = pTarget - pShooter;
float64 dist = toTarget.Length();
Vec2 pSFacing = (pTarget - pShooter);
float64 pShootRots = pSFacing.AngleRads();
float64 tRot = fabs(pShootRots)/wShooter;
float64 tFlight = dist/sProjectile;
float64 tShot = tImpact - (tRot + tFlight);
cout << "Iteration: " << idx
<< " tMin: " << tMin
<< " tMax: " << tMax
<< " tShot: " << tShot
<< " tImpact: " << tImpact
<< " tRot: " << tRot
<< " tFlight: " << tFlight
<< " Impact: " << pTarget.ToString()
<< endl;
if(dist >= maxDist)
{
cout << "FAIL: TARGET OUT OF RANGE (" << dist << "m >= " << maxDist << "m)" << endl;
return false;
}
tImpactLast = tImpact;
if(tShot > 0.0)
{
tMax = tImpact;
tImpact = (tMin + tMax)/2;
}
else
{
tMin = tImpact;
tImpact = (tMin + tMax)/2;
}
if(fabs(tImpact - tImpactLast) < SOLUTION_TOLERANCE_SECONDS)
{ // WE HAVE A WINNER!!!
solution = pTarget;
return true;
}
}
return false;
}