In DevelopmentIndependent · 2026 – Present

Holdout

Creator & UI Technical Designer

A survival-wave FPS built as a showcase for a fully data-driven, animated night-ops HUD — every element bound to live game state, on a locked design system. Solo, in Unreal Engine 5.

Scroll
Overview

HOLDOUT is a wave-survival first-person shooter I built solo as a focused portfolio piece — but the gameplay is a deliberately lightweight backdrop. The real deliverable is the HUD: a fully data-driven, animated, night-ops interface that reacts to live game state, built on a locked design system and a clean, push-based data architecture. I chose survival-wave specifically because it generates constant, legible state changes — wave number, hostiles remaining, kills, countdowns, damage direction — so every element on screen has something real to react to, bound to a gameplay source rather than faked.

Role
Solo — UI · HUD · Motion
Engine
Unreal Engine 5 · Blueprint
Discipline
UI Technical Design
Status
In development · 2026 – Present
HOLDOUT in motion — the HUD and menu system reacting to live game state.
01

The Design System

The whole interface runs on a small, strictly-enforced token system. The governing rule is one meaning per colour — the discipline that lets it stay readable and coherent across eight screens and a live gameplay HUD.

One meaning per colour

Teal is interactive, positive, active. Red is danger, death, incoming threat. Everything else is a neutral grayscale hierarchy — white for primary values, two grays for labels and secondary text, near-black for panels and background. Nothing on screen picks up a colour it hasn’t earned.

One type system

Bahnschrift throughout — uppercase, letter-spaced — on a single type scale that governs everything from the logo down to a HUD label. Colour is stored in one convention so tokens render identically everywhere, with two documented, deliberate exceptions: HUD labels and teal are stored slightly brighter, because over-gameplay text needs more contrast than text on a flat dark menu. Knowing when to break your own system, and documenting why, is part of the craft.

Design system · Zero Protocol
One meaning per colour · clean military · data-driven
01 · Colour tokens
Teal
#4DA3A0
Interactive · good
Red
#C4536B
Danger · death
White
#D8DDE2
Primary values
Gray
#8A929C
Labels
Muted
#5F6771
Panel
#12161B
BG
#0F1216
Dark
#2A323B
02 · Type scale — Bahnschrift
HOLDOUT135 · Logo
YOU DIED90 · Hero
OPTIONS44 · Section title
[ 24 ] WAVE 0332 · HUD value
WAVE · RIFLE · NOVA_720 · HUD label
[ARROWS] NAVIGATE · [ENTER] SELECT13 · Nav hint
03 · Layout tokens
Menu button640 × 84
Overlay plate#0F1216 @ 82%
Plate rules2px teal
Cornershard / hairline
Anchoringedges → res-indep
04 · Motion
Ease-out, never linear, no bounce
e = 1 − (1 − t)²
Banner assembly5 beats
Hitmarker0.1s + fade
HUD boot-up0.09s stagger
One meaning per colour · clean military · data-drivenHUD is the deliverable
Component library
Shared controls · interaction states
Primary button
PLAY · focused
OPTIONS · default
Destructive button
QUIT · focused
QUIT · default
Slider
1.00
Filled track = teal · value readout live
Segmented selector
Low
Med
High
Epic
Active = teal border + teal label
Toggle
OnOff
Keycap
W A S DSPACEP
One focus style (teal bracket) · one destructive style (red) · shared across all screens
The title screen — one type system, teal for the action, red for the exit.
The title screen — one type system, teal for the action, red for the exit.
The same tokens on the most utilitarian screen — sliders, toggles, segmented controls.
The same tokens on the most utilitarian screen — sliders, toggles, segmented controls.
02

HUD Architecture

The HUD is built on a push-based data flow, not per-frame polling. Gameplay pushes state; the interface listens. It never reaches into gameplay, and it carries no Tick cost.

Push, don’t poll

Gameplay systems — the character, the game mode, the projectile — push state through a single Blueprint interface on the player controller, which routes to Receive functions on the HUD widget. Systems only push when something actually changes, so the HUD stays fully decoupled and any widget can subscribe without polling every frame.

Every element maps to a source

Health lerps on a receive-health call; ammo on receive-bullets; the wave counter and banner are driven live from the game mode; the hostile count decrements on the kill-hook with a red flash; the damage arc rotates toward the attacker and fades over 1.2s; the compass reads live controller yaw. Nothing is faked — each readout is bound to a real gameplay signal.

