Unreal 5.1 – Advanced UI Prototype


Building a complete UI system in UMG: menu animations, settings persistence with Save Game, and in-game HUD design.


This project is a UI-focused prototype built entirely in Blueprints with Unreal Engine 5.1, developed following this Udemy course. Rather than building gameplay systems, the entire scope is dedicated to what players interact with before and around the game itself: the main menu, the settings screen, and the in-game HUD. These are often treated as secondary concerns, but getting them right requires deliberate architectural decisions that are easy to get wrong and hard to refactor later.

You can watch the prototype in action here: YouTube


Menu Stack and Widget Lifecycle

The first architectural question in any menu system is ownership: who creates widgets, who destroys them, and what happens to the ones that are hidden but not removed. A naive implementation creates a new widget every time a menu is opened and destroys it when closed. This works for simple cases but becomes a problem when menus need to preserve state — a settings screen that remembers unsaved changes, or a menu that animates out before being removed.

A better approach is to treat menus as a stack: opening a screen pushes it onto the stack, going back pops it. The stack manager handles creation (typically once, on first open) and visibility toggling rather than repeated creation and destruction. This keeps widget instances alive across open/close cycles, preserving any transient state they hold, and gives a single authoritative place to manage which screen is currently active.


UI Animations in the Start Menu

The start menu uses UMG animations to bring interface elements in and out — titles fading in, buttons sliding up, transitions between screens. UMG’s animation system works on a timeline model: each animation is a sequence of keyframes applied to widget properties (opacity, position, scale, color), and animations can be played forward, in reverse, or looped.

The key design decision is where animation playback is triggered. Triggering animations directly from button click handlers works but produces tight coupling between interaction logic and presentation. A cleaner approach is to use animation events — playing an “exit” animation on the current screen before the navigation logic fires, then triggering the navigation callback from the animation’s completion event. This keeps the visual transition synchronized with the state change without hardcoding timing values into the game logic.

Playing animations in reverse is particularly useful for menus: the same animation that fades elements in can play in reverse to fade them out, without requiring a separate animation asset for each direction.


Settings Menu and Persistence with Save Game

The settings menu presents a design challenge beyond its visual layout: settings need to persist across sessions. A setting changed in one play session should still be applied the next time the game launches. In Unreal, the standard mechanism for this is the Save Game system — a USaveGame subclass that holds the data to persist, serialized to disk via UGameplayStatics::SaveGameToSlot.

The settings flow has three distinct moments that need to be handled correctly:

On game launch: the Save Game slot is checked. If it exists, settings are loaded and applied immediately — before the first frame renders if possible, to avoid a flash of default settings. If no slot exists, defaults are used and a new slot is created.

While in the settings menu: changes are held in a temporary state, not applied immediately. This allows the player to make multiple changes and then either confirm (apply and save) or cancel (discard without writing to disk). Applying changes before confirmation is a common mistake that produces a jarring experience when the player cancels.

On confirm: the temporary state is written to the Save Game slot and applied to the active systems (audio, graphics, controls). The separation between “apply to game” and “write to disk” matters here — applying settings updates the running systems; saving ensures they survive a restart.


In-Game HUD: Minimalist Item Display

The in-game UI uses a minimalist design for item display — showing only what’s necessary without cluttering the screen. This is partly an aesthetic choice and partly an information architecture decision: what does the player need to see at all times versus what can be surfaced on demand.

The HUD is implemented as a persistent widget that lives for the duration of gameplay, unlike menu widgets which are shown and hidden. It binds to player state via Event Dispatchers rather than polling every frame — when the relevant data changes, the HUD receives a notification and updates. Polling-based HUDs (checking values in Tick) are a common performance mistake in Blueprint projects; event-driven updates are cheaper and produce more responsive UI.


Widget Hierarchy and Reusability

A recurring UMG best practice this project reinforces is designing widgets as composable units rather than monolithic screens. A button with hover animation, a slider with label and value display, a notification banner — each of these is a self-contained widget that can be placed inside multiple parent screens without duplicating logic. The settings menu and the start menu can share the same button widget type, each instance configured independently.

This composability is what separates a maintainable UMG project from one that becomes difficult to modify. When a button needs a visual change, it changes in one place and updates everywhere it’s used. When a new screen needs a slider, it reuses the existing one rather than rebuilding it.


Reflection

UI systems are one of the most underestimated areas of game development. The technical surface is different from gameplay — there’s no physics, no AI, no complex math — but the architectural challenges are real: state management across screens, persistence, animation timing, event-driven updates, and widget reusability. Getting these right in a dedicated prototype, without the pressure of building gameplay systems simultaneously, is exactly the right way to internalize them.

In a C++ context, the Save Game system maps to a USaveGame subclass with serialized UPROPERTY fields, and widget binding maps to TAttribute delegates or explicit UUserWidget::NativeOnInitialized setup. The patterns established in this Blueprint prototype translate directly.

Leave a comment

Create a website or blog at WordPress.com

Up ↑