1 min read

Simple bullet spread for AI

Simple bullet spread for AI
Photo by Tengyart / Unsplash

Good AI is not fun AI

It’s easy to make your AI bots accurate. Target minus source and you have your aim vector. But where’s the fun in being shot by the AI with every bullet? Here’s a simple suggestion for improving this by making the AI less accurate.

The idea is to generate random projectile paths within a circle of controllable size around the perfect target location. We do this as follows:

  1. Find the perfect projectile direction
  2. Aim n units above the perfect target and find the direction vector. We call these n units our MaxBulletSpread and expose it to the editor so it can be tweaked easily.
  3. Find the angle between the perfect projectile direction vector and the one pointing above the target.
  4. Randomize a number (or two) between -angle and angle and add these to the pitch and yaw of the perfect projectile direction.

The above gives us random trajectories inside a circle of radius MaxBulletSpread around the target. This can then be modified based on AI skill level, distance to target, etc. to have some control over just how inaccurate the AI is.

See example code for UE5 below.

// variable for the size of our spread. expose this to the editor for easier tweaking
float MaxBulletSpread = 100.f;

// calculate perfect projectile direction
FVector MuzzleLocation = Character->GetMesh()->GetSocketLocation(SocketName);
FVector ProjectileDirection = TargetActor->GetActorLocation() - MuzzleLocation;

// calculate the direction of the projectile if we were to fire it above the target at max spread value
FVector ActorLocationWithSpread = TargetActor->GetActorLocation();
ActorLocationWithSpread.Z += MaxBulletSpread;
FVector ProjectileDirectionWithSpread = ActorLocationWithSpread - MuzzleLocation;

// find what the angle is for this max spread
ProjectileDirectionWithSpread.Normalize();
ProjectileDirection.Normalize();
const float MaxSpreadAngle = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(ProjectileDirection, ProjectileDirectionWithSpread)));

// create a new rotation to act as the direction of the projectile by randomizing an increase/decrease in pitch and yaw based on the max spread angle
FRotator NewProjectileRotation = ProjectileDirection.Rotation();
NewProjectileRotation.Pitch += FMath::RandRange(-MaxSpreadAngle, MaxSpreadAngle);
NewProjectileRotation.Yaw += FMath::RandRange(-MaxSpreadAngle, MaxSpreadAngle);

// spawn projectile
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
Params.Instigator = Character;
GetWorld()->SpawnActor<AActor>(ProjectileClass, MuzzleLocation, NewProjectileRotation, Params);