projectile trajectory prediction

Hi. I have AI autoturret. Everything works fine (prediction for shooting, detecting enemies). However, I have a trouble with implementation of a bullet drop (gravity for a bullet). I have seen many posts about ballistic bullets and it’s trajectory but I cant make it work.
What my turret needs is a Vector 3 target for shooting. Right now, it has its computed vector3 target. I need to access the Vector3.y value of the target and increase it based on the distance from the target, gravity applied to the bullet and the position of the target and make a new Vector 3. (speed of the bullet is constant)
Can anybody help me with this ? Thanks :slight_smile:

You have all the needed information in this page : Projectile motion - Wikipedia

Feel free to use my own class if you dont want to write your own.
Ballistics

using UnityEngine;
using System;

    public static class Ballistics {

        // Note, doesn't take drag into account.

        /// <summary>
        /// Calculate the lanch angle.
        /// </summary>
        /// <returns>Angle to be fired on.</returns>
        /// <param name="start">The muzzle.</param>
        /// <param name="end">Wanted hit point.</param>
        /// <param name="muzzleVelocity">Muzzle velocity.</param>
        public static bool CalculateTrajectory(Vector3 start, Vector3 end, float muzzleVelocity, out float angle){//, out float highAngle){

            Vector3 dir = end - start;
            float vSqr = muzzleVelocity * muzzleVelocity;
            float y = dir.y;
            dir.y = 0.0f;
            float x = dir.sqrMagnitude;
            float g = -Physics.gravity.y;

            float uRoot = vSqr * vSqr - g * (g * (x) + (2.0f * y * vSqr));


            if (uRoot < 0.0f) {

                //target out of range.
                angle = -45.0f;
                //highAngle = -45.0f;
                return false;
            }

    //        float r = Mathf.Sqrt (uRoot);
    //        float bottom = g * Mathf.Sqrt (x);

            angle = -Mathf.Atan2 (g * Mathf.Sqrt (x), vSqr + Mathf.Sqrt (uRoot)) * Mathf.Rad2Deg;
            //highAngle = -Mathf.Atan2 (bottom, vSqr - r) * Mathf.Rad2Deg;
            return true;

        }

        /// <summary>
        /// Gets the ballistic path.
        /// </summary>
        /// <returns>The ballistic path.</returns>
        /// <param name="startPos">Start position.</param>
        /// <param name="forward">Forward direction.</param>
        /// <param name="velocity">Velocity.</param>
        /// <param name="timeResolution">Time from frame to frame.</param>
        /// <param name="maxTime">Max time to simulate, will be clamped to reach height 0 (aprox.).</param>

        public static Vector3[] GetBallisticPath(Vector3 startPos, Vector3 forward, float velocity, float timeResolution, float maxTime = Mathf.Infinity){

            maxTime = Mathf.Min (maxTime, Ballistics.GetTimeOfFlight (velocity, Vector3.Angle (forward, Vector3.up) * Mathf.Deg2Rad, startPos.y));
            Vector3[] positions = new Vector3[Mathf.CeilToInt(maxTime / timeResolution)];
            Vector3 velVector = forward * velocity;
            int index = 0;
            Vector3 curPosition = startPos;
            for (float t = 0.0f; t < maxTime; t += timeResolution) {
            
                if (index >= positions.Length)
                    break;//rounding error using certain values for maxTime and timeResolution
            
                positions [index] = curPosition;
                curPosition += velVector * timeResolution;
                velVector += Physics.gravity * timeResolution;
                index++;
            }
            return positions;
        }
        
        /// <summary>
        /// Checks the ballistic path for collisions.
        /// </summary>
        /// <returns><c>false</c>, if ballistic path was blocked by an object on the Layermask, <c>true</c> otherwise.</returns>
        /// <param name="arc">Arc.</param>
        /// <param name="lm">Anything in this layer will block the path.</param>
        public static bool CheckBallisticPath(Vector3[] arc, LayerMask lm){
        
            RaycastHit hit;
            for (int i = 1; i < arc.Length; i++) {

                if (Physics.Raycast (arc [i - 1], arc [i] - arc [i - 1], out hit, (arc [i] - arc [i - 1]).magnitude) && GameMaster.IsInLayerMask(hit.transform.gameObject.layer, lm))
                    return false;

    //            if (Physics.Raycast (arc [i - 1], arc [i] - arc [i - 1], out hit, (arc [i] - arc [i - 1]).magnitude) && GameMaster.IsInLayerMask(hit.transform.gameObject.layer, lm)) {
    //                Debug.DrawRay (arc [i - 1], arc [i] - arc [i - 1], Color.red, 10f);
    //                return false;
    //            } else {
    //                Debug.DrawRay (arc [i - 1], arc [i] - arc [i - 1], Color.green, 10f);
    //            }
            }
            return true;
        }

        public static Vector3 GetHitPosition(Vector3 startPos, Vector3 forward, float velocity){

            Vector3[] path = GetBallisticPath (startPos, forward, velocity, .35f);
            RaycastHit hit;
            for (int i = 1; i < path.Length; i++) {

                //Debug.DrawRay (path [i - 1], path [i] - path [i - 1], Color.red, 10f);
                if (Physics.Raycast (path [i - 1], path [i] - path [i - 1], out hit, (path [i] - path [i - 1]).magnitude)) {
                    return hit.point;
                }
            }

            return Vector3.zero;
        }
        

        public static float CalculateMaxRange(float muzzleVelocity){
            return (muzzleVelocity * muzzleVelocity) / -Physics.gravity.y;
        }

        public static float GetTimeOfFlight(float vel, float angle, float height){

            return (2.0f * vel * Mathf.Sin (angle)) / -Physics.gravity.y;
        }
        
    }
