Jun 212013
 

In an old post I explained how to shoot an object to hit a moving target in 2D. The method in 3D is basically the same, but the code below is much cleaner and might be simpler to understand even for the 2D case.

Unity3D example and source code

Unity webplayer example

Unity project

The interesting bit of C#:

private Vector3 FindInterceptVector(Vector3 shotOrigin, float shotSpeed,
    Vector3 targetOrigin, Vector3 targetVel) {
   
    Vector3 dirToTarget = Vector3.Normalize(targetOrigin - shotOrigin);
   
    // Decompose the target's velocity into the part parallel to the
    // direction to the cannon and the part tangential to it.
    // The part towards the cannon is found by projecting the target's
    // velocity on dirToTarget using a dot product.
    Vector3 targetVelOrth =
    Vector3.Dot(targetVel, dirToTarget) * dirToTarget;
   
    // The tangential part is then found by subtracting the
    // result from the target velocity.
    Vector3 targetVelTang = targetVel - targetVelOrth;
   
    /*
    * targetVelOrth
    * |
    * |
    *
    * ^...7  <-targetVel
    * |  /.
    * | / .
    * |/ .
    * t--->  <-targetVelTang
    *
    *
    * s--->  <-shotVelTang
    *
    */

   
    // The tangential component of the velocities should be the same
    // (or there is no chance to hit)
    // THIS IS THE MAIN INSIGHT!
    Vector3 shotVelTang = targetVelTang;
   
    // Now all we have to find is the orthogonal velocity of the shot
   
    float shotVelSpeed = shotVelTang.magnitude;
    if (shotVelSpeed > shotSpeed) {
        // Shot is too slow to intercept target, it will never catch up.
        // Do our best by aiming in the direction of the targets velocity.
        return targetVel.normalized * shotSpeed;
    } else {
        // We know the shot speed, and the tangential velocity.
        // Using pythagoras we can find the orthogonal velocity.
        float shotSpeedOrth =
        Mathf.Sqrt(shotSpeed * shotSpeed - shotVelSpeed * shotVelSpeed);
        Vector3 shotVelOrth = dirToTarget * shotSpeedOrth;
       
        // Finally, add the tangential and orthogonal velocities.
        return shotVelOrth + shotVelTang;
    }
}

Update:

If you want to find the point where they meet, you can calculate the time it will take, and then multiply the shot velocity by that. In practice they will collide sooner since they have a certain radius, but we can take that into account when we calculate the time.

// Find the time of collision (distance / relative velocity)
float timeToCollision = ((shotOrigin - targetOrigin).magnitude - shotRadius - targetRadius)
        / (shotVelOrth.magnitude-targetVelOrth.magnitude);

// Calculate where the shot will be at the time of collision
Vector3 shotVel = shotVelOrth + shotVelTang;
Vector3 shotCollisionPoint = shotOrigin + shotVel * timeToCollision;
  • Saiyajin

    😮
    Thank’s is good :)

  • rvharten@hotmail.com

    Thanks!

  • rvharten

    Argh, posted with email as my name, could you change it or remove it? Thanks a lot!

    • http://Danikgames.com Danik

      You’re welcome!
      I removed the old comment.

  • Vlad

    I have a question. Is there a way to actually find the exact point of interception. Lets say when you find the velocity and direction of the projectile to say it hits at exactly (x,y,z). Thanks for your help, your article is amazing.

    • http://Danikgames.com Danik

      Yes, that shouldn’t be hard. I will update the post with the answer.

    • http://Danikgames.com Danik

      See the update in the post.

      • Vlad

        Wo thank you so much!!! You are amazing :D!

  • fastbird

    Thanks! this is a good method.

    By the way when calculating timeToCollision, ‘shotVelOrth.magnitude’ and ‘targetVelOrth.magnitude’ may have the same value and results in divide zero error.

    Maybe your intention is using
    ‘Vector3.Dot(targetVel, dirToTarget)’ instead of ‘targetVelOrth.magnitude’

    • http://Danikgames.com Danik

      Thanks Fastbird. Yeah good point, the collision point calculation assumes that shotVelOrth.magnitude > targetVelOrth.magnitude, or the time will be infinite (zero division if they are equal) or negative.

  • Douglasg14b

    This post was a godsend, here is a .GIF of it in action: http://i.imgur.com/4N3Zofv.webm

    • http://Danikgames.com Danik

      Nice! Thanks for sharing :)

  • Bugbird

    You are my hero! Thank You!!

  • Pingback: Studio 2: Back at it again | Caleb's Blog()

  • Trevor Thorpe

    Wow I learned so much from this. Holy crap. It’s nice to see something and then try it and succeed.

    Is there a relatively easy way to implement the cannon’s velocity into this so that if it is moving it will still have an accurate shotCollisionPoint?