-
Notifications
You must be signed in to change notification settings - Fork 0
LevelFlow System
"If you can't predict the order of operations during a level transition, you don't have a system. You have a prayer."
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:
- The game must know which level to load (sounds trivial — it is not when your level catalog is data-driven).
- 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).
- The old level's actors are destroyed. This includes anything that was not explicitly marked persistent.
- The new level's assets must be loaded, either synchronously or asynchronously.
- Sub-levels for the new level must be streamed in, potentially in a specific priority order.
- Shaders for the new level's materials may need to compile (the "first frame hitch" problem).
- The game state system needs to know we are now in a new context (e.g., "MainMenu" to "Gameplay").
- The player must be placed at a specific entry point within the new level.
- The loading screen must dismiss, but only after everything above is genuinely complete.
- 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.
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."
+------------------------------------------------------------------+
| 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 | |
| +-------------------------------------------------------------+ |
+------------------------------------------------------------------+
| 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 |
| 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 |
| 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 |
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.
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?"
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 |
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
Global configuration for the system:
- Default transition mode
- Default timing values
- Shader wait defaults
- History buffer size
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)
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.
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 |
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.
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.
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).
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 |
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.
El sistema LevelFlow convierte las transiciones de nivel de Unreal Engine en un pipeline determinista de 6 fases, eliminando las cadenas de callbacks fragiles y las pantallas negras que plagan la mayoria de los proyectos.
El problema: coordinar la carga de nivel, sub-levels, compilacion de shaders, puntos de entrada, pantallas de carga, y estado de juego requiere orquestacion manual que cada proyecto reinventa con resultados inconsistentes.
La solucion de PGX:
- Pipeline de 6 estados: Idle, Preparing, Loading, Transitioning, PostLoad, Complete/Failed
- Catalogo de niveles por GameplayTag: nunca rutas hardcodeadas en codigo gameplay
- Sub-levels con prioridad: geometria critica antes que audio ambiental
- Espera de shaders nativa: no muestra el nivel hasta que los shaders esten compilados
- Tiempos minimos/maximos: la pantalla de carga se ve lo suficiente, nunca se queda pegada
- Integracion automatica con estado de juego: el tag de game flow se setea al entrar al nivel
- 7 codigos de resultado: exito, tag no encontrado, ya en el nivel, transicion en progreso, carga fallida, cancelada, timeout
8 comandos de consola, 18 nodos Blueprint, inspector con catalogo visual, historial de transiciones, y visor de sub-levels. Actor colocable en niveles para declarar puntos de entrada.
El resultado: transiciones de nivel que simplemente funcionan, desde el prototipo hasta el shipping.
- 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