I am trying to calculate the launch velocity of a ball that needs to land at a certain position in 3D space. I know the height the ball will be launched from, the position it needs to land and the initial magnitude of the velocity.
I want to produce a Vector3
that I can use with AddForce
that propels the ball and lands on the target.
I have the following function that I use to work out the launch angle, based on equations in this video:
private Vector3 GetLaunchAngle(float speed)
{
//Calculate angle to z-axis
float height = transform.position.y; //Height from 0
float distance = Vector3.Distance(new Vector3(transform.position.x, 0, transform.position.z), new Vector3(aim.transform.position.x, 0, aim.transform.position.z)); //Distance to target (aim)
float angle;
Vector3 launchAngle = Vector3.forward;
float quadB = 0.5f * Physics.gravity.y * (distance / speed); //B in quadratic equation = 1/2g * (d/v)
float quadC = quadB + height; //C in quadratic equation
float tanAngle1 = (-quadB + Mathf.Sqrt((quadB * quadB) - (4 * distance * quadC))) / (2 * distance); //Calculate + in quadratic equation
float tanAngle2 = (-quadB - Mathf.Sqrt((quadB * quadB) - (4 * distance * quadC))) / (2 * distance); //Calculate - in quadratic equation
//Calculate both possibilities of angle
float angle1 = Mathf.Rad2Deg * Mathf.Atan(tanAngle1);
float angle2 = Mathf.Rad2Deg * Mathf.Atan(tanAngle2);
//If angle 1 is invalid
if (angle1 > 90 || angle1 < -90 || float.IsNaN(angle1))
{
angle = angle2;
}
//If angle 2 is invalid
else if (angle2 > 90 || angle2 < -90 || float.IsNaN(angle2))
{
angle = angle1;
}
//If both are valid get min angle
else if (!float.IsNaN(angle1) && !float.IsNaN(angle2))
{
angle = Math.Min(angle1, angle2);
}
else
{
angle = float.NaN;
}
//Try to rotate correct angles
if (!float.IsNaN(angle))
{
//Apply x-axis rotation
launchAngle = Quaternion.AngleAxis(angle, Vector3.left) * launchAngle;
//Calculate (x,y) angle to target
float angleTarget = Vector2.Angle(new Vector2(transform.position.x, transform.position.z), new Vector2(aim.transform.position.x, aim.transform.position.z));
//Set left/right rotation
angleTarget = angleTarget > 90 ? angleTarget - 180 : angleTarget;
//Apply y-axis rotation
launchAngle = Quaternion.AngleAxis(angleTarget, Vector3.up) * launchAngle;
}
else
{
launchAngle = Vector3.forward;
}
return launchAngle;
}
The main things that are wrong with this are, the (x,z) direction is off (see screenshot 1 below). I’m sure that the below is not the best way to do this:
float angleTarget = Vector2.Angle(new Vector2(transform.position.x, transform.position.z), new Vector2(aim.transform.position.x, aim.transform.position.z));
//Set left/right rotation
angleTarget = angleTarget > 90 ? angleTarget - 180 : angleTarget;
//Apply y-axis rotation
launchAngle = Quaternion.AngleAxis(angleTarget, Vector3.up) * launchAngle;
Also, I think that the calculated angle of incline is correct but I am open to other ideas around that.
Final issue is with the rigidbody.AddForce(launchAngle * speed);
. So the speed is used to calculate the launch angle, but when used as the impulse for the force it barely moves, it looks like screenshot 2. I am using the ratio of 1 unit to 1m in Unity and therefore used a value of 35 for the speed (assuming this to mean 35m/s or approximately 80mph). Upon keeping the speed for the calculation of the angle but changing the impulse of the force to 1000, the ball moves like screenshot 3. How do you get the correct impulse on the rigidbody?
What is the best method for this?
EDIT: I have changed the AddForce
to use the different ForceMode
values with speed
(35). Impulse
and VelocityChange
look like screens 4 & 5, it has improved the look of the arc, but it is still out (Long and left).
Force
and Acceleration
looks like screenshot 2.
Screenshot 1
Screenshot 2
Screenshot 3
Screenshot 4
Screenshot 5