Crosshair Targeting System

I’m making a space sim, and I need a targeting cursor in front of an enemy so the player can aim. Since when you fire a bullet, you need to fire ahead so the bullet actually hits it’s target. It’s hard at the moment to judge where to shoot based off your speed, the bullet’s speed, and your distance from the target. So essentially there would be a little crosshair in front of the target where the player needs to shoot in order to hit it. Here’s an example (What I’m talking about doesn’t actually start till 2:35). I know I need to do some math to calculate where to place the GUI crosshair based off that target’s distance, the target’s speed, your speed, and the bullet’s speed. I just don’t know where to start. Any help would be appreciated.

Actually, that game doesn’t seem to do that - it seems to just draw a crosshair at some distance in the airplane’s forward direction.

But if you want to find the position where to shoot taking into account the target velocity, there exists a “mathmagic” to do that: the function HitTime below calculates the time needed to a projectile with velocity speed to reach the target targ moving at velocity vel (if the target is moving away too fast to be reached, the function returns -1). Once the time is calculated, you can find the position to shoot using the target velocity, draw the crosshair and shoot in its direction.

In most cases you don’t know the target velocity, but you can calculate it from the target positions over time. The calculated velocity is somewhat jerky, thus you must filter it in order to have a smooth behaviour - but this also makes the aim unreliable until the calculated velocity reaches a stable value.

You must fire the projectile by setting its rigidbody.velocity to bulletSpeed * direction.normalized (forget about AddForce - you will never get the correct speed).

// targ is the target transform, vel is its velocity vector,
// and speed is the projectile speed. Returns the time needed
// to reach the target, or -1 if it's not possible

function HitTime(targ: Transform, vel: Vector3, speed: float): float {
  var dist = targ.position - transform.position;
  var a = Vector3.Dot(vel, vel) - speed * speed;
  var b = 2 * Vector3.Dot(vel, dist);
  var c = Vector3.Dot(dist, dist);
  var det = b * b - 4 * a * c;
  if (det >= 0)
    return 2 * c / (Mathf.Sqrt(det) - b);
  else
    return -1;
}

// that's an example code to test this function:

var guiCross: GUITexture; // drag the GUITexture crosshair here
var target: Transform; // target transform
var bulletSpeed: float = 10; // projectile speed

private var targetVel: Vector3;
private var lastPos: Vector3;

function Update(){
  if (target){ // if some target locked...
    // if you don't know the target velocity, calculate it
    var vel = (target.position - lastPos)/Time.deltaTime;
    lastPos = target.position;
    // filter the velocity to avoid a shaking crosshair
    targetVel = Vector3.Lerp(targetVel, vel, Time.deltaTime);
    
    // calculate the hit position:
    var t = HitTime(target, targetVel, bulletSpeed);
    if (t < 0){
      // target moving away too fast to be reached
      guiCross.enabled = false;
    } else {
      // target may be hit:
      guiCross.enabled = true;
      // calculate the position where the target will be hit
      var hitPos = target.position + t * targetVel;
      // calculate the crosshair position
      var crossPos = Camera.main.WorldToViewportPoint(hitPos);
      guiCross.transform.position = crossPos;
    }
    
    if (Input.GetButtonDown("Fire1")){
      var missile = GameObject.CreatePrimitive(PrimitiveType.Sphere);
      Destroy(missile, 20); // kill the missile after 20s
      missile.AddComponent(Rigidbody);
      missile.rigidbody.position = transform.position;
      missile.rigidbody.useGravity = false;
      
      // use this to shoot at the calculated point:
      var dir = hitPos - transform.position;
      missile.rigidbody.velocity = dir.normalized * bulletSpeed;
    }
  }
}

CREDITS: I based the HitTime function in an interesting article about this subject, Leading a target.

Start with Camera.WorldToScreenPoint(); Calculate the necessary targeting crosshair’s position in 3D, then use the WorldToScreenPoint method to transfer it’s position to a point on the screen.

How to calculate the crosshair’s position, though?

Algorithm should be something like:

  1. Determine projectile’s travel time from weapon position to enemy position through division of distance by speed and then again by average FPS
  2. Predict enemy’s position by calculating it’s position in the next frame, multiplied by the projectile’s travel time (to predict rotation have a look at this answer - it’s working great)
  3. Call Camera.WorldToScreenPoint(predictedPosition);
  4. Draw the crosshair through GUI