-
Notifications
You must be signed in to change notification settings - Fork 0
Loading System
"The loading screen is the one piece of UI that must work perfectly in the exact moment your game is least capable of supporting UI."
Loading screens in Unreal Engine are deceptively difficult. They appear simple — show an image while content loads, hide it when done — but the reality involves a web of interacting concerns that conspire to produce bugs:
Survival. When the engine loads a new level, it destroys almost everything in the current world. Actors, components, widgets — gone. Your loading screen widget was part of that world. It is now a dangling reference. The most common loading screen bug is the loading screen destroying itself during the very operation it is supposed to cover.
Timing. The loading screen must appear before the old level is torn down (otherwise the player sees a black frame) and disappear after the new level is fully ready (otherwise the player sees an incomplete world). These two boundaries are managed by different engine systems with different timing characteristics.
Input capture. While the loading screen is active, the player should not be able to interact with the game world beneath it. But simply disabling input is not enough — you need to remember the previous input state (cursor visibility, input mode, focus) and restore it exactly when the loading screen dismisses. Get this wrong and the player's mouse cursor disappears, or they cannot click anything, or their gamepad stops working.
Shader compilation. Even after the level's assets are loaded, the GPU may still be compiling shaders for materials it has never seen before. If the loading screen dismisses based on "assets loaded" alone, the player sees a level where every new material causes a visible hitch for the first few seconds.
Progress accuracy. Players expect progress bars to reflect reality. A progress bar that sits at 95% for 30 seconds and then jumps to 100% is worse than no progress bar at all. The loading screen must combine multiple progress sources (asset loading, shader compilation, minimum display time) into a single, honest percentage.
Reentry. What happens if a new loading request arrives while a loading screen is already active? What if the same level is requested twice? What if a network error occurs during an online game's loading sequence? Each of these is a valid scenario that production code must handle.
Stuck states. The worst loading screen bug is the one that never resolves. The loading screen appears and stays forever. The player's only option is to force-quit. This happens when a prerequisite system fails silently and the loading screen never receives its "you can dismiss now" signal.
Most projects address these issues one at a time, as they are discovered in QA. The result is a loading screen implementation that grows in complexity with every bug fix until no one on the team fully understands its state machine. PGX addresses all of these from the architecture level.
PGX implements loading screens as a persistent overlay system with a deterministic state machine, configurable visual strategies, and multiple safety mechanisms.
1. Persistent Overlays
The loading screen is not a widget in the game world. It is a persistent overlay that exists outside the normal actor lifecycle. When a level is destroyed and reconstructed, the loading screen survives because it was never part of the level's object graph. This eliminates the entire category of "loading screen destroys itself" bugs.
2. Six-State Deterministic Pipeline
+------+ +-----------+ +----------+
| Idle |---->| Preparing |---->| FadingIn |
+------+ +-----------+ +----------+
^ |
| v
+----------+ +---------------+ +--------+
| FadingOut|<----| WaitingClose |<-| Active |
+----------+ +---------------+ +--------+
Each state has explicit entry conditions, exit conditions, and timeout guards:
- Preparing — Resolve the loading profile from the requested context tag. Validate the profile. Capture current input state for later restoration. Timeout: configurable (default 5 seconds). If profile resolution fails, transition to Idle with error.
- FadingIn — Execute the fade-in animation. Begin capturing input. Duration is per-profile.
- Active — Loading screen is fully visible. Track progress from asset loading and shader compilation. Display visual content (image, slideshow, animation, video, or custom). This is where the loading screen spends most of its time.
- WaitingClose — Loading is complete, but the close policy may require waiting. Three policies: automatic (dismiss immediately), manual-only (wait for explicit close call), auto-with-skip (dismiss automatically but allow the player to skip early via button press). Timeout: configurable (default 20 seconds) to prevent stuck states.
- FadingOut — Execute the fade-out animation. Restore captured input state. Duration is per-profile.
3. Six Visual Strategies
Not every loading screen looks the same. The system supports six visual types:
| Type | Behavior |
|---|---|
| Minimal | Solid color with optional spinner. Zero asset dependencies. |
| Static Image | Single image, optionally with progress bar. |
| Slideshow | Rotating images with crossfade. Good for tips/lore. |
| Animated Material | A material instance drives the visuals. Supports arbitrary shader effects. |
| Video | Pre-rendered video playback during loading. |
| Custom | Developer provides their own visual implementation. |
The visual type is specified per loading profile, so different contexts can have different loading screen styles. The main menu might use Minimal. A story mission might use Video. A fast-travel might use Static Image.
4. Combined Progress Calculation
Progress is not just "how much of the level has loaded." It is a weighted combination:
Total = (1 - PSOWeight) * AssetProgress + PSOWeight * PSOProgress
Where:
- AssetProgress is the level asset loading percentage from the engine.
- PSOProgress is the shader precompilation percentage (if a PSO system is active and the profile is configured to wait for it).
- PSOWeight is a configurable value per loading profile, defaulting to 0.3.
A timeout prevents the combined progress from stalling forever if shader compilation encounters errors. After the timeout, the PSO portion is treated as complete regardless of actual state.
5. Input State Capture and Restoration
When the loading screen begins fading in, the system captures a snapshot of the current input state:
- Cursor visibility
- Input mode (game-only, UI-only, game-and-UI)
- Focus state
When the loading screen finishes fading out, the captured state is restored exactly. This prevents the "my cursor disappeared after loading" and "I can't click anything after the loading screen" bugs that plague many implementations.
6. Watchdog Timers
Two safety timers prevent stuck states:
- Preparing Timeout (default 5s) — If profile resolution takes too long, the system abandons the attempt and returns to Idle with an error. This catches broken asset references.
- WaitingClose Timeout (default 20s) — If the close signal never arrives (e.g., a system fails silently), the loading screen force-closes after the timeout. The player is never permanently stuck.
7. Reentry Policies
Three policies handle overlapping loading requests:
| Policy | Behavior |
|---|---|
| Ignore | If a loading screen is already active, discard the new request. |
| Restart | Dismiss the current loading screen and start the new one. |
| Queue | Complete the current loading screen, then execute the new request. |
+------------------------------------------------------------------+
| Loading System v1.0 |
+------------------------------------------------------------------+
| |
| +------------------+ +------------------+ +--------------+ |
| | Profile | | State Machine | | Visual | |
| | Resolution | | | | Renderer | |
| | | | 6-state | | | |
| | Context tag -> |---->| deterministic |-->| 6 strategies | |
| | Loading Profile | | pipeline | | Minimal to | |
| | DA | | with timeouts | | Custom | |
| +------------------+ +------------------+ +--------------+ |
| | |
| v |
| +------------------+ +------------------+ +--------------+ |
| | Input Manager | | Progress Engine | | Safety | |
| | | | | | Mechanisms | |
| | Capture state | | Asset + | | | |
| | on FadingIn | | PSO weighted | | 2 watchdogs | |
| | Restore on | | combination | | Reentry | |
| | FadingOut | | | | policies | |
| +------------------+ +------------------+ +--------------+ |
| |
| +-------------------------------------------------------------+ |
| | Delegates (5 dynamic + 4 native) | |
| | | |
| | OnStarted | OnProgress | OnCompleted | OnFailed | |
| | OnStateChanged | |
| +-------------------------------------------------------------+ |
+------------------------------------------------------------------+
| Capability | Description |
|---|---|
| Request by context | Show a loading screen using the profile associated with a context tag |
| Force close | Immediately dismiss the loading screen regardless of state |
| Request skip | Ask the loading screen to dismiss (only honored by AutoWithSkip policy) |
| Capability | Description |
|---|---|
| Active check | Whether any loading screen is currently visible |
| Current state | Which of the 6 states the system is in |
| Current context | The context tag of the active loading screen |
| Elapsed time | How long the current loading screen has been active |
| Visual type | Which of the 6 visual strategies is currently rendering |
| Capability | Description |
|---|---|
| Combined progress | Weighted total of asset loading and shader compilation |
| Asset progress | Raw asset loading percentage |
| PSO progress | Raw shader compilation percentage |
| Status message | Human-readable description of current loading activity |
| Capability | Description |
|---|---|
| Validate profile | Check whether a context tag maps to a valid, loadable profile |
| Profile count | How many loading profiles were auto-discovered |
| List context tags | All context tags with registered loading profiles |
Each completed loading sequence is recorded:
| Field | Description |
|---|---|
| Context tag | Which profile was used |
| Visual type | Which visual strategy was displayed |
| Total duration | Wall-clock time from request to dismiss |
| Per-phase durations | Time spent in each state machine phase |
| Result code | Success, timeout, force-closed, cancelled, etc. |
| Flags | Whether PSO wait was active, whether timeout occurred, whether player skipped |
The primary configuration surface. Each profile defines how a specific loading context should behave:
Visual Configuration:
- Visual type (Minimal / Static Image / Slideshow / Material / Video / Custom)
- Background image or material reference
- Slideshow images with interval timing
- Spinner style and position
- Progress bar visibility and style
Timing Configuration:
- Fade-in duration
- Fade-out duration
- Fade curve (linear, ease-in, ease-out, custom)
- Minimum display time (ensures the loading screen is visible long enough to read)
Behavior Configuration:
- Close policy (Automatic / ManualOnly / AutoWithSkip)
- Reentry policy (Ignore / Restart / Queue)
- Whether to wait for shader compilation
- PSO weight in combined progress calculation
- PSO wait timeout
Safety Configuration:
- Preparing timeout
- WaitingClose timeout
Global configuration:
- Default close policy
- Default reentry policy
- Default timeout values
- History buffer size
The system binds to the engine's network failure and travel failure delegates. When a network error occurs during a loading sequence, the system handles it according to the active profile's failure policy rather than leaving the loading screen in an undefined state.
A dedicated editor panel with four sections:
Current Status — Real-time display of the state machine with color coding (Gray=Idle, Blue=Preparing, Cyan=FadingIn, Yellow=Active, Orange=WaitingClose, Purple=FadingOut). Shows context tag, visual type, elapsed time, and combined progress.
Profile Catalog — Browse all discovered Loading Profile assets. View visual type, close policy, timing settings, and PSO integration configuration for each profile. One-click navigation to any profile asset.
Loading History — Scrollable log of past loading sequences with timing breakdowns per phase. Identifies slow sequences and highlights those that hit timeouts or were force-closed. Essential for performance tuning.
Debug Controls — Buttons for simulating loading requests, force-closing active screens, and triggering PSO simulation (for testing combined progress without actual shader compilation). These controls are editor-only and do not appear in shipping builds.
10 commands for runtime control and diagnostics:
| Command | Purpose |
|---|---|
pgx.loading.status |
Current state, context, elapsed time, progress |
pgx.loading.request |
Trigger a loading screen by context tag |
pgx.loading.close |
Force-close active loading screen |
pgx.loading.skip |
Request skip (if policy allows) |
pgx.loading.history |
Show loading history with timing |
pgx.loading.profiles |
List discovered profiles |
pgx.loading.config |
Show global configuration values |
pgx.loading.debug |
Toggle debug overlay (phase/timing) |
pgx.loading.simulate |
Simulate a loading sequence (no actual load) |
pgx.loading.simulate.pso |
Simulate PSO integration progress |
This is the primary integration. When the level transition system begins a transition for a level that has "show loading screen" enabled, the loading screen activates automatically. The loading screen dismisses when the transition completes. The developer does not need to manage this coordination manually.
When a loading profile has PSO wait enabled, the loading screen queries shader compilation progress and factors it into the combined progress bar. The PSO timeout ensures the loading screen will eventually dismiss even if shader compilation stalls.
When configured, the loading screen system sets a "Loading" game state tag on activation and clears it on dismissal. This allows other systems to query whether a loading screen is active through the game state system rather than coupling directly to the loading screen.
The system binds to engine network failure and travel failure delegates. When a network error interrupts a loading sequence, the loading screen transitions to its failure path rather than staying active indefinitely.
15 Blueprint nodes across 4 categories:
| Category | Nodes | Purpose |
|---|---|---|
| Core | 3 | Request, force close, skip |
| Query | 6 | Active check, state, context, progress, elapsed time, visual type |
| Advanced | 3 | Profile validation, discovered count, registered tags |
| Debug | 2 | History, last duration |
Loading screens are one of the few UI elements that every game has and every player sees. They are also one of the most technically challenging because they operate during the precise moment when the engine is tearing down and rebuilding the game world.
The most common approach — a widget-based loading screen created in the level — is fundamentally broken by design. The level transition will destroy it. The second most common approach — a persistent widget added to the viewport — works better but still requires manual state management, input handling, progress tracking, and safety guards.
PGX makes loading screens a solved problem. The state machine is deterministic. Visual strategies are swappable without code changes. Progress is accurate because it accounts for both asset loading and shader compilation. Input state is captured and restored. Watchdog timers prevent stuck states. Reentry policies handle edge cases. Network failures are caught. Everything is configured through Data Assets with no initialization code required.
For the player, this means loading screens that always appear, always show accurate progress, and always dismiss. For the developer, this means never debugging loading screen bugs again.
The history system also serves as a powerful profiling tool. "Our Forest level loading screen averages 4.2 seconds, 1.8 seconds of which is shader compilation. If we pre-warm those shaders, we can cut loading time by 43%." This kind of data-driven optimization is impossible without instrumentation, and PGX provides it out of the box.
- Development Preview
- Getting Started
- Release branch catalog
- Public Plugin Matrix
- Early Preview Plugins
- Known Issues
- Architecture Overview
- Plugin Topology
- Module Reference
- Configuration and Registry
- Data-Driven Design
- Profiles and Budgets
- Gameplay Tag Architecture
- Initialization Pipeline
- Cross-Plugin Communication
- Message System
- Event Handlers
- Logging and Trace
- Runtime Flows
- Blueprint API Design
- Editor Integration
- Editor Visual System