Skip to content

LevelFlow System

PlatanoGames edited this page Feb 26, 2026 · 3 revisions

LevelFlow System v1.0 — Deterministic Level Transitions

"If you can't predict the order of operations during a level transition, you don't have a system. You have a prayer."


The Problem

Unreal Engine's level loading primitives are powerful but fundamentally low-level. The engine gives you OpenLevel, LoadStreamLevel, shader pipeline queries, and a collection of delegates that fire in non-obvious orders. The gap between these primitives and a production-quality level transition is enormous.

Consider what actually needs to happen when a player moves from one level to another:

  1. The game must know which level to load (sounds trivial — it is not when your level catalog is data-driven).
  2. A loading screen must appear before the old level is torn down (the Loading Screen system handles this, but someone has to tell it when).
  3. The old level's actors are destroyed. This includes anything that was not explicitly marked persistent.
  4. The new level's assets must be loaded, either synchronously or asynchronously.
  5. Sub-levels for the new level must be streamed in, potentially in a specific priority order.
  6. Shaders for the new level's materials may need to compile (the "first frame hitch" problem).
  7. The game state system needs to know we are now in a new context (e.g., "MainMenu" to "Gameplay").
  8. The player must be placed at a specific entry point within the new level.
  9. The loading screen must dismiss, but only after everything above is genuinely complete.
  10. If anything fails at any point, the system must report what failed and recover gracefully.

Most projects implement this as a chain of callbacks, timers, and boolean flags scattered across multiple classes. It works until it does not. The failure modes are spectacular: black screens, stuck loading, wrong spawn points, shaders compiling on visible geometry, game state desynchronized from the actual level.

The core issue is that level transitions are inherently sequential, multi-phase operations, but Unreal provides no built-in state machine for managing them. Every project reinvents this wheel, usually with increasing levels of duct tape as edge cases are discovered in QA.


How PGX Solves It

PGX treats level transitions as a deterministic pipeline with explicit phases, observable state, and data-driven configuration.

1. Six-State Pipeline

  +------+     +-----------+     +---------+
  | Idle |---->| Preparing |---->| Loading |---+
  +------+     +-----------+     +---------+   |
                                               v
  +--------+     +----------+     +--------------+
  | Failed |<----| PostLoad |<----| Transitioning|
  +--------+     +----------+     +--------------+
                      |
                      v
                 +----------+
                 | Complete |
                 +----------+

Each phase has a clear responsibility:

  • Preparing — Resolve the target level from the catalog, validate prerequisites, notify subscribers.
  • Loading — Execute the actual level load (async or sync, per configuration).
  • Transitioning — Wait for minimum transition time, coordinate shader compilation wait.
  • PostLoad — Stream sub-levels, locate entry points, apply game state changes.
  • Complete — All systems ready. Player can interact.
  • Failed — Something went wrong. Error code explains what.

Every transition between phases fires a delegate. External systems can observe the pipeline without coupling to its internals.

2. GameplayTag-Based Level Catalog

Levels are not referenced by map path. They are referenced by GameplayTag. The mapping from tag to actual level asset lives in Level Profile Data Assets that the system auto-discovers.

This means:

  • Gameplay code never contains hard-coded map paths.
  • The same gameplay code works across different level configurations (e.g., test vs. production levels).
  • Level resolution can be overridden without touching gameplay logic.
  • The catalog is queryable at runtime ("give me all levels tagged as combat zones").

3. Shader Wait Integration

The system natively queries the engine's shader pipeline cache to determine how many shaders are still compiling. During the Transitioning phase, it can optionally wait for all pending shader compilations to finish before advancing to PostLoad. This eliminates the "first frame of the new level is a stuttering mess" problem.

Critically, this shader wait uses the engine's own API. It does not depend on or couple to any external PSO system. It simply asks the engine: "are there pending shader compilations?" and waits if configured to do so.

4. Sub-Level Streaming with Priority

