Unreal 5.2 – Zombie FPS Prototype


Building a first-person shooter from scratch: weapon switching, hit detection, ammo management, pickups, and zombie AI in Unreal Engine 5.2.


This project is a first-person shooter prototype built entirely in Blueprints with Unreal Engine 5.2, developed following this Udemy course. It implements the complete core loop of the FPS genre — movement, three distinct weapons, ammo and health management, pickups, and zombie enemies — using Quixel assets for the environment. Compared to the survival horror prototype, this project inverts the relationship between player and AI: here the player is armed and the zombies are the threat to be eliminated, which changes the design requirements of every system involved.

You can watch the prototype in action here: YouTube


First-Person Camera and Arms Rig

The FPS perspective requires a specific actor setup that differs from third-person games. The player character has two distinct visual layers: the first-person arms mesh (visible only to the local player, rendered on a separate depth layer to prevent clipping through geometry) and the full body mesh (used for shadow casting and third-person visibility if needed). The camera is attached to the head socket of the character, moving with it but controlled independently for look input.

This separation between the camera-attached arms and the world-space body is a fundamental FPS architecture pattern. It allows the arms and weapon to always render correctly relative to the camera — at a fixed screen-space depth — while the full body interacts normally with the world for collision and animation purposes.


Weapon System: Three Weapons, Shared Architecture

The three weapons — pistol, shotgun, and rifle — share enough behavior that a single base weapon class makes sense, with each weapon type configuring its own parameters. The base class handles the common behavior: fire rate, ammo count, reload logic, fire animation, muzzle flash, and sound. Each weapon subclass overrides what’s specific to it: damage, spread, number of pellets, range.

The architectural decision that matters most here is whether weapons are actor components on the player character or separate actors that the character holds. Component-based weapons (attached as components) keep the weapon tightly integrated with the character but make it harder to drop, throw, or interact with weapons as world objects. Actor-based weapons (spawned actors attached to a socket) treat weapons as independent entities that happen to be held, which is more flexible but requires managing the attachment and detachment lifecycle carefully.

For a prototype of this scope, either approach works. The more important concern is keeping the weapon’s data — ammo count, fire state, reload state — as the source of truth that both the weapon logic and the HUD read from, rather than duplicating it across both.

Weapon switching requires tracking which weapon is currently active and handling the transition: deactivating the current weapon (hiding its mesh, disabling its input response), activating the new one, and updating the HUD to reflect the change. The switch should be instantaneous at the data level but can have a brief visual transition — a holster animation followed by a draw animation — to feel grounded.


Hit Detection: Hitscan and Spread

The pistol and rifle use hitscan — a line trace fired from the camera at the moment of shooting that instantly registers a hit at whatever it intersects. Hitscan is the correct approach for weapons that fire fast-moving projectiles where travel time is negligible at gameplay distances. The line trace result provides the hit location, the hit actor, and the hit normal — enough information to apply damage, spawn a decal at the impact point, and trigger a hit reaction on the target.

The shotgun introduces spread: instead of a single line trace, it fires multiple traces simultaneously, each with a random offset within a cone angle. The number of pellets and the spread angle define the weapon’s behavior — a tight spread at close range lands most pellets, while the same spread at long range produces mostly misses. This is implemented as a loop that generates N random directions within the spread cone and fires N line traces, applying damage for each hit independently.

Accuracy degradation — spread increasing while moving or after sustained fire — adds mechanical depth without requiring additional weapon logic. It’s a modifier applied to the spread angle based on the character’s velocity and recent fire rate, making precision a reward for controlled play.


Ammo Management and Reload

Each weapon tracks two ammo values: rounds in the current magazine and total reserve ammo. Firing decrements the magazine count. Reloading transfers ammo from the reserve to the magazine, capped at the magazine size. The reserve is decremented by the amount transferred, not by the magazine size — partial reloads carry over correctly.

The reload has a time cost enforced by a timer that blocks firing during the animation. This is one of the cases where animation and gameplay state need to be synchronized carefully: the reload animation should not be interruptible by a fire input, and the ammo values should update at the animation’s completion event (the moment the magazine snaps in) rather than at the start of the reload.

Empty magazine handling is a deliberate UX decision: auto-reload when firing on empty versus requiring explicit input. Auto-reload is more forgiving and common in modern FPS games; manual reload creates more tension in high-pressure situations.


Pickups: Weapons, Health Packs, and Ammo

The three pickup types — weapons, health packs, and ammo — share a common structure: a world actor with a collision overlap that triggers on player contact, applies an effect, and destroys itself. The difference is in the effect applied:

Weapon pickups add the weapon to the player’s inventory if not already carried, or refill its ammo if the player already has it. This dual behavior prevents inventory clutter in a simple prototype without a full inventory system.

Health pack pickups increment the player’s health value, clamped to the maximum. The pickup’s heal amount is a configurable parameter on the actor, allowing different heal values for different pickup sizes without subclassing.

Ammo pickups are weapon-type specific — picking up pistol ammo only refills pistol reserve ammo. This requires the ammo pickup to know which weapon type it corresponds to, which can be encoded as an enum on the pickup actor and matched against the player’s carried weapons.

All three use the same Blueprint Interface for their interaction with the player — the player character calls the pickup interface function on overlap, and each pickup implements its own response. This is consistent with the interaction pattern used across the other projects.


Zombie AI

The zombie AI operates on the same Behavior Tree / Blackboard foundation as the survival horror monster, but tuned for combat rather than evasion. The behavioral difference is that zombies are expected to be killed — they need health, a death state, and a death animation — while the survival horror monster was effectively immortal.

The zombie patrol and detection logic is similar: wander or patrol until the player is detected, then pursue. The key difference is the attack state: once within melee range, the zombie plays an attack animation and applies damage to the player on the animation’s hit frame, using an animation notify to time the damage application precisely. This is the same notify mechanism described in the Paper2D context — the attack doesn’t deal damage when the animation starts or ends, but at the specific frame where the strike connects visually.

Multiple zombies operating simultaneously requires the NavMesh to handle path planning for each independently. UE5’s NavMesh handles this well for moderate numbers of agents, but dense crowds of zombies can produce visible path conflicts where agents block each other. For a prototype, this is acceptable; a production system would use crowd simulation (Detour Crowd) to add separation and flow behavior between agents.


Reflection

The zombie FPS is the most complete gameplay loop of the Blueprint projects in this series — it has a clear win/lose condition, resource management through ammo, escalating threat through zombie waves, and a full weapon system with meaningful choice between the three options. These are the ingredients of a functional game, not just a technical demo.

The weapon system architecture is the most transferable takeaway: a base class with shared behavior, subclasses for type-specific configuration, and a clean separation between weapon data and weapon presentation. This pattern scales directly to more complex weapon systems — modular attachments, alternate fire modes, weapon degradation — without requiring a structural redesign.

In C++, the hitscan implementation maps to GetWorld()->LineTraceSingleByChannel(), the spread to a loop generating random unit vectors within a cone using FMath::VRandCone(), and the weapon base class to a UActorComponent subclass with UPROPERTY fields for all configurable parameters. The Blueprint prototype makes the data flow visible in a way that informs the C++ design directly.

Leave a comment

Create a website or blog at WordPress.com

Up ↑