target tracking with lead time

Hello all -

I am making a simple turret system that fires at a target as it flies by. I currently have the code set up so that the turret tracks the target, and fires at it whenever it detects a "hit."

The problem is, that when the target is moving in a straight line or something like that, my bullets will never actually hit the target. The turret fires, but the moment it fires and the projectiles are on their way, the target has already moved to a new position. Therefore unless the target turns in toward the turret or away or some such action, the projectiles will simply miss. As the target moves in a circle around the turret, the bullets will never hit. Ever. Thats just the nature of the beast :)

However, I want to add some sort of leading on the turret so that it can hit the target. Now, I know with random movement my odds of hitting it are the same whether I shoot at where it is or if I lead the target. However, even with random movement as I am going to be using it, there are still going to be situations where the target will be going in a straight line long enough for projectiles to hit it if they are aimed properly.

So my question is, what is the best way to "lead" my target so that providing there are no additional forces acting on the object, my bullets will hit?

My current idea, though I don't know how to implement this in code, is :

Have some sort of variable (float, maybe vector3), speed, that gives me the speed and direction of the target.

Then, have the turret take into account on any given frame the speed of the target, the distance of the target from the turret, and the projectile speed. With that information, I should be able to solve for how much "lead" space there needs to be. Then just take the current position of the target, add the "lead" space ahead of it where it is moving to, and fire there. In theory, if the target is then not acted upon by any forces, the bullets should hit. Any ideas on how this could be implemented?

I know I've answered a question identical to this a while back, but I can't seem to find it.

The idea is that you know how fast your target is moving in what direction and you aim at where they are going to be when your projectile reaches the distance that they are located away from you. targetPosition + targetDistance * targetDirection * TargetSpeed / projectileSpeed. This is not perfect, but sufficient for most purposes. A more complex approach will actually calculate the exact point at the distance that they will be at rather than using the current distance.

I'm not sure how your targets are being moved, but I'll assume you can figure out how to get that information.

Of course, simply calculating the point to look at won't be enough. If you look at that point and the target suddenly stops or changes direction, you're going to get some jerky movement. You'd best consider using some kind of rotation smoothing. Something like this may work for you:

var target : Transform;

//Get these using your target:
//TargetDirection can have magnitude of targetSpeed
//in which case you only need one variable for both of them.
var targetDirection : Vector3; // Normalized direction.
var targetSpeed : float; //World units.

var projectileSpeed : float = 200.0f; //World units
var maxDegreeRotation : float = 30.0f;
//var someFactor : float;

function Update() {
    if(!target) return;
    var leadDirection : Vector3 = target.position - transform.position;
    leadDirection += leadDirection.magnitude
                     *targetDirection
                     *targetSpeed
                     /projectileSpeed;
    transform.rotation = Quaternion.RotateTowards(transform.rotation,
                           Quaternion.LookRotation(leadDirection, transform.up),
                           maxDegreeRotation);
    /*or
    transform.rotation = Quaternion.Slerp(transform.rotation,
                           Quaternion.LookRotation(leadDirection, transform.up),
                           Time.deltaTime*someFactor);
    */
}

If you wanted to add in things like inertial movement, you would have to maintain additional information and adjust your input parameters accordingly, something like: check if old target lead position is in the same direction as the current lead position and if not, subtract from the maxDegreeRotation while still rotating towards the old target lead until the maxDegreeRotation is <=0 at which point you'd flip the sign and set the old target lead to be the new target lead and then add to maxDegreeRotation up to some maximum.

If you want to constrain the rotations to only be within a certain number of degrees, you could do something to clamp in a circle like this:

var target : Transform;

//Get these using your target:
//TargetDirection can have magnitude of targetSpeed
//in which case you only need one variable for both of them.
var targetDirection : Vector3; // Normalized direction.
var targetSpeed : float; //World units.

var projectileSpeed : float = 200.0f; //World units
var maxDegreeRotation : float = 30.0f;
//var someFactor : float;

var maxRotationRange : float = 30.0f;    
private var startDirection : Vector3 = Vector3.forward;

function Start() {
    startDirection = transform.forward;
}

function Update() {
    if(!target) return;
    var leadDirection : Vector3 = target.position - transform.position;
    leadDirection += leadDirection.magnitude
                     *targetDirection
                     *targetSpeed
                     /projectileSpeed;
    leadDirection = Vector3.RotateTowards(startDirection, leadDirection,
                            maxRotationRange * Mathf.Deg2Rad, 0.0f);
    transform.rotation = Quaternion.RotateTowards(transform.rotation,
                           Quaternion.LookRotation(leadDirection,transform.up),
                           maxDegreeRotation);
}

To clamp on set axes would be done a bit differently, calculating your rotation yourself and constraining the axes.