Real levels are rarely a single map file. They are composed of a persistent level and multiple streaming sub-levels (lighting, gameplay, audio, etc.). The system supports sub-level declarations with priority ordering, so critical sub-levels (gameplay geometry) load before optional ones (ambient audio volumes).

5. Entry Point Actors

The system provides a placed-in-level actor that serves as both a level identifier and a collection of entry points. When you place this actor in a level, you are declaring: "this level exists in the catalog, its tag is X, and these are the places where players can spawn."


Architecture (ASCII Diagram)

+------------------------------------------------------------------+
|                   LevelFlow System v1.0                           |
+------------------------------------------------------------------+
|                                                                   |
|  +-----------------+     +------------------+     +-------------+ |
|  | Level Catalog   |     | Transition       |     | Entry Point | |
|  |                 |     | Pipeline         |     | Resolution  | |
|  | Tag -> Level    |     |                  |     |             | |
|  | mapping from    |---->| 6-state machine  |---->| Placed      | |
|  | Profile DAs     |     | per-phase        |     | actors in   | |
|  | auto-discovered |     | delegates        |     | levels      | |
|  +-----------------+     +------------------+     +-------------+ |
|                                |                                  |
|                                v                                  |
|  +-----------------+     +------------------+     +-------------+ |
|  | Sub-Level       |     | Shader Wait      |     | Game State  | |
|  | Streaming       |     |                  |     | Integration | |
|  |                 |     | Native engine    |     |             | |
|  | Priority-       |     | query for        |     | Auto-set    | |
|  | ordered         |     | pending PSO      |     | game flow   | |
|  | loading         |     | compilations     |     | tag on      | |
|  +-----------------+     +------------------+     | level enter | |
|                                                   +-------------+ |
|                                                                   |
|  +-------------------------------------------------------------+ |
|  | Delegates (6 dynamic + 3 native)                             | |
|  |                                                               | |
|  | OnStarted | OnProgress | OnCompleted | OnFailed               | |
|  | OnSubLevelLoaded | OnSubLevelUnloaded | OnStateChanged         | |
|  +-------------------------------------------------------------+ |
+------------------------------------------------------------------+

Capabilities

Level Transition Control

Capability Description
Request transition Start a transition to a level identified by GameplayTag
Cancel transition Abort an in-progress transition and return to Idle
Transition progress Percentage of completion across all phases
Active check Whether a transition is currently in progress

Level Catalog

Capability Description
Resolve by tag Look up the level asset for a given GameplayTag
List registered tags All level tags known to the system
Profile count How many Level Profile assets were discovered
Level count Total registered levels across all profiles

Sub-Level Management

Capability Description
Request sub-level load Explicitly stream in a sub-level by tag
Request sub-level unload Stream out a loaded sub-level
Query loaded state Whether a specific sub-level is currently loaded
List loaded sub-levels All currently-streamed sub-levels

Transition Timing

Each level profile can specify:

  • Minimum transition time — Ensures the loading screen is visible long enough to be read (prevents flash-loading on fast hardware).
  • Maximum transition time — Safety timeout. If the transition exceeds this, it fails with a timeout error code.
  • Shader wait flag — Whether to wait for pending shader compilations before completing.

Transition History

The system maintains a history of all transitions, each recording:

  • Source and destination level tags
  • Timestamp
  • Load duration
  • Shader wait duration
  • Number of shader compilations encountered
  • Whether the transition timed out
  • Result code (success, cancelled, load failed, etc.)

This history is invaluable for performance profiling. "How long does the Forest-to-Castle transition take on average? How many shaders need compiling?"

Result Codes

Seven distinct result codes eliminate guesswork about failure:

Code Meaning
Success Transition completed normally
TagNotFound The requested level tag is not in any profile
AlreadyInLevel Already at the requested destination
TransitionInProgress A transition is already running
LoadFailed The engine failed to load the level asset
Cancelled The transition was explicitly cancelled
Timeout Maximum transition time exceeded

Configuration