Reading the HUD
What each readout shows — and the live data behind it
K/D counterTop left
Team score — pulses on every change
CompassTop centre
Reads live controller yaw
Wave + bannerTop right
Driven live from the game mode
Hostiles leftTop right
Decrements on the kill-hook, red flash
CrosshairCentre
Spreads on fire and move · hitmarker on hit
Damage arcCentre
Rotates toward the attacker, fades over 1.2s
Health + callsignBottom left
Lerps smoothly on ReceiveHealth
Ammo + weaponBottom right
Updates on ReceiveBullets
Interactive / status Danger NeutralCorners = persistent · centre = event-driven
The live HUD — compass, wave counter, health, ammo, and damage arc, all bound to real state.
The live HUD — compass, wave counter, health, ammo, and damage arc, all bound to real state.
03

The Wave System

One small state machine in the game mode drives the whole loop — and four separate UI moments — from a single source of truth.

One source of truth

The wave manager tracks state as plain integers — current wave, enemies remaining — spawning escalating waves and timing the gaps between them. Rather than tracking a list of enemy references, I hooked enemy-death decrementing directly into the existing scoring signal, so the single “an enemy died” event drives both the score and the HUD.

Four moments, one machine

That one state machine drives the wave counter in the HUD, the incoming banner on wave start, the cleared countdown between waves, and the wave-reached stat on the death screen — so every wave-related surface always agrees, because they all read the same numbers.

Gameplay loop × UI
Each game state drives a UI reaction
Mechanic
Wave starts
spawn N enemies · N = base + escalation
UI reacts
Incoming banner + counter set
Mechanic
Combat
shoot · take damage · get hit
UI reacts
Hitmarker · health drain · damage arc · ammo
Mechanic
Enemy killed
kill-hook → score + counter decrement
UI reacts
Hostile count flash · K/D pulse
Mechanic
Wave cleared
count hits 0 · between-wave timer
UI reacts
Cleared overlay + live countdown
↺ loop · wave + 1, escalate
Fail state
Player dies
health reaches 0 · any wave
UI reacts
Game over · wave reached, K/D summary
Core loop   Fail stateOne state machine · four UI touchpoints
The death screen — the wave you reached, kills, and deaths, all from the same source.
The death screen — the wave you reached, kills, and deaths, all from the same source.
04

Motion Design

The animation language is Battlefield-restrained: fast, mechanical, hard-settling, with no bounce or overshoot. Everything runs on a math-based ease-out driven straight from the Blueprint graph.

Engineered, not faded

The signature piece is the wave-banner assembly. Instead of a single fade, the elements cascade in over roughly 0.4s: the plate wipes in, the teal rules snap outward, the “INCOMING” label drops in, then the wave number slams to a hard stop. That staggered choreography is what makes it read as engineered rather than faded.

One timing language

The same fast-in, hard-settle ease-out runs through the HUD boot-up sequence, the hitmarker, the countdown punch, and the per-kill ticks. Every overlay settles with the same weight, so the interface feels authored by one hand.

The pause overlay — bracket corners and hard-settling motion, no bounce.
The pause overlay — bracket corners and hard-settling motion, no bounce.
05

Key Decisions

The parts I am proudest of are the UI-technical judgment calls — where a real constraint met a reasoned answer.

Legibility over any background

The HUD washed out over the bright arena. I tested three fixes — an edge scrim, per-glyph text shadows, and a solid backing plate — and chose the plate for overlays plus a corner vignette and lighter labels for the HUD, because they read over any background without occluding gameplay. Diegetic-over-gameworld UI needs a different treatment than menu UI.

The system in service of the UI

For the damage indicator I chose a thin segmented bracket-arc over a big glowing wedge, so it read as part of my HUD rather than borrowed from another game. And when the template’s bright orange arena fought the dark interface, I recoloured the environment to a desaturated night-ops palette specifically so the UI would read — treating art direction as being in service of the interface.

How to Play — the same system holds top to bottom, on every screen.
How to Play — the same system holds top to bottom, on every screen.
Reflection

What I learned

Architecture over decoration

A data-driven HUD with a clean separation between gameplay and UI, pushed through one interface with no Tick cost, is a foundation that scales. The motion language exists for feel, not decoration — every element earns its animation.

A documented system

A locked token system applied consistently across a full product, with every deliberate exception written down. Repeated consistency audits kept it honest, and the result reads as one product across every screen.

Knowing when to break it

The judgment I value most here is knowing when to break my own system on purpose — brightening HUD labels over gameplay, bending the environment to serve the interface — and documenting exactly why. That is the UI-technical mindset the whole piece was built to show.

Next project

Feral