public struct FiringSolution { public float timeToHit; public Vector3 projectileVelocity; public bool validSolutionExists; } public struct FiringParameters { public float projectileSpeed; public Vector3 relativeTargetPosition; public Vector3 targetVelocity; } public static FiringSolution GetFiringSolution(FiringParameters parameters) { var vDotDeltaP = Vector3.Dot(parameters.targetVelocity, parameters.relativeTargetPosition); var squarePMagnitude = parameters.relativeTargetPosition.sqrMagnitude; var squareVMagnitudeLessSquareSpeed = parameters.targetVelocity.sqrMagnitude - (parameters.projectileSpeed * parameters.projectileSpeed); var tSolution = new QuadraticSolution(squareVMagnitudeLessSquareSpeed, 2 * vDotDeltaP, squarePMagnitude); if (!tSolution.hasSolution) { return new FiringSolution { validSolutionExists = false }; } var chosenT = math.max(tSolution.solutionOne, tSolution.solutionTwo); if (chosenT < 0) { return new FiringSolution { validSolutionExists = false }; } var projectedTargetPosition = parameters.relativeTargetPosition + parameters.targetVelocity * chosenT; // firing origin is assumed to be (0, 0, 0) var firingVector = projectedTargetPosition.normalized * parameters.projectileSpeed; return new FiringSolution { timeToHit = chosenT, projectileVelocity = firingVector, validSolutionExists = true }; } private struct QuadraticSolution { public float solutionOne; public float solutionTwo; public bool hasSolution; public QuadraticSolution(float a, float b, float c) { var root = b * b - 4 * a * c; if (root < 0) { solutionOne = float.NaN; solutionTwo = float.NaN; hasSolution = false; return; } root = math.sqrt(root); solutionOne = (-b + root) / (2 * a); solutionTwo = (-b - root) / (2 * a); hasSolution = true; } }