Level Profile Data Assets

The primary configuration surface. Each profile asset contains a catalog of levels:

Per-Level Entry:

  • Level tag (GameplayTag identifier)
  • Display name (for UI)
  • Level asset reference (soft reference, does not force-load)
  • Load strategy (async, sync, or stream as sub-level)
  • Transition mode (instant, fade, or custom)
  • Timing (minimum/maximum time, shader wait)
  • Whether to show a loading screen during this transition
  • Game flow tag to set on level entry
  • Sub-level list with priority ordering

Level Flow Config Data Asset

Global configuration for the system:

  • Default transition mode
  • Default timing values
  • Shader wait defaults
  • History buffer size

Placed Actor

A level-placed actor that declares:

  • This level's tag (must match a catalog entry)
  • Available entry points (named spawn positions)
  • Managed sub-levels (override per-profile sub-level list)

Editor Tooling

LevelFlow Inspector

A dedicated editor panel with four sections:

Transition Status — Current state machine state with color coding (Gray=Idle, Blue=Preparing, Yellow=Loading, Orange=Transitioning, Purple=PostLoad, Green=Complete, Red=Failed). Shows current/previous level tags and elapsed time.

Level Catalog — Browse all discovered Level Profile assets. View the complete level catalog with tags, display names, and configuration summaries. Navigate to any profile asset with one click.

Transition History — Scrollable list of past transitions with timing data, shader counts, and result codes. Useful for identifying slow transitions during development.

Sub-Level Viewer — Shows currently loaded sub-levels with their tags and load state. Controls for manual load/unload during development.

Console Commands

8 commands for runtime control and inspection:

Command Purpose
pgx.level.status Current state, level tag, elapsed time
pgx.level.load Trigger a transition to a specific level tag
pgx.level.cancel Cancel in-progress transition
pgx.level.profiles List discovered Level Profile assets
pgx.level.resolve Resolve a tag to its level asset path
pgx.level.history Show transition history with timing
pgx.level.sublevels List currently loaded sub-levels
pgx.level.entrypoints List entry points in the current level

Integration Points

With Loading Screens

The LevelFlow system coordinates with the loading screen system to ensure visual coverage during transitions. When a level profile has "show loading screen" enabled, the loading screen activates during the Preparing phase and dismisses after PostLoad. The developer does not need to manage this manually.

With Game State

When a level profile specifies a "game flow tag on enter," the system sets the game state tag automatically upon completing the transition. This keeps the game state system synchronized with the actual level without requiring manual state management in gameplay code.

With Shader Compilation

The shader wait integration in the Transitioning phase queries pending shader compilations from the engine's pipeline cache. This is a native engine query — not a dependency on any external system. The transition will not complete until shaders are done, or until the maximum transition time is exceeded (whichever comes first).

Blueprint Access

18 Blueprint nodes across 4 categories:

Category Nodes Purpose
Core 4 Request transition, cancel, current tag, history
Query 6 State, previous tag, active check, progress, resolve, list tags
SubLevel 4 Load, unload, query loaded, list loaded
Debug 4 Actor query, counts, shader compilations pending

Why This Matters

Level transitions are where the seams of your game are most visible. A smooth transition says "this game was made by professionals." A black screen, a stuck loading bar, or shaders compiling on the first frame says "this game was shipped before it was finished."

The problem is not that level loading is hard. The problem is that coordinating everything that needs to happen around level loading is hard, and every project solves it differently, usually incrementally, usually with bugs that are not discovered until QA.

PGX makes level transitions a solved problem. The pipeline is deterministic. The states are explicit. The configuration is data-driven. The failure modes have codes instead of black screens. Sub-levels load in the right order. Shaders compile before the player sees geometry. Entry points are declared in the level, not hardcoded in gameplay scripts.

For teams working on games with many levels — or even just a main menu and a gameplay level — this eliminates an entire category of bugs that traditionally consume disproportionate QA and engineering time.

Clone this wiki locally