
My first Unreal Engine game — a top-down wave shooter built from scratch in UE4 C++. Features enemy AI wave management, multiple weapon types (guns + bombs), placeable ranged turrets, and multi-level progression. The whole project was a deliberate deep dive into UE4's actor/component model, projectile physics, and AI perception.
EnemyMove.cpp uses MoveToActor with tuned acceptance radii; enemies flank the player by sampling random offsets around the target.Bullet.cpp uses UE4's ProjectileMovementComponent; hit results branch on surface type for ricochets vs. penetration.Bomb.cpp radial overlap query on detonation; impulse applied to all physics bodies within blast radius, scaled by distance.
Enemies pursue the player character using UE4's MoveToActor. A random lateral offset prevents all enemies stacking on the same path, creating a natural flanking spread.
void AEnemyMove::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!PlayerRef) return;
// Random lateral offset — prevents enemies stacking
const FVector Offset = FVector(
FMath::FRandRange(-FlankRadius, FlankRadius),
FMath::FRandRange(-FlankRadius, FlankRadius), 0.f);
FVector Target = PlayerRef->GetActorLocation() + Offset;
MoveToLocation(Target, AcceptanceRadius, false);
}
void AEnemyMove::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector NormalImpulse,
const FHitResult& Hit)
{
if (ACharacterKhaled* Player = Cast<ACharacterKhaled>(OtherActor))
{
Player->TakeDamage(AttackDamage, FDamageEvent(), GetController(), this);
Destroy();
}
}
On detonation an overlap sphere queries all actors within blast radius. Physics impulse is scaled by inverse distance; each actor in range also receives damage.
void ABomb::Detonate()
{
TArray<AActor*> OverlappingActors;
GetOverlappingActors(OverlappingActors);
for (AActor* Actor : OverlappingActors)
{
const float Dist = FVector::Dist(GetActorLocation(), Actor->GetActorLocation());
const float ImpulseMag = FMath::Clamp(1.f - Dist / BlastRadius, 0.f, 1.f)
* MaxImpulse;
if (UPrimitiveComponent* Prim = Actor->FindComponentByClass<UPrimitiveComponent>())
if (Prim->IsSimulatingPhysics())
{
FVector Dir = (Actor->GetActorLocation() - GetActorLocation()).GetSafeNormal();
Prim->AddImpulse(Dir * ImpulseMag, NAME_None, true);
}
Actor->TakeDamage(BlastDamage * (1.f - Dist / BlastRadius),
FDamageEvent(), nullptr, this);
}
SpawnExplosionFX();
Destroy();
}