Getting AI to lead with its shots

Does anyone know or have a link to a formula for solving the angle at which you should shoot a projectile with constant velocity in order to hit a target with constant velocity?

I only need to solve this in two dimensions.

The simplest way is to:
Take the speed and location of the target.
Calculate the distance to the location the target will be at the time of impact.
Multiply the target’s speed by the time and add to its current position.

Step 2 is the tricky part, because you don’t know how long this distance will be until you know the point, but you can’t find the point until you know the distance. The easiest solution is to guess - just use the distance to the current target position. This is usually “good enough”. If it’s imperative to be deadly accurate, you can use that estimate to estimate again, closer and closer… ultimately, if you’ve taken calculus, you might arrive at a method where you could find the limit of this guessing formula. That’s beyond my current skill level, though, and I’m pretty good at math.

Just guess. :slight_smile:

Look up the Pursuit steering behaviour for some code for what StarManta is talking about. It basically does the same thing to pick a point to move to.

-Jeremy

I was hoping for an exact calculation. Googling only dragged up a lot of high school math help pages talking about finding x-intercepts, and pages for actual missile guidance systems, which are a little more complicated.

This page might be useful: https://www.math.ubc.ca/~cass/courses/m308-05b/projects/knox/John%20P%20Knox%20Math%20308%20Project.htm#flightime

That’s about ballistics and drag, which get much more complicated because they introduce (negative) acceleration into the equation.

Anyway, I think I’ve almost worked it out. Once I do, I’ll post it on the wiki, because it’s probably a common problem.

Can’t you just set acceleration to zero? That simplifies quite a bit.

This would depend heavily on wether you’re using the Physics-Engine of Unity, since you can’t really know how their calculating the flight path of your projectile.

If you know the flight path of your projectile, you could do the following:

Add a 3rd dimension to your calculations: time. Now you should be able to make a formula for the flight path of your projectile in dependance on the time and the angle at which you fire.
This is what it looks like, if you disregard drag.

[ x, y, 0] + [ cos( alpha ) * vel, sin( alpha ) * vel, 1 ] * t + [ 0, -9.81*0.5, 0] t^2

The first summand represents the starting point, the second the direction and the velocity in which the projectile is initially fired. The third one represents gravity.

Your other object could be represented like this:

[x2, y2, 0] + [ cos( beta ) * vel2, sin( beta ) * vel2, 1 ] * t

So equate these two equations:

[ x, y, 0] + [ cos( alpha ) * vel, sin( alpha ) * vel, 1 ] * t + [ 0, -9.81*0.5, 1/t] t^2 = [x2, y2, 0] + [ cos( beta ) * vel2, sin( beta ) * vel2, 1 ] * t

Solve this linear system of euqations and you should get your angle, in which you have to fire.

Hope this helps.

Der Dude:

Let me guess what your skillset is :wink:

Way beyond me.

-Jeremy

+Color me impressed.

Why don’t you copy the original trajectory and whotnot and then duplicate that for the intercepting projectile, adding adjustments for differences in the velocity and X Y.
If they are both at a constant velocity (whatever the ‘differences’) then you can get the ratio of their angular differences from this. Assuming the projectiles are not arcing.

Cheers,

Luis.

I’ll admit, this wasn’t for a Unity project, which is why it was in 2D. It’s in C, but there’s nothing fancy in it. alienDumbShoot() just fires straight at one location from another. I realize it’s sort of hobo-ish to just drop a bunch of C on this forum, but I’m too busy at the moment to do any better. I’d like to do it properly in JS at some point. I can probably adapt it to three dimensions, as well.

void alienSmartShoot(	double alienX,
						double alienY,
						double targetX,
						double targetY,
						double targetDX,
						double targetDY) {
	double a, b, c, t1, t2, determinant, sqrtDeterminant;
	
	a = pow(targetDX, 2.0) + pow(targetDY, 2.0) - pow(ALIEN_PHOTON_SPEED, 2.0);
	
	if (a == 0.0) {
		return;
	}
	
	b = 2.0*(targetDX*(targetX - alienX) + targetDY*(targetY - alienY));
	
	c = pow(targetX-alienX, 2.0) + pow(targetY-alienY, 2.0);
	
	determinant = pow(b, 2.0) - 4.0*a*c;
	
	if (determinant < 0.0) { //determinant < 0; no intercept path
		return;
	} else if (determinant == 0.0) { //determinant = 0; one intercept path
		t1 = -b/(2.0*a);
		if (t1 < 0.0) t1 = 0.0;
	} else { //determinant > 0; two intercept paths
		sqrtDeterminant = sqrt(determinant);
		t1 = (-b+sqrtDeterminant)/(2.0*a);
		t2 = (-b-sqrtDeterminant)/(2.0*a);
		//choose shortest path
		if (t1 > 0.0  t2 > 0.0) {
			if (t2 < t1) t1 = t2;
		} else {
			if (t2 > t1) t1 = t2;
			if (t1 < 0.0) t1 = 0.0;
		}
	}
	//set collision location
	targetX = targetDX*t1 + targetX;
	targetY = targetDY*t1 + targetY;
	//fire
	alienDumbShoot(alienX, alienY, targetX, targetY);
}

