3D Trajectory Prediction

Hello,
I’ve been trying to make a trajectory following script for the last 7 days and I’ve gotten nowhere with it, I bought all 4 available projects in the unity asset store but none of them helped, they just got me more frustrated.

What I’m trying to do is make a trajectory appear when I hold a button, the trajectory will start at a certain position on the scene and end wherever the player points his mouse at, then a projectile will be launched with the clicked position as its target. I’ve gotten to a point where Im about to quit, this is my last hope to get some help. Is there any tutorial for this that could help(one that I could’ve missed because I already searched most of the internet for it) ?

Thank you!

A few months ago, I was working out a few basic formulas for trajectory.

I eventually settled on using a Trajectory class with static functions to determine the necessary launch vector to reach the destination based on a specified speed, (2D) angle, or time.

In addition to those static functions, however, I also created a second set, which populates the attributes of a trajectory assigned to an object in order to display a preview line of where the object will go under any circumstances.

Two issues I wasn’t able to account for in my experimentation were:

  1. factoring in drag and still reaching a target destination accurately and…

  2. varying gravitational pull and still reaching the target accurately.

However, the preview paths drawn are always perfectly accurate, assuming no collisions occur outside the center of the approximation.

I’m afraid I won’t be sharing absolutely everything in this response, but just to get a few notes out there regarding PhysX’ implementation of various physical elements:

rigidbodyDrag = Mathf.Clamp01(1.0f - (rb.drag * Time.fixedDeltaTime)); // Per FixedUpdate()
// This means that if your drag is equal to the framerate of FixedUpdate(), your object will lose 100% of its speed every frame

velocityPerFrame = lastFrameVelocity + (Physics.gravity * Time.fixedDeltaTime);
velocityPerFrame *= rigidbodyDrag;
positionPerFrame += (velocityPerFrame * Time.fixedDeltaTime);

Those are a few key elements involved in accurately determining your position per frame.

However, I’ll leave that to you and cut to the chase for now: Calculating launch trajectory in the first place.

The basis for these trajectory calculations is to go from Vector A to Vector B, with any height difference, or as close as it can get, based on each of three parameters.

First, A → B, arriving after a specified length of time:

public static Vector3 HitTargetAtTime(Vector3 startPosition, Vector3 targetPosition, Vector3 gravityBase, float timeToTarget)
{
	Vector3 AtoB = targetPosition - startPosition;
	Vector3 horizontal = GetHorizontalVector(AtoB, gravityBase);
	float horizontalDistance = horizontal.magnitude;
	Vector3 vertical = GetVerticalVector(AtoB, gravityBase);
	float verticalDistance = vertical.magnitude * Mathf.Sign(Vector3.Dot(vertical, -gravityBase));

	float horizontalSpeed = horizontalDistance / timeToTarget;
	float verticalSpeed = (verticalDistance + ((0.5f * gravityBase.magnitude) * (timeToTarget * timeToTarget))) / timeToTarget;

	Vector3 launch = (horizontal.normalized * horizontalSpeed) - (gravityBase.normalized * verticalSpeed);
	return launch;
}

Second, A → B, attempting to arrive based on a specified launch angle relative to the direction of gravity:

public static Vector3 HitTargetByAngle(Vector3 startPosition, Vector3 targetPosition, Vector3 gravityBase, float limitAngle)
{
	if(limitAngle >= 90f || limitAngle <= -90f)
	{
		return Vector3.zero;
	}
	
	Vector3 AtoB = targetPosition - startPosition;
	Vector3 horizontal = GetHorizontalVector(AtoB, gravityBase);
	float horizontalDistance = horizontal.magnitude;
	Vector3 vertical = GetVerticalVector(AtoB, gravityBase);
	float verticalDistance = vertical.magnitude * Mathf.Sign(Vector3.Dot(vertical, -gravityBase));
	
	float radAngle = Mathf.Deg2Rad * limitAngle;
	float angleX = Mathf.Cos(radAngle);
	float angleY = Mathf.Sin(radAngle);
	
	float gravityMag = gravityBase.magnitude;
	
	if(verticalDistance / horizontalDistance > angleY / angleX)
	{
		return Vector3.zero;
	}
	
	float destSpeed = (1 / Mathf.Cos(radAngle)) * Mathf.Sqrt((0.5f * gravityMag * horizontalDistance * horizontalDistance) / ((horizontalDistance * Mathf.Tan(radAngle)) - verticalDistance));

	Vector3 launch = ((horizontal.normalized * angleX) - (gravityBase.normalized * angleY)) * destSpeed;
	return launch;
}

Third, A → B, attempting to arrive based on a specified launch speed:

