Indie · UE4 · First Game · C++ · 2021

Top-Down Shooter

Top-Down Shooter

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.

Unreal Engine 4 C++ Blueprints AI Perception Projectile Physics
What I Built
  • Enemy AIEnemyMove.cpp uses MoveToActor with tuned acceptance radii; enemies flank the player by sampling random offsets around the target.
  • Projectile systemBullet.cpp uses UE4's ProjectileMovementComponent; hit results branch on surface type for ricochets vs. penetration.
  • Bomb mechanicsBomb.cpp radial overlap query on detonation; impulse applied to all physics bodies within blast radius, scaled by distance.
  • Ranged turrets — sphere overlap detects enemies; turret rotates toward nearest target and fires on a configurable cooldown.
  • Wave manager — spawns enemy groups at intervals; difficulty scales with wave index via a configurable curve asset.

Enemy AI Movement

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.

EnemyMove.cpp
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();
    }
}
Bomb — Radial Blast

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.

Bomb.cpp — Detonate()
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();
}
Release December 15, 2021 Engine Unreal Engine 4 Language C++ Category Action · Shooter Source github.com/khaled71612000 ↗
Connect