After looking through the (surprisingly) very few posts found on this subject, I wanted to give it a shot. After scratching my head for awhile trying to figure out how to calculate the future position of the target I stumbled on to the fact that rigidbody.velocity is a Vector3.

I don’t know what gave me the idea to try adding Target.rigidbody.velocity to Target.transform.position but I tried it, thinking it wouldn’t work. After creating an empty game object to represent the new target position I noticed it did indeed work, and well too.

Of course being so simple, it didn’t calculate distance and projectile speed. So a little math trial-and-error and presto!

var Target : GameObject;
var projectileSpeed = 500;
var turnSpeed = 5;


function FixedUpdate () {
    if (Target) {
		// actual distance to target
        var distTarget = Vector3.Distance(Target.transform.position, transform.position);
		// first calculation, using actual distance
		velocityPosition1 = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (distTarget/projectileSpeed) );
		velocityDist1 = Vector3.Distance(velocityPosition1, transform.position);
		// second calc., using distance from first calc.
		velocityPosition2 = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (velocityDist1/projectileSpeed) );
		velocityDist2 = Vector3.Distance(velocityPosition2, transform.position);
		// third calc., using distance from second calc.
		TargetInterceptPosition = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (velocityDist2/projectileSpeed) );
		// aim turret/ship at intercept position
        rotationTarget = Quaternion.LookRotation((TargetInterceptPosition) - transform.position);		
        transform.rotation = Quaternion.Lerp(transform.rotation, rotationTarget, Time.deltaTime * turnSpeed);
    }
    else {
        Target = GameObject.FindWithTag("ship");//or find closest object in seperate function, etc.
    }
}

While this script is on an AI spaceship, which fires the projectile, I don’t see any reason why it wouldn’t work on a projectile, (just translate position.) other than it may get costly if there are many, many projectiles.

I tested this script running on 10 ships (with rigidbodies of their own) and 10 target ships. I didn’t notice any performance loss over the old LookAt and shoot method I was using.

It’s simple, and FAST! Did someone here say that it wasn’t possible? :stuck_out_tongue: This uses 3 calculations, testing with 4 made no difference, in fact it was a bit off lol. Not sure why I had to multiply Target.rigidbody.velocity*1.15 but it seems to be the magical number.

I am excited to share this freely, so enjoy! If anyone would like to make a donation to my food fund for the effort I certainly won’t decline :smile: and my name being mentioned on credits is appreciated as well!

2 Likes

I’ve posted something like this on the Unity Answers page. While what i have is not for the AI but for the Player. A visual Target Lead Indicator. Do you think this could be adapted to the Player for this purpose? What i have kind of works but i’m missing some element be it relative velocity, and or angular velocity, because my target indicator stays in the same place in front of the target relative to my distance. I’m not sure how to incorporate them into the equation.

Yes it will work perfectly for a lead target indicator. When I was testing this I had 3 sphere meshes, red, yellow, and green that represented the the 3 calculations…

var Target : GameObject;
var projectileSpeed = 500;

private var Sphere1 : GameObject;
private var Sphere2 : GameObject;
private var Sphere3 : GameObject;


function Start () {
    Sphere1 = GameObject.Find("Sphere1");
    Sphere2 = GameObject.Find("Sphere2");
    Sphere3 = GameObject.Find("Sphere3");
}

function FixedUpdate () {
    if (Target) {
      // actual distance to target
      var distTarget = Vector3.Distance(Target.transform.position, transform.position);
      // first calculation, using actual distance
      velocityPosition1 = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (distTarget/projectileSpeed) );
      velocityDist1 = Vector3.Distance(velocityPosition1, transform.position);
      // second calc., using distance from first calc.
      velocityPosition2 = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (velocityDist1/projectileSpeed) );
      velocityDist2 = Vector3.Distance(velocityPosition2, transform.position);
      // third calc., using distance from second calc.
      TargetInterceptPosition = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (velocityDist2/projectileSpeed) );
      // aim turret/ship at intercept position
    }
    raySphere1.transform.position = velocityPosition1;
    raySphere2.transform.position = velocityPosition2;
    raySphere3.transform.position = TargetInterceptPosition;
    else {
        Target = GameObject.FindWithTag("ship");//or find closest object in seperate function, etc.
    }
}

Of course you don’t actually need 3 spheres, just the last one. Rather than a sphere you could use a mesh shaped like an X or + and have it always look at the player and update the rotation to keep it from looking like an object and more like a HUD, etc.

