Shoot a Moving Target in Unreal Engine 5 with C++
Let's use the quadratic formula to aim ahead and shoot a moving target.

Game Engine
Unreal Engine 5.5.3
IDE
Rider 2024.3.6
Project Name
MyProject
OS
macOS Sequoia 15.3.1
Shooting a moving target consists of knowing the target's velocity to determine its future location so we can rotate towards the position and trigger a projectile to meet it at it's destination. The quadratic formula along with some simple math is required to make this happen, I'll provide some links to other posts of individuals who have done an awesome job writing about the subject.
Helpful Links and Posts
- The Quadratic Formula
- Dot Product
- Stack Exchange Shoot a Target Algorithm
- PlayTech's Post about aiming at a moving target
- Wonderful YouTube video about shooting at a moving target in Godot
- Direct Link (starting at the explanation) to the function created in the above mentioned Godot Video. The links can also be found the video's description.
In this example we'll have a stationary turret that locates a moving character and a fires projectile to hit the actor in route.
To start, I created a MyHelper
class to handle the aiming logic (potentially adding this to a plugin in the future). This will help separate the logic from the actor classes to help us isolate the algorithm. The MyHelper
class is an UObject
that will server to provide helper functions to other resources in the project.
MyHelpers.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "MyHelpers.generated.h"
UCLASS()
class MYPROJECT_API UMyHelpers : public UObject
{
GENERATED_BODY()
public:
UFUNCTION()
static FVector AimAheadLocation(FVector ActorLocation, float ProjectileSpeed, FVector TargetLocation, FVector TargetVelocity);
UFUNCTION()
static FRotator LookAtAimAheadLocation(FVector ActorLocation, float ProjectileSpeed, FVector TargetLocation, FVector TargetVelocity);
};
MyHelpers.cpp
#include "MyHelpers.h"
#include "Kismet/KismetMathLibrary.h"
FVector UMyHelpers::AimAheadLocation(FVector ActorLocation, float ProjectileSpeed, FVector TargetLocation, FVector TargetVelocity)
{
/** Helpful variables to assist with A, B, and C */
const FVector LocationDelta = TargetLocation - ActorLocation;
const float VelocityDot = FVector::DotProduct(TargetVelocity, TargetVelocity);
const float SpeedSqrd = FMath::Pow(ProjectileSpeed, 2.0f);
/** Create A, B, and C for the Quadratic Formula */
const float A = SpeedSqrd - VelocityDot; // Speed Delta
const float B = 2.f * FVector::DotProduct(TargetVelocity, LocationDelta);
const float C = FVector::DotProduct(LocationDelta, LocationDelta);
const float D = B*B + 4.f*A*C; // Discriminant (using addition rather than subtraction to negate negative values)
if (D >= 0.f)
{
const float T = (B + FMath::Sqrt(D)) / (2.f*A);
const FVector AimLocation = TargetLocation + (TargetVelocity * T);
return AimLocation;
}
return TargetLocation;
}
FRotator UMyHelpers::LookAtAimAheadLocation(FVector ActorLocation, float ProjectileSpeed, FVector TargetLocation, FVector TargetVelocity)
{
FVector AimLocation = AimAheadLocation(ActorLocation, ProjectileSpeed, TargetLocation, TargetVelocity);
return UKismetMathLibrary::FindLookAtRotation(ActorLocation, AimLocation);
}
The AimAheadLocation
function is responsible for finding the target's future location in world space.
AimAheadLocation's Parameters
ActorLocation
: The actor's world space location. In this example it is the turret's location. This could also be a muzzle location or wherever the projectile will initially spawn.ProjectileSpeed
: The projectile's speed. Here it will be the speed of the projectile in which the the turret will be firing. In other cases this might be a bullet, missile, grenade, rock, etc, anything.TargetLocation
: The target's current location. We'll need the target's current location so we can determine where it will be in the future.TargetVelocity
: The target's current velocity. The velocity will give us the speed and direction in which the target is moving so we can plan accordingly. We are assuming a constant rate not accounting for acceleration.
I start the function by creating some helpful variables to assist with the quadratic formula. We'll want to know the delta between the two actors/objects to help with getting dot product values in the following lines. The VelocityDot
value is simply the TargetVelocity
squared or it's dot product and then speed squared is simply there to hopefully create cleaner syntax.
With our helper variables created we can start establishing A
, B
, and C
(and the discriminant) for the quadratic formula. A
is SpeedSqrd - VelocityDot
, B is 2.f * FVector::DotProduct(TargetVelocity, LocationDelta)
and C equals FVector::DotProduct(LocationDelta, LocationDelta)
. We're now able to create D
(the discriminant) equal to B*B + 4.f*A*C
. Some of the negative values have been cancelled out for brevity.
Since a negative number results in an impossible square root, if D
is greater than zero we'll proceed with the quadratic formula. We find T
(time) with T = (B + FMath::Sqrt(D)) / (2.f*A)
and then we can determine the exact location when our projectile will intersect with the moving target. The destination vector is found via TargetLocation + (TargetVelocity * T)
and that new value is then returned as the result of the function. We can now use that new value in future functions.
Please refer to the links mentioned at the start of the post for more comprehensive explanations of the math.
Next, I'm going to use the LookAtAimAheadLocation
in my turret class to rotate my actor to the character's destination. Adjust the code as you see fit, I might change it in the future, but currently the separation of additional functions is to isolate concerns. The turret in this example is spawning the projectile directly from origin while ignoring all collisions. This technique will vary with each project. For example, when you need to spawn the projectile from a muzzle location while also being able to receive damage, we'll need to adjust the code a little bit, but a lot of the logic will remain the same. Below is how MyEnemyTurret
is using the UMyHelpers::LookAtAimAheadLocation
. There's a lot of additional code in the MyEnemyTurret
class to account for acitvation and triggers inside the GD Tactics Gym level.
MyEnemyTurret.cpp
AMyEnemyTurret::AMyEnemyTurret()
{
PrimaryActorTick.bCanEverTick = true;
SetActorTickInterval(2.f);
}
...
void AMyEnemyTurret::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (IsValid(MyMovingCharacter))
{
...
FVector ActorLocation = GetActorLocation();
FVector TargetLocation = MyMovingCharacter->GetActorLocation();
FVector TargetVelocity = MyMovingCharacter->GetVelocity();
float ProjectileSpeed = FMath::RandRange(1000.f, 3000.f);
FRotator NewRotation = UMyHelpers::LookAtAimAheadLocation(ActorLocation, ProjectileSpeed, TargetLocation, TargetVelocity);
SetActorRotation(NewRotation);
ShootProjectile(ProjectileSpeed);
}
}
In this example if the target IsValid
we'll get the turret's location, the target's location, the target's velocity, and to have fun we'll set a random speed for the projectile to test different speeds. We then run UMyHelpers::LookAtAimAheadLocation
to get the FRotator
that will point the turret in the exact location of where the target and projectile will intersect. Finally, we trigger ShootProjectile
while passing the randomly generated ProjectileSpeed
as the only argument.
MyEnemyTurret.cpp
...
void AMyEnemyTurret::ShootProjectile(float ProjectileSpeed)
{
if (MyTurretProjectileClass != nullptr)
{
UWorld* const World = GetWorld();
if (World != nullptr)
{
FTransform SpawnTransform(GetActorTransform());
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AMyTurretProjectile* MyProjectile = World->SpawnActorDeferred<AMyTurretProjectile>(MyTurretProjectileClass, SpawnTransform, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (MyProjectile)
{
// We can now access class variables before spawning
MyProjectile->ProjectileMovement->InitialSpeed = ProjectileSpeed;
MyProjectile->ProjectileMovement->MaxSpeed = ProjectileSpeed;
UGameplayStatics::FinishSpawningActor(MyProjectile, SpawnTransform);
}
}
}
}
In ShootProjectile
we use SpawnActorDeferred
to give us the ability to set the projectile speed so it matches what we used in the UMyHelpers::LookAtAimAheadLocation
function. Setting the projectile speed in the parent is likely bad practice and we shouldn't do it, but to keep things simple and since this just an example, this technique allows us to have some fun testing different values while ensuring the functions are working regardless of values are set for speed or velocity.

Sometimes the projectile will miss due to the character's acceleration. The turret is set on a two second timer and by chance it might record the actor's current velocity while it's still accelerating. I tried to negate acceleration in the character's movement component by making it equal to 20480
, but nothing is perfect.
Definitely check out the GD Tactics Unreal repo for the full example. I make Blueprints out the classes made in this example, and set up the actors to activate with an interface. I might write about the moving character in the future because instead of spawning Manny every time I just re-use him every time he dies.
Hopefully this helped, I know I had re-learn some things and look at some of my old code to remember how hit a moving target.