-
Notifications
You must be signed in to change notification settings - Fork 0
GameFlow System
"The first thing every game programmer builds is a state machine with an enum. The second thing they build is a bigger enum. The third thing they realize is that one enum cannot describe a game."
Game state management in Unreal Engine typically starts with an enum. Five states. One switch. It works — for about two weeks.
Dimensionality. A game does not have one state. The UI can be in a menu while gameplay is active. The AI can be in a cinematic state while the camera is in free-look. Cramming orthogonal dimensions into a single enum produces combinatorial explosion — InGame_Playing_NormalCam_AIActive_UIHidden and forty-seven friends.
Extensibility. Enums are compile-time constants. Adding a state means modifying a header, recompiling every consumer, and updating every switch. For a framework plugin, this means users cannot add game states without modifying framework code.
Validation. Transition rules are scattered if-else chains, growing with every new state. No declarative way to say "from MainMenu, you can go to Loading or Options, but not to GameOver."
History. Enums carry no history. "Previous state" requires a separate variable. "Revert" requires a separate flag plus validation. Every project reimplements this from scratch.
The root cause: enums are rigid (compile-time), flat (single dimension), and opaque (no metadata, no history, no validation).
PGX GameFlow replaces enums with GameplayTags and replaces the single state machine with eight independent channels.
GameplayTags are hierarchical string identifiers in UE5's tag system. Extensible via config files without recompilation. Hierarchical queries built in. Lightweight, hashable, serializable.
Enum: EGameState::MainMenu Tag: "PGX.Flow.Global.MainMenu"
Adding a state: modify .h, recompile Adding a state: Project Settings, done
Tags are hierarchical, enabling branch-based validation. A rule that says "allowed destinations under PGX.Flow.Global" automatically permits all current and future tags in that branch — including tags the framework author never imagined.
Instead of one state machine, GameFlow provides eight:
Channel 0: GLOBAL "What phase is the game in?"
Channel 1: UI "What is the UI doing?"
Channel 2: CHARACTERS "What are the characters doing?"
Channel 3: AI "What is the AI doing?"
Channel 4: CAMERAS "What is the camera doing?"
Channel 5: SYSTEMS "What are the backend systems doing?"
Channel 6: LEVEL LOGIC "What is the level's state?"
Channel 7: ACTORS "What are the actors doing?"
Each channel is an independent FSM: current state, history with timestamps, validation rules (Data Asset-driven), and its own delegate. Channels are orthogonal — changing the UI channel does not affect the AI channel.
Single-enum: InGame_Playing_NormalCam_AIActive_UIHidden (47+ combos)
Eight-channel: Global:InGame | UI:Hidden | Camera:Normal | AI:Active
Chars:Playing | Systems:Online | Level:Loaded | Actors:Spawned
The total state space is identical, but each channel's space is small, manageable, and independently extensible.
+----------------------------------------------+
| GameFlow Subsystem |
| (GameInstanceSubsystem) |
| |
| +--------+--------+--------+--------+ |
| | Ch 0 | Ch 1 | Ch 2 | ... | |
| | Global | UI | Chars | (x8) | |
| | | | | | |
| | State | State | State | State | |
| | History| History| History| History| |
| +--------+--------+--------+--------+ |
| |
| +-------------------+ +-----------------+ |
| | Validation Engine | | Batch Engine | |
| | | | | |
| | IsInBranch check | | Sequential | |
| | Allowed list | | Parallel | |
| | Disallowed query | | All-or-nothing | |
| +-------------------+ +-----------------+ |
| |
| +-------------------+ +-----------------+ |
| | History Manager | | Delegate Hub | |
| | | | | |
| | Per-channel log | | 8 dynamic (BP) | |
| | Timestamps | | 1 native (C++) | |
| | Revert support | | | |
| +-------------------+ +-----------------+ |
+----------------------------------------------+
|
+---------------+---------------+
| | |
+-------v------+ +-----v--------+ +----v---------+
| Rules Config | | Rules Config | | Rules Config |
| DA: Global | | DA: UI | | DA: AI |
| | | | | |
| Tag -> Rule | | Tag -> Rule | | Tag -> Rule |
| Allowed [] | | Allowed [] | | Allowed [] |
| Disallowed[]| | Disallowed[]| | Disallowed[]|
| CanRevert | | CanRevert | | CanRevert |
+--------------+ +--------------+ +--------------+
SetStateByTag(Global, "PGX.Flow.Global.InGame", Source)
|
v
1. Branch Check -- Is tag under the channel's root? (string prefix match)
2. Redundancy -- Already in this state? -> RedundantState
3. Rule Lookup -- Find rule for CURRENT state. No rule = ALLOW
4. Whitelist -- AllowedDestinations contains target? (empty = allow all)
5. Blacklist -- DisallowedTagQuery matches? (veto overrides whitelist)
6. Apply -- Push to history, set state, broadcast delegates
|
v
Return: Success / ValidationError / RedundantState
Rules are declared per source state in Data Assets:
+------------------+-------------------------------+-----------+
| Source State | Allowed Destinations | Revert? |
+------------------+-------------------------------+-----------+
| MainMenu | [Loading, Options, Credits] | No |
| Loading | [InGame, MainMenu] | No |
| InGame.Playing | [InGame.Paused, GameOver] | Yes |
| InGame.Paused | [InGame.Playing, MainMenu] | Yes |
| GameOver | [MainMenu, Credits] | No |
+------------------+-------------------------------+-----------+
(Source states without rules = unrestricted)
Each rule specifies allowed destinations (whitelist), disallowed tag queries (blacklist veto), revert permission, and an error code. The key design choice: no rule = permissive. The system works with zero configuration and becomes restrictive only as rules are added.
| Feature | Description |
|---|---|
| Single transition | Change one channel's state with full validation |
| Batch sequential | Change multiple channels in order; stop on first failure |
| Batch parallel | Change multiple channels; apply all or rollback all |
| Revert | Return to the previous state (if the current rule permits) |
| Pre-validation | Check if a transition would succeed without applying it |
Each channel maintains an ordered history with timestamps. History enables revert (return to previous state with validation), audit trails, duration calculation (time between consecutive entries), and pattern detection ("how many pauses this session?").
The delegate model is designed for both Blueprint and C++ consumers:
| Delegate | Count | When |
|---|---|---|
| Per-channel dynamic | 8 (one per channel) | That specific channel changes state |
| Global native | 1 (carries channel param) | Any channel changes state |
Blueprint code subscribes to the specific channel it cares about. C++ code can subscribe to the single native delegate and filter by channel parameter.
All queries are non-mutating and safe to call from any context:
| Query | Description |
|---|---|
| Get current state | Current tag for a specific channel |
| Get last state | Previous tag for a specific channel |
| Get channel history | Full history array with timestamps |
| Get allowed transitions | Valid destinations from the current state |
| Is current state | Boolean check against a specific tag |
| Can change | Pre-validation without applying the transition |
| Can revert | Check if revert is allowed from the current state |
| Channel name | String name for a channel enum value (static utility) |
Each channel's validation rules live in a separate Data Asset:
Content Browser > Right-click > PGX > GameFlow
-> Creates a Flow Rules Config asset
1. Select the channel this rules config governs
2. Add rules per source state:
- Source tag
- Allowed destinations (leave empty for unrestricted)
- Disallowed tag queries (blacklist veto)
- Revert permission flag
3. Done. Auto-discovered at initialization.
Multiple rules configs can exist for the same channel. The subsystem discovers all of them and merges the rule sets. This allows modular rule definition — base rules in one asset, DLC-specific rules in another.
Tags follow a hierarchical convention: PGX.Flow.{Channel}.{State}. The framework provides root branches (PGX.Flow.Global, PGX.Flow.UI, etc.). Game projects add leaf tags via Project Settings > Gameplay Tags. No framework modification. No recompilation.
Without any Rules Config Data Assets, all transitions are allowed on all channels. The system is fully functional with zero configuration — it becomes a tag-based state tracker with history. Validation is opt-in, added incrementally as the project's state machine solidifies.
+----------------------------------------------------------+
| PGX GameFlow Inspector [Pin] |
+----------------------------------------------------------+
| |
| CHANNEL STATES (live, color-coded) |
| |
| [=] Global PGX.Flow.Global.InGame.Playing |
| [=] UI PGX.Flow.UI.HUD |
| [=] Characters PGX.Flow.Characters.Exploring |
| [=] AI PGX.Flow.AI.Combat |
| [=] Cameras PGX.Flow.Cameras.ThirdPerson |
| [=] Systems PGX.Flow.Systems.Online |
| [=] LevelLogic PGX.Flow.LevelLogic.Exploration |
| [=] Actors PGX.Flow.Actors.Active |
| |
+----------------------------------------------------------+
| |
| TRANSITION HISTORY |
| +------+----------+---------------------------+------+ |
| | Time | Channel | Transition | Src | |
| +------+----------+---------------------------+------+ |
| | 14:32| Global | Loading -> InGame.Playing | Sys | |
| | 14:32| UI | Loading -> HUD | Sys | |
| | 14:33| AI | Idle -> Combat | NPC | |
| | 14:34| Cameras | Default -> ThirdPerson | Ctrl | |
| +------+----------+---------------------------+------+ |
| |
+----------------------------------------------------------+
| Channels: 8/8 active | History: 47 entries |
+----------------------------------------------------------+
The inspector provides:
- Channel state panel: All eight channels displayed simultaneously with their current tags. Each channel has its own color (Gold for Global, Sky Blue for UI, Green for Characters, Red-Orange for AI, Purple for Cameras, Gray for Systems, Teal for Level Logic, Orange for Actors).
- Transition history: Chronological list of all transitions across all channels, with timestamps, source references, and transition details.
- Channel detail: Click a channel to see its full history, current validation rules, and allowed transitions from the current state.
The typical integration flow:
- Define your game states as GameplayTags (Project Settings > Gameplay Tags)
- (Optional) Create Rules Config Data Assets for channels that need validation
- In Blueprints or C++, set states and subscribe to channel delegates
- Query current states to drive game logic
No inheritance required. No base class extension. No framework class modification.
Several PGX systems consume GameFlow state:
- Loading: Checks the Global channel before initiating level transitions
- PSO: Uses GameFlow state to activate appropriate pipeline caches
- Audio: Can react to game state changes for ambient sound management
- Save: Can tie auto-save triggers to game state transitions
These integrations use the framework's message bus — they do not create direct dependencies on the GameFlow plugin.
Blueprint: subscribe to channel-specific delegates (e.g., "OnFlowGlobalChanged" fires with FlowTag + Source). C++: subscribe to the single native delegate that carries a Channel parameter for filtering.
| Command | Description |
|---|---|
pgx.gameflow.status |
Print all 8 channels with their current state tags |
pgx.gameflow.set |
Set a channel's state from the console (debug) |
pgx.gameflow.canchange |
Pre-validate a transition without applying it |
pgx.gameflow.history |
Print a channel's transition history |
pgx.gameflow.revert |
Revert a channel to its previous state |
pgx.gameflow.rules |
Print the validation rules for a channel |
Organized into three categories:
| Category | Nodes |
|---|---|
| Core | Set State By Tag, Revert To Previous, Get Current Flow Tag, Is Current Flow Tag |
| Query | Get Last Flow Tag, Get Channel History, Get Allowed Transitions, Get Allowed From Current, Get Channel Name, Is Initialized |
| Advanced | Set Batch Sequential, Set Batch Parallel, Can Change By Tag, Can Batch Change, Check Can Revert |
State management is the skeleton of every game. Every system in the project — rendering, audio, input, AI, UI — needs to know "what is the game doing right now?" The answer to that question determines which systems are active, which inputs are consumed, which audio plays, which UI is visible.
When that answer lives in an enum, the skeleton is rigid. Adding a bone requires surgery on every joint. When the game grows beyond the enum's capacity, the result is either an explosion of compound states or an ad-hoc secondary state system that contradicts the primary one.
GameplayTags eliminate the rigidity. Tags are extensible without recompilation. The framework defines the channels and the validation pattern. The game project defines the states. A new state is a config entry, not a code change.
Eight independent channels eliminate the dimensionality problem. Instead of modeling the product of all state dimensions as a single flat enum, each dimension is an independent state machine. The UI team manages UI states. The AI team manages AI states. Neither team's changes affect the other.
Data Asset-driven validation eliminates the procedural validation problem. Transition rules are declarative, auditable, and modular. A designer can look at a Rules Config and understand exactly which transitions are legal without reading code. A new rule is an asset change, not a code change.
History and revert eliminate the state-tracking boilerplate. Every channel remembers where it has been. Reverting to the previous state is a one-call operation with built-in validation.
The result is a state management system where the game project never modifies framework code. States are tags. Rules are assets. Channels are independent. History is automatic. The skeleton is flexible.
El sistema GameFlow v1.0 es un gestor de estado basado en GameplayTags con 8 canales independientes, validacion por Data Asset, historial, y revert.
El problema: En UE5, los state machines se construyen con enums. Agregar un estado requiere recompilacion. Los estados son planos — no puedes separar estado de UI del estado de AI. La validacion es manual (cadenas if-else). No hay historial.
La solucion: GameplayTags en lugar de enums. 8 canales independientes (Global, UI, Characters, AI, Cameras, Systems, LevelLogic, Actors). Cada canal es un FSM independiente con su propio estado, historial, y reglas de validacion.
Tags extensibles: El framework define las ramas raiz. El proyecto del juego agrega sus estados como tags hijos via Project Settings. Cero modificacion de codigo del framework. Cero recompilacion.
Validacion declarativa: Data Assets de reglas por canal. Destinos permitidos (whitelist), queries de veto (blacklist), permiso de revert. Sin regla = todo permitido (default permisivo).
Historial automatico: Cada canal recuerda todos sus estados anteriores con timestamps. Revert a estado anterior es una llamada con validacion integrada.
En el editor: Inspector con 8 canales coloreados, historial de transiciones cruzado, detalle por canal. 6 comandos de consola, 15 nodos Blueprint en 3 categorias.
Filosofia de diseno: Los estados son tags (extensibles sin compilar). Las reglas son assets (auditables sin leer codigo). Los canales son independientes (el equipo de UI no afecta al equipo de AI). El esqueleto del juego es flexible por construccion.
- 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