The only thing I see that needs to be done is how you will handle selecting a target. Good luck and have fun!

Starcruiser,

This is perfect. Sorry it’s been a while, got busy doing other Unity stuff. I already had a targeting system so i just plugged in your code and presto. I’m using a GUI for my target lead.

Thanks so much for this code!

var TLI : GUITexture;

function TargetLeadIndicator(){

	// actual distance to target 
	var distTarget = Vector3.Distance(Target.transform.position, transform.position); 
	Debug.Log(distTarget);
	// first calculation, using actual distance 
	velocityPosition1 = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (distTarget/projectileSpeed) ); 
	velocityDist1 = Vector3.Distance(velocityPosition1, transform.position); 
	// second calc., using distance from first calc. 
	velocityPosition2 = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (velocityDist1/projectileSpeed) ); 
	velocityDist2 = Vector3.Distance(velocityPosition2, transform.position); 
	// third calc., using distance from second calc. 
	TargetInterceptPosition = Target.transform.position + ( (Target.rigidbody.velocity*1.15) * (velocityDist2/projectileSpeed) ); 
	// aim turret/ship at intercept position 
	  
		var relPos : Vector3;

		relPos = Target.transform.position - camera.main.transform.position;
					
		if (Vector3.Dot(camera.main.transform.TransformDirection(Vector3.forward), relPos) > 0){
			TLI.transform.position=camera.main.WorldToViewportPoint(TargetInterceptPosition);
	  	}
}

Firstly, my first Unity game, Starcruiser, is VERY close to first release! :smile: I have spent nearly 1000 hours, or more, over the course of 4 years on this game. I have worked another few hundred hours on a few other projects that are ‘halfway’ through completion, which has taught me a many great things! All content is %100 percent original, I have modeled, scripted, tweaked and tweaked and tweaked game objects, created textures, music and sounds, all me! :slight_smile:

Now to my point, there is a part of Starcruiser with laser turrets, I applied this ‘targeting lead’ script to my turrets and discovered it made them VERY dangerous :open_mouth: but they weren’t dangerous enough :wink: Now being shot at by this script revealed the mystery of the 1.15, they are more accurate without this number! (or using 1 instead)

Then it dawned on me that this could be used as a variable to simulate inaccuracy… so replacing ‘Target.rigidbody.velocity1.15’ with 'Target.rigidbody.velocityaccuracy’ and of course adding ‘var accuracy = 1.0;’ gives you this option.

Now seems to me that 1.0 is the most accurate, making it a higher number causes it to shot too far ahead, lower too far behind. One could go as far as to use a random number between say .9 and 1.1 to give it some variation. Or, use some other variable to adjust accuracy based on distance or angular velocity…

Vector3.distance uses a square root. 3 times a frame for your 10 ships, is going to eat up a lot of juice.

You might want to use a coroutine to spread out the calculations over time. No real need to do it every physics frame, unless you’re writing an Aimbot! An update frequency for each ship might be a better inaccuracy metric in that regard.

Bumping this old thread because I’m at this stage in my project.

I did a 2d version of this ages ago, and what I found is that if all you wanted was the correct angle to shoot, then the calculation is quite simple and the distance to target does not matter at all. Here is a little flash mockup that I did back then: leadtarget

If you click OK, you’ll see some ships flying around that show it is dead accurate.

It hinges on the fact that you know the speed of the target and the bullet, and you know the angle from the line that connects the objects, to the direction of movement of the target. When you know these things, the angles are always the same regardless of distance. Distance just scales the size of the triangle.

In pseudocode:

deltaAngle = angleFromTargetToGun - angleOfTargetMotion
aimOffsetAngle = aSin(( bulletSpeed / targetSpeed )* Sin ( deltaAngle ))
angleToAim = angleFromGunToTarget + aimOffsetAngle;

Now, in my current project, I don’t just want the correct angle, I want the exact point of impact for a HUD element, and this is where my simple solution above becomes not so simple anymore.

Extending the above, instead of solving for angleToAim, I have to solve for distanceTargetTravels, so that I can place the HUD element at target.position + target.velocity.normalized * distanceTargetTravels

using the deltaAngle and aimOffsetAngle calculated above, together with distanceToTarget and the sine rule a/sin(A) = b / sin(B), I get this:

distanceTargetTravels = distanceToTarget * Sin ( aimOffsetAngle ) / Sin ( 180 - aimOffsetAngle - deltaAngle )

that’s 3 Sines and an arcSine, it works but I am sure it can be done much more efficiently, if only my trigonometry weren’t so rusty.

edit: a note on accuracy, the trigger for my AI to actually fire is:

if (Vector3.Dot(transform.forward, angleToAim) > accuracy) //shoot

When accuracy is just a little less than 1.0 it is dead accurate, though lower accuracy means they spam a lot of shots which can be just as deadly.

Create a child transform that is slightly forward offset from the target and aim at that?