5 Likes

Thank you very much for the reply.
I am not sure if I am asking too much but, could you please tell, how would you modify your script, if you wanted only the direction (vector3) to be calculated, instead of the Angle to be fired on ?
I mean, my turret script needs vector3 target. So If I could give it only the direction for shooting instead of the angle, it would be ideal :slight_smile:

1 Like

I’m not sure what you mean by that, do you mean what would be the forward vector of given angle?

you would have to calculate it with the function I gave you earlier, this is a snippet from my game, it’s use to aim a mortar/howitzer.

        public override Quaternion AimRotation(Vector3 start, Vector3 end, Vector3 velocity){

            float low;
            //float high;
            Ballistics.CalculateTrajectory (start, end, velocity, out low);//, out high); //get the angle


            Vector3 wantedRotationVector = Quaternion.LookRotation (end - start).eulerAngles; //get the direction
            wantedRotationVector.x = low; //combine the two
            return Quaternion.Euler (wantedRotationVector); //into a quaternion
        }

btw, you can see all those “high” angles commented out, this is for performance because I don’t need it right now, you can uncomment it back it and use it if you want the high tarjectory.

2 Likes

This is what I need mate.

I literally gave you exactly this.

Call ‘CalculateTrajectory’ with the muzzle position for the ‘start’ variable, the target position for the ‘end’ variable, and the speed at launch, IE the muzzle velocity for the ‘velocity’ variable, this will output the angle into ‘low’ (you can also uncomment ‘high’ incase you want that).

this will angle the turret to look along the hard line you’ve drawn, but when you fire a rigidbody projectile along the vector gravity will pull it along the dotted line.

if you want a vector 3 array with the arch given gravity you can call ‘GetBallisticPath’.

1 Like

Ok, so lets say that I got the target and I rotate the turret with newRotation (Quaternion).
Now, I have a low float from you, that gives me the extra angle, to compensate for the bullet drop.
How do I modify the already existing newRotation, that points at the target, with a “low” float from your script ? So that I have modified , newRotationWithLow (Quaternion) ?
Thanks :slight_smile:

Here’s a full tutorial:

Hello. How would you follow the ballistic path with a gameobject, so that the GetTimeOfFlight method value would be accurate?

For anyone referencing this from google, I had to make one change. Most likely because my start and end locations are at different heights (y).

wantedRotationVector.x += low; //combine the two

@SparrowsNest thank you for posting this- I had fun trying to derive the equations until I got into trig substitutions and went to google instead :slight_smile:

2 Likes

I was a step away from losing sanity.

@SparrowsNest your CalculateTrajectory is not valid, you mixed ± and order in atan2 and function produces invalid results.

This function takes into account different heights, you probably encountered the same problem, but you false “fixed it” by combining some angles.

This is correct:

angle = -(Mathf.Atan2 (vSqr - r, bottom) * Mathf.Rad2Deg);
highAngle = -(Mathf.Atan2 (vSqr + r, bottom) * Mathf.Rad2Deg);
4 Likes

Hi and thanks for your code. This math calculations are really REALLY hard for someone like me from eights grade

hi there i think the the method “GetBallisticPath” the Angle calc should be forward instead of up

 Vector3.Angle(forward, Vector3.forward)

also the time calculation should more look like this:

    public static float GetTimeOfFlight(float vel, float angle, float height)
    {
        var a = vel * Mathf.Sin(angle);
        return (a + Mathf.Sqrt(Mathf.Pow(a, 2) + 2 * -Physics.gravity.y * height)) / -Physics.gravity.y;
    }

where height is the base height from the launching point and ground is asumed as 0

PS: not performance optimized yet

THX bro this really helped me out,Code is superb,and works.

1 Like

As seen here, you can easily shoot using both the bullet and the target’s position/velocity/acceleration into consideration.

1 Like