public static Vector3[] HitTargetBySpeed(Vector3 startPosition, Vector3 targetPosition, Vector3 gravityBase, float launchSpeed)
{
	Vector3 AtoB = targetPosition - startPosition;
	Vector3 horizontal = GetHorizontalVector(AtoB, gravityBase);
	float horizontalDistance = horizontal.magnitude;
	Vector3 vertical = GetVerticalVector(AtoB, gravityBase);
	float verticalDistance = vertical.magnitude * Mathf.Sign(Vector3.Dot(vertical, -gravityBase));

	float x2 = horizontalDistance * horizontalDistance;
	float v2 = launchSpeed * launchSpeed;
	float v4 = launchSpeed * launchSpeed * launchSpeed * launchSpeed;

	float gravMag = gravityBase.magnitude;

	float launchTest = v4 - (gravMag * ((gravMag * x2) + (2 * verticalDistance * v2)));

	Vector3[] launch = new Vector3[2];

	if(launchTest < 0)
	{
		launch[0] = (horizontal.normalized * launchSpeed * Mathf.Cos(45.0f * Mathf.Deg2Rad)) - (gravityBase.normalized * launchSpeed * Mathf.Sin(45.0f * Mathf.Deg2Rad));
		launch[1] = (horizontal.normalized * launchSpeed * Mathf.Cos(45.0f * Mathf.Deg2Rad)) - (gravityBase.normalized * launchSpeed * Mathf.Sin(45.0f * Mathf.Deg2Rad));
	}
	else
	{
		float[] tanAngle = new float[2];
		tanAngle[0] = (v2 - Mathf.Sqrt(v4 - gravMag * ((gravMag * x2) + (2 * verticalDistance * v2)))) / (gravMag * horizontalDistance);
		tanAngle[1] = (v2 + Mathf.Sqrt(v4 - gravMag * ((gravMag * x2) + (2 * verticalDistance * v2)))) / (gravMag * horizontalDistance);

		float[] finalAngle = new float[2];
		finalAngle[0] = Mathf.Atan(tanAngle[0]);
		finalAngle[1] = Mathf.Atan(tanAngle[1]);
		launch[0] = (horizontal.normalized * launchSpeed * Mathf.Cos(finalAngle[0])) - (gravityBase.normalized * launchSpeed * Mathf.Sin(finalAngle[0]));
		launch[1] = (horizontal.normalized * launchSpeed * Mathf.Cos(finalAngle[1])) - (gravityBase.normalized * launchSpeed * Mathf.Sin(finalAngle[1]));
	}

	return launch;
}

And, the associated helper functions:

public static Vector3 GetHorizontalVector(Vector3 AtoB, Vector3 gravityBase)
{
	Vector3 output;
	Vector3 perpendicular = Vector3.Cross(AtoB, gravityBase);
	perpendicular = Vector3.Cross(gravityBase, perpendicular);
	output = Vector3.Project(AtoB, perpendicular);
	return output;
}

public static Vector3 GetVerticalVector(Vector3 AtoB, Vector3 gravityBase)
{
	Vector3 output;
	output = Vector3.Project(AtoB, gravityBase);
	return output;
}

Once you have the launch vector calculated, you can make use of the basic velocity and drag formulas addressed above and calculate where the object will be located at various points in time during its flight. The time to destination can be calculated for each (and, obviously, is the condition for the first), therefore, in order to determine how many calculations are necessary to create an accurate path:

// Time
totalTime = timeToTarget;

// Angle
totalTime = horizontalDistance / (angleX * destSpeed);

// Speed
totalTime = horizontalDistance / (launchSpeed * Mathf.Cos(finalAngle));

calculations = (int)(totalTime / Time.fixedDeltaTime);

This will determine how many calculations of the velocity/position/drag updates will be necessary in order to display a perfectly accurate path based on the launch vector and total time estimate.

Aaaaaaanyway, I hope this gets you started in the direction you’d like to go, and I really wish I’d had some of this information on hand sooner when I first started working at this!

Edit [2/20/2023]: Cleaned up erroneous text filter ($$anonymous$$) and cached degree-to-radian angle multiplication(s)

How to do this depends on what your variables and constants are. I assume the angle is variable and the launch speed and downward acceleration are constants. I once struggled with this problem and, like you, searched most of the internet for a solution. Eventually, I asked a professional physicist, who gave me this: https://plus.google.com/108587811515961406246/posts/53ZMeNFhJnX Convert the formula into script code, and use it to find the x-component of your angle. You will need to multiply the result by -1. To draw the trajectory, fire a harmless projectile with a Trail Renderer or something similar. To speed up this projectile, multiply the launch speed by x, disable gravity, add a Constant Force, and set the force to gmx/f. g is the acceleration due to gravity. m is Mass. f is Fixed Timestep.

This guy has really great sollution with only 95lines of code. However it’s 2D, but you can easily apply for 3d.
Link: Unity Projectile Trajectory Prediction With Collision Detection Tutorial - YouTube