- Home /
Formula to calculate a position to fire at?
Edit: Is there a formula that calculates the position a bullet would meet an enemy depending on the bullet's speed and the enemy's speed and direction?
In example: Let's take a 2-dimensional grid (x,y) and there is a tower at (1,1) and a enemy at (4,2), the enemy lies within the tower's range so the tower fires a bullet. The bullet travels at a speed of 3 (Units/Sec). The enemy is traveling at a speed of 2 (Units/Sec) at a 52 degree angle... Where would the tower have to shoot the bullet in order to hit the enemy at it's current velocity, while taking into consideration the bullet's speed?
Answer by Bunny83 · Aug 11, 2012 at 02:46 AM
Ok, I finally converted my CalculateInterceptCourse function from c++ to c#, simplified it a bit and fixed a problem so it always picks the best course when there are two available.
public static Vector3 CalculateInterceptCourse(Vector3 aTargetPos, Vector3 aTargetSpeed, Vector3 aInterceptorPos, float aInterceptorSpeed)
{
Vector3 targetDir = aTargetPos - aInterceptorPos;
float iSpeed2 = aInterceptorSpeed * aInterceptorSpeed;
float tSpeed2 = aTargetSpeed.sqrMagnitude;
float fDot1 = Vector3.Dot(targetDir, aTargetSpeed);
float targetDist2 = targetDir.sqrMagnitude;
float d = (fDot1 * fDot1) - targetDist2 * (tSpeed2 - iSpeed2);
if (d < 0.1f) // negative == no possible course because the interceptor isn't fast enough
return Vector3.zero;
float sqrt = Mathf.Sqrt(d);
float S1 = (-fDot1 - sqrt) / targetDist2;
float S2 = (-fDot1 + sqrt) / targetDist2;
if (S1 < 0.0001f)
{
if (S2 < 0.0001f)
return Vector3.zero;
else
return (S2) * targetDir + aTargetSpeed;
}
else if (S2 < 0.0001f)
return (S1) * targetDir + aTargetSpeed;
else if (S1 < S2)
return (S2) * targetDir + aTargetSpeed;
else
return (S1) * targetDir + aTargetSpeed;
}
Additionally here's another function which might be useful: FindClosestPointOfApproach
public static float FindClosestPointOfApproach(Vector3 aPos1, Vector3 aSpeed1, Vector3 aPos2, Vector3 aSpeed2)
{
Vector3 PVec = aPos1 - aPos2;
Vector3 SVec = aSpeed1 - aSpeed2;
float d = SVec.sqrMagnitude;
// if d is 0 then the distance between Pos1 and Pos2 is never changing
// so there is no point of closest approach... return 0
// 0 means the closest approach is now!
if (d >= -0.0001f && d <= 0.0002f)
return 0.0f;
return (-Vector3.Dot(PVec, SVec) / d);
}
I've made a small test application which shows the difference between the "true" interception point / course and the approximation of cassius. You can change the speed of the target and the speed of your bullet. When you have at least a 5 times higher bulletspeed the result of the simple prediction is enough.
Usually the speeds don't differ that much. In Quakelife for example the player's runspeed is 320 and the rocketspeed is 1000 --> x3.125 The original Quake3 had even less: rocketspeed 800 --> x2.5 On top of that in Quake it's possible to move even faster than the rocket can fly ;)
edit
I've exported a package of the sample application. The script is quite messy. Most stuff is just there for the visualization or to distinguish the two different methods ;)
I like your application, and I also want to mention, u shouldn't worry about the path direction changing, because it moves in a straight line from waypoint to waypoint... so it will work! But I have a question about the code... I can't get it to work... I've never dealt with creating a Vector3 that takes arguments... how do I do it, I keep getting an error
It's not working the way it did on your application :(
Edit: Do you think you'd be able to upload your project so I can see how you set everything up please?
I've edited my answer and added a package which contains the sample scene. Good luck ;)
@Bunny83 Right-on. I see what you're saying about this script vs $$anonymous$$e. I guess the way my game works the shots are always accurate, but I can clearly see in your example why it wouldn't have worked for the OPs needs.
Answer by Seth-Bergman · Aug 06, 2012 at 06:50 AM
void CalculateAimPos(Vector3 targetPos, Quaternion targetRot, int targetSpeed, Vector3 myPos)
{
Vector3 aimPos = (targetPos + myPos) * .9;
desiredRotation = Quaternion.LookRotation(aimPos);
}
I believe it's as simple as that
of course as far as I can see here you are not passing the (aimPos) to the bullet yet, to know where to end up.. I assume you are planning this..
Bigger problem though, is that all your declarations are in javascript! but this looks to be a c# script.. to declare your vars, change it to:
Vector3 targetPos = myTarget.position;
//Find target's rotation
Quaternion targetRot = Quaternion.LookRotation (myTarget.position - transform.position, Vector3.up);
//Find target's speed
int targetSpeed = (int) myTarget.gameObject.GetComponent().mySpeed;
//Find tower's position
Vector3 myPos = transform.position;
hope this helps
Yes I changed all of the "var" but the problem with the first part:
{ Vector3 aimPos = (targetPos + myPos) * .9; desiredRotation = Quaternion.LookRotation(aimPos); } well first off it'd be (targetPos - myPos), but the enemy has a set speed, so if it went higher, the bullet wouldn't hit the enemy on time... and the bullet has a speed that can change as well. so what I'm asking is there a formula to find the Postion an enemy will be at given it's own speed and direction while also taking in consideration the bullet's speed?void CalculateAimPos(Vector3 targetPos, Quaternion targetRot, int targetSpeed, Vector3 myPos)
Answer by cassius · Aug 09, 2012 at 12:11 AM
Actually I wrote a script that does almost exactly what you want. Unity3D Enemy AI Script to Anticipate Player’s Future Position. Basically it will calculate the player's speed (though you could change it to whatever) and takes into account the speed of the enemy's bullet. It then shoots the bullet towards where the player would be once the bullet reaches that distance.
I'm sure it can be streamlined better but hope it helps. It's javascript.
Well that sounds like what I am looking for, of course it's the opposite way :) but that shouldn't be a problem, and I use C# but I'm sure I can recode it! I'll check it out!
Unfortuantly, I couldn't use it... It would work if it moved in straight line, but it won't help to calculate it's movement on a 2D grid :( I can't figure out the part that was left out... I'm still trying to find the right formula
@cassius: This is actually very inaccurate. When the bullet is very fast it's almost neglectable, but when they move at around the same speed or just 2 or 3 times different it might get very inaccurate depending on the angle.
@$$anonymous$$_Gray: What do you mean by not in a straight line? Do you mean you have a tower defence game and an arbitrary path the enemies will follow? in this case it's almost impossible to create a formula that calculates this. In such a case it's really better to use something like cassius said, but with two changes:
You need to be able to calculate the position of the enemy at a specific time in the future.
You have to use an iterative approach.
So you calculate the distance to the target and then you get the time the bullet would take. now move the enemy ahead of time to the position it will be after that time and then repeat.
Each iteration it will get closer to the real interception point.
If your enemy moves in one direction, you can calculate an interception course. I've written such a function a while ago in C++ and wasn't in need to convert it to C#. If you're interested i can convert it, or do it yourself ;)
Actually I shouldn't post the code it since it's the evil code that enables bot programmers to create aimbots for projectile weapons like rocket launchers ;)
@Bunny83: I'm not sure what you mean by my script beco$$anonymous$$g inaccurate. It works 100% of the time in my game exactly as expected. But of course understanding that $$anonymous$$_Gray is looking for something that changes direction, I think you're right that it's almost impossible to write a formula for.
Wouldn't the iterative approach you mentioned basically result in a heat-seeking missile-style action? Or maybe that's what $$anonymous$$_Gray is actually after?
@cassius: Your method is never 100% correct, sorry ;) You calculate time you need to the target from the actual position of you and of the target, but the time until you reach the target can be more or less than that depending on if the object moves away or comes closer.
Your approach will improve when you do your steps multiple times.
Again, if the speed difference is big (so the projectile is times faster than the enemy) the error is almost neglectable, but when it's just double the speed or less your method fails. I've made an example. I will post an answer myself. It just takes some more time ;)
Answer by PoyrazGoksel · Mar 08, 2020 at 03:55 AM
This is what i did using simple maths and physics end result is 100% precise
/// <summary>
/// <para>Since Laser speed is constant no need to calculate relative speed of laser to get interception pos!</para>
/// <para>Calculates interception point between two moving objects where chaser speed is known but chaser vector is not known(Angle to fire at * LaserSpeed"*Sort of*")</para>
/// <para>Can use System.Math and doubles to make this formula NASA like precision.</para>
/// </summary>
/// <param name="PC">Turret position</param>
/// <param name="SC">Speed of laser</param>
/// <param name="PR">Target initial position</param>
/// <param name="VR">Target velocity vector</param>
/// <returns>Interception Point as World Position</returns>
public Vector3 CalculateInterceptionPoint3D(Vector3 PC, float SC, Vector3 PR, Vector3 VR)
{
//! Distance between turret and target
Vector3 D = PC - PR;
//! Scale of distance vector
float d = D.magnitude;
//! Speed of target scale of VR
float SR = VR.magnitude;
//% Quadratic EQUATION members = (ax)^2 + bx + c = 0
float a = Mathf.Pow(SC, 2) - Mathf.Pow(SR, 2);
float b = 2 * Vector3.Dot(D, VR);
float c = -Vector3.Dot(D, D);
if ((Mathf.Pow(b, 2) - (4 * (a * c))) < 0) //% The QUADRATIC FORMULA will not return a real number because sqrt(-value) is not a real number thus no interception
{
return Vector2.zero;//TODO: HERE, PREVENT TURRET FROM FIRING LASERS INSTEAD OF MAKING LASERS FIRE AT ZERO!
}
//% Quadratic FORMULA = x = ( -b+sqrt( ((b)^2) * 4*a*c ) ) / 2a
float t = (-(b) + Mathf.Sqrt(Mathf.Pow(b, 2) - (4 * (a * c)))) / (2 * a);//% x = time to reach interception point which is = t
//% Calculate point of interception as vector from calculating distance between target and interception by t * VelocityVector
return ((t * VR) + PR);
}
This is almost perfect. But firing a bullet with a rigidbody and a velocity applied, (zero mass, no gravity and zero drag), will result in a miss. If I do about 20% of speed, it's better, but it still misses quite a lot. I have debugged this using rays, and placing a red dot on the aimpoint, and it aims exactly at the point.
There might be some Unity physics shenanigans involved here, but the math here should be perfect, and it's reasonably performant
Your answer
Follow this Question
Related Questions
2D Cursor Aiming 2 Answers
Getting the face direction 2 Answers
Pointing 2 empty game objects towards one point? (Raycast?) 1 Answer
addforce question for android? 0 Answers