Projectile Aerodynamics models the aerodynamic forces acting on projectiles without compromising their ability to hit targets.
You can use this feature to simulate curve effect seen in sports such as football, baseball, golf (draw/fade), etc. You can also use it to simulate wind, air drag, or implement creative weapons, like boomerangs.
We will use the Aero Projectile Launcher template.
Templates are "high-level" prefabs that contain all essential components needed for a range of use cases, with no-code/low-code development capability.
Location of Aero Projectile Launcher template: .../Blobcreate/Projectile Toolkit/Templates/Aero Projectile Launcher.prefab
Drag and drop the template to the scene,
Add your projectile prefab to the inspector property Projectile Prefab,
Set the parameters in the inspector to your liking,
To control the launcher with scripts, make sure Controlled By Another Script is turned on in the inspector (otherwise, the template has a default behavior: launching when the left mouse button is pressed),
Control the launcher by calling AimAt(...) and Launch().
Example code
xusing Blobcreate.ProjectileToolkit.Aerodynamics;
public class LaunchController : MonoBehaviour{ [SerializeField] AeroProjectileLauncher launcher; [SerializeField] Transform target; void Update() { launcher.AimAt(target.position); if (Input.GetMouseButtonDown(0)) { launcher.Launch(); } }}

💡Tip: From the inspector, you can click on the question icon, it will open your browser and bring you directly here.
Projectile Prefab: Projectile you want to launch. The projectile must have a Rigidbody component and an AeroRuntime component attached.
Launch Point: Start position of the projectile.
Height Above Target: Highest point of the trajectory relative to the target point (end point). Internally it uses Projectile.VelocityByHeight(...) to compute launch velocity. doc link. Advanced users can modify line 98 to change the computation method.
Max Offset: Maximum displacement along local space for a curved trajectory. This shapes the horizontal movement of the trajectory.
The local space is formed by using end - start vector (ignoring y) as forward vector, and use the middle point between end and start as the origin.
The figure below is a top-down view of a scene, it explains the definition and shows two example values of maxOffset vector (you can also set a non-zero value to y axis, it changes the gravity and thus changes the overall movement speed of the projectile).

