/// <summary> /// Calculates an approximate leading target point to ensure a ballistic projectile will impact a moving target assuming a given launch angle. /// Assumes constant target velocity and constant projectile speed after launch. Precision can be adjusted parametrically. /// Uses vertical gravity constant defined in project Physics settings. /// </summary> /// <param name="firePosition">Starting point of the projectile.</param> /// <param name="targetPosition">The current position of the intended target.</param> /// <param name="targetVelocity">Vector representing the velocity of the intended target.</param> /// <param name="launchAngle">The angle at which the projectile is to be launched.</param> /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param> /// <param name="gravity">Gravitational constant (Vertical only. Positive = down)</param> /// <param name="precision">Number of iterations to approximate the correct position. Higher precision is better for faster targets.</param> /// <returns>Vector3 representing the leading target point. Vector3.zero if no solution.</returns> public static Vector3 CalculateBallisticLeadingTargetPointWithAngle(Vector3 firePosition, Vector3 targetPosition, Vector3 targetVelocity, float launchAngle, BallisticArcHeight arcHeight, float gravity, int precision = 2) { // No precision means no leading, so we early-out. if (precision <= 1) { return(targetPosition); } Vector3 testPosition = targetPosition; for (int i = 0; i < precision; i++) { float launchSpeed = CalculateBallisticFireVectorFromAngle(firePosition, testPosition, launchAngle, gravity) .magnitude; float impactTime = CalculateBallisticFlightTime(firePosition, testPosition, launchSpeed, launchAngle, gravity); if (float.IsNaN(launchSpeed) || float.IsNaN(impactTime)) { return(Vector3.zero); } testPosition = targetPosition + (targetVelocity * impactTime); } return(testPosition); }
/// <summary> /// Calculates an approximate leading target point to ensure a ballistic projectile will impact a moving target assuming a given launch speed. /// Assumes constant target velocity and constant projectile speed after launch. Precision can be adjusted parametrically. /// Uses vertical gravity constant defined in project Physics settings. /// </summary> /// <param name="firePosition">Starting point of the projectile.</param> /// <param name="targetPosition">The current position of the intended target.</param> /// <param name="targetVelocity">Vector representing the velocity of the intended target.</param> /// <param name="launchSpeed">Initial speed of the projectile.</param> /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param> /// <param name="precision">Number of iterations to approximate the correct position. Higher precision is better for faster targets.</param> /// <returns>Vector3 representing the leading target point. Vector3.zero if no solution.</returns> public static Vector3 CalculateBallisticLeadingTargetPointWithSpeed(Vector3 firePosition, Vector3 targetPosition, Vector3 targetVelocity, float launchSpeed, BallisticArcHeight arcHeight, int precision = 2) { return(CalculateBallisticLeadingTargetPointWithSpeed(firePosition, targetPosition, targetVelocity, launchSpeed, arcHeight, Mathf.Abs(Physics.gravity.y), precision)); }
/// <summary> /// Calculates the angle at which a projectile with a given initial speed must be fired to impact a target. /// </summary> /// <param name="firePosition">Position from which the projectile is fired</param> /// <param name="targetPosition">Intended target position.</param> /// <param name="launchSpeed">The speed that the projectile is launched at.</param> /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param> /// <param name="gravity">Gravitational constant (Vertical only. Positive = down)</param> /// <returns>The required launch angle in degrees. NaN if no valid solution.</returns> public static float CalculateBallisticFireAngle(Vector3 firePosition, Vector3 targetPosition, float launchSpeed, BallisticArcHeight arcHeight, float gravity) { Vector3 target = targetPosition; target.y = firePosition.y; Vector3 toTarget = target - firePosition; float targetDistance = toTarget.magnitude; float relativeY = targetPosition.y - firePosition.y; float vSquared = launchSpeed * launchSpeed; // If the distance to our target is zero, we can assume it's right on top of us (or that we're our own target). if (Mathf.Approximately(targetDistance, 0f)) { // If we're preferring a high-angle shot, we just fire straight up. if (arcHeight == BallisticArcHeight.UseHigh || arcHeight == BallisticArcHeight.PreferHigh) { return(90f); } // If we're doing a low-angle direct shot, we tweak our angle based on relative height of target. if (relativeY > 0) { return(90f); } if (relativeY < 0) { return(-90f); } } float b = Mathf.Sqrt((vSquared * vSquared) - (gravity * ((gravity * (targetDistance * targetDistance)) + (2 * relativeY * vSquared)))); // The "underarm", parabolic arc angle float theta1 = Mathf.Atan((vSquared + b) / (gravity * targetDistance)); // The "overarm", direct arc angle float theta2 = Mathf.Atan((vSquared - b) / (gravity * targetDistance)); bool theta1Nan = float.IsNaN(theta1); bool theta2Nan = float.IsNaN(theta2); // If both are invalid, we early-out with a NaN to indicate no solution. if (theta1Nan && theta2Nan) { return(float.NaN); } // We'll init with the parabolic arc. float returnTheta = theta1; // If we want to return the direct arc if (arcHeight == BallisticArcHeight.UseLow) { returnTheta = theta2; } // If we want to return theta1 wherever valid, but will settle for theta2 if theta1 is invalid if (arcHeight == BallisticArcHeight.PreferHigh) { returnTheta = theta1Nan ? theta2 : theta1; } // If we want to return theta2 wherever valid, but will settle for theta1 if theta2 is invalid if (arcHeight == BallisticArcHeight.PreferLow) { returnTheta = theta2Nan ? theta1 : theta2; } return(returnTheta * Mathf.Rad2Deg); }
/// <summary> /// Calculates the angle at which a projectile with a given initial speed must be fired to impact a target. /// Uses vertical gravity constant defined in project Physics settings. /// </summary> /// <param name="firePosition">Position from which the projectile is fired</param> /// <param name="targetPosition">Intended target position.</param> /// <param name="launchSpeed">The speed that the projectile is launched at.</param> /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param> /// <returns>The required launch angle in degrees. NaN if no valid solution.</returns> public static float CalculateBallisticFireAngle(Vector3 firePosition, Vector3 targetPosition, float launchSpeed, BallisticArcHeight arcHeight) { return(CalculateBallisticFireAngle(firePosition, targetPosition, launchSpeed, arcHeight, Mathf.Abs(Physics.gravity.y))); }
/// <summary> /// Calculates the launch velocity for a parabolic-path projectile to hit a given target point when /// fired at a given speed. /// </summary> /// <param name="firePosition">Position from which the projectile is fired.</param> /// <param name="targetPosition">Intended target position.</param> /// <param name="launchSpeed">The speed that the projectile is launched at.</param> /// <param name="arcHeight">Preference between parabolic ("underhand") or direct ("overhand") projectile arc.</param> /// <param name="gravity">Gravitational constant (Vertical only. Positive = down)</param> /// <returns>Vector3 representing launch launchSpeed to hit the target. Vector3.zero if no solution.</returns> public static Vector3 CalculateBallisticFireVectorFromVelocity(Vector3 firePosition, Vector3 targetPosition, float launchSpeed, BallisticArcHeight arcHeight, float gravity) { float theta = CalculateBallisticFireAngle(firePosition, targetPosition, launchSpeed, arcHeight, gravity); // If our angle is impossible, we early-out. if (float.IsNaN(theta)) { return(Vector3.zero); } Vector3 target = targetPosition; target.y = firePosition.y; Vector3 toTarget = target - firePosition; float targetDistance = toTarget.magnitude; Vector3 aimVector = Vector3.forward; if (targetDistance > 0f) { // Flatten aim vector so we can rotate it aimVector = toTarget / targetDistance; aimVector.y = 0; } Vector3 rotAxis = Vector3.Cross(aimVector, Vector3.up); Quaternion rotation = Quaternion.AngleAxis(theta, rotAxis); aimVector = rotation * aimVector.normalized; return(aimVector * launchSpeed); }