Use Callback: Whether to invoke the Action you assigned to the Action OnFinished. See "Scripting API > OnFinished".
Use Prediction: Whether to predict and display the trajectory.
Predictor: PEB Trajectory Predictor that is used for trajectory prediction and rendering. The template comes with one preconfigured.
Controlled By Another Script: Make sure this is on so that you can control the template using scripting API.
Aim at the target point.
xxxxxxxxxxpublic void AimAt(Vector3 point)
Launch the Projectile Prefab you defined in the inspector, using the velocity you calculated earlier by calling AimAt(...).
xxxxxxxxxxpublic void Launch()
Whether to predict and display the trajectory.
xxxxxxxxxxpublic bool UsePrediction { get; set; }
Custom callback that is triggered when the projectile reaches its target, providing the projectile's Rigidbody as an arg for use. Note that it is triggered after a pre-calculated time, not by collision.
xxxxxxxxxxpublic Action<Rigidbody> OnFinished { get; set; }
Other Properties
The properties in the Inspector (we described earlier in the Inspector section) are all public and you can modify them through scripts.
Note
The whole low-level reference section is intended for edge cases, most times you will only need the template and its API.
Tip
A concrete example script (the main script of the template) can be found here: .../Blobcreate/Projectile Toolkit/Core/Projectile Aerodynamics/AeroProjectileLauncher.cs
The template can handle 99% of scenarios, but sometimes you may want to implement the Projectile Aerodynamics feature into your own class for custom purpose, here's how:
1. Create a solver
In Start() or Awake(), create a new AeroSolver instance.
xxxxxxxxxxaeroSolver = new AeroSolver();2. Solve
Calculate all the data needed by calling Solve(...). You can add the code below directly into your logic or encapsulate it to a method.
xxxxxxxxxxvar offY = new Vector3(0, maxOffset.y, 0);// You can use any velocity method in the Projectile class.var vRaw = Projectile.VelocityByHeight(startPoint, targetPoint + offY, 10f);var vReal = aeroSolver.Solve(startPoint, targetPoint, maxOffset, vRaw);3. Run
This is the final step, we launch the projectile, and then set up execution of projectile aerodynamics through AeroRuntime component. And now your projectile moves in the air in curve!
xxxxxxxxxxvar myProjectile = Instantiate(projectilePrefab, startPoint, Quaternion.identity);myProjectile.AddForce(vReal + Projectile.VelocityCompensation, ForceMode.VelocityChange);
myProjectile.TryGetComponent<AeroRuntime>(out var rt);rt ??= myProjectile.gameObject.AddComponent<AeroRuntime>();rt.Configure(aeroSolver, myProjectile);rt.Run();
Options:
You can add any custom methods to the Action OnFinished, it will be called when the projectile reaches target.
xxxxxxxxxxaeroSolver.OnFinished = (ball) =>{ ball.angularVelocity = Vector3.zero; ball.velocity = new Vector3(0, 10, 0);};
If you also want to predict the trajectory, in Start(), set up a PEBTrajectoryPredictor instance (here we call it predictor), the instance will be used later:
xxxxxxxxxxvar b = Instantiate(ballPrefab, startPoint, Quaternion.identity);predictor.Simulatee = b;predictor.AeroSolverInstance = aeroSolver;b.gameObject.SetActive(false);Call the predictor when you want to predict the trajectory.
xxxxxxxxxxpredictor.LaunchVelocity = vReal + Projectile.VelocityCompensation;predictor.SimulateAndRender();
Note
The whole low-level reference section is intended for edge cases, most times you will only need the template and its API.
Projectile Aerodynamics' code is inside the namespace Blobcreate.ProjectileToolkit.Aerodynamics.
It consists of 2 classes:
AeroSolver: Pure C# class that does the computation.
AeroRuntime: Unity component that controls a projectile.
Below, the items marked with "get/set" are the properties, the items marked with a star✰ are the most important.
SolvedVelocity
xxxxxxxxxxpublic Vector3 SolvedVelocity { get; }The returned value of Solve(...) — the computed launch velocity that takes projectile aerodynamics into account. Only valid after Solve(...) has been called.
Acceleration
xxxxxxxxxxpublic Vector3 Acceleration { get; }Computed continuous acceleration that is used to conduct projectile aerodynamics.
OffsetVector
xxxxxxxxxxpublic Vector3 WorldOffset { get; }World space version of maxOffset (one parameter you passed to method Solve(...)). It is used internally to get offset target point (= target + OffsetVector).
TimeOfFlight
xxxxxxxxxxpublic float TimeOfFlight { get; }Time needed for projectile to reach endpoint. The acceleration (force) is applied during this period of time.
OnFinished
xxxxxxxxxxpublic Action<Rigidbody> OnFinished { get; set; }Custom callback that is triggered when the projectile reaches its target, providing the projectile's Rigidbody as an arg for use. Note that it is triggered after a pre-calculated time, not by collision.
✰ Solve
Computes the data required for projectile aerodynamics.
xxxxxxxxxxpublic Vector3 Solve(Vector3 start, Vector3 end, Vector3 maxOffset, Vector3 v)start: Launch point.
end: Target point.
maxOffset: Maximum displacement along local space for a curved trajectory. The local space is formed by using "end - start" vector (ignoring y) as forward vector.
v: The original launch velocity that makes the projectile move from start point to end point without taking aerodynamics into account.
returns: The modified launch velocity that takes aerodynamics into account.
ApplyAcceleration
Executes one physics update of projectile aerodynamics.
This is used internally in PEBTrajectoryPredictor class for projectile aerodynamics prediction, but for the execution applied to real projectile, we recommend using AeroRuntime class.
xxxxxxxxxxpublic bool ApplyAcceleration(Rigidbody rBody, ref float timer)rBody: The Rigidbody of the object to which the acceleration is applied.
timer: This is used as a runtime context, it records how much time has been passed since the projectile launch.
returns: Whether or not the acceleration is applied. False means the procedure is finished.
TimeOfFlight
xxxxxxxxxxpublic float TimeOfFlight { get; }Time needed for projectile to reach endpoint. The acceleration (force) is applied during this period of time.
Timer
xxxxxxxxxxpublic float Timer { get; }How much time has been passed since the projectile launched.
✰ Configure
Configure the runtime. A valid AeroSolver instance should have been called Solve(...) beforehand.
xxxxxxxxxxpublic void Configure(AeroSolver solver, Rigidbody rBody)solver: AeroSolver instance to get data from.
rBody: Rigidbody you want to control.
✰ Run
Starts the internal timer and logic (applying Acceleration, etc.).
xxxxxxxxxxpublic void Run()
Stop
Stops applying Acceleration, and resets the internal timer. Useful for replay, handling collisions, etc.
xxxxxxxxxxpublic void Stop()