-
Notifications
You must be signed in to change notification settings - Fork 0
Construction System
Replace hardcoded class assignments with configurable Data Assets. One dropdown, zero recompilation.
Unreal Engine's gameplay class system is powerful and deeply hardcoded.
The Game Mode specifies which Player Controller class to use. The Player Controller specifies which Pawn to possess. The HUD specifies which widget to display. These assignments live in C++ constructors or Class Defaults, and changing them requires one of two things: recompilation or inheritance gymnastics.
Here is what this looks like in practice:
Problem 1: Class assignment requires code changes. Want to swap the Player Controller for a different one during development? Change the C++ constructor, recompile, wait 45 seconds. Want to test a different Pawn class for a specific level? Create a Game Mode subclass just for that level, set the Pawn override, and manage a growing inheritance tree of Game Mode variants.
Problem 2: Component injection requires constructors. Adding components to an actor -- health, inventory, abilities, audio listeners -- happens in the C++ constructor. The set of components is fixed at compile time. A data-driven game where different character archetypes have different component sets requires either a monolithic actor with everything always attached, or a deep inheritance hierarchy where each variant adds its components.
Problem 3: No per-level configuration without code. Level designers want different default classes per level. An arena level needs a spectator pawn. A cutscene level needs a cinematic controller. Without per-level overrides, the Game Mode for each level must be a different subclass, creating a combinatorial explosion of Game Mode classes that differ by one or two settings.
Problem 4: No platform-aware injection. A VR platform needs HRTF audio components. A mobile platform should skip expensive physics components. A server should never have HUD components. These platform and network role distinctions are implemented ad-hoc, usually with preprocessor conditionals scattered through constructors.
The core issue: the engine treats class architecture as a compile-time decision. Game development treats it as a runtime decision that changes weekly.
The Construction System replaces hardcoded class assignments with Data Assets. Each gameplay class category has a dedicated Data Asset type that defines everything about how that class should be constructed: which components to inject, which system configs to apply, which gameplay tags to grant, and type-specific settings.
Configuration resolves through a two-level priority system: per-level overrides take precedence over project-wide defaults. Both are configured without code -- one through a World-scoped subsystem, the other through Project Settings.
Resolution Flow (static, evaluated once per construction)
Resolve<T>(World) -- static, typed resolution
|
v
+------------------------------------------+
| 1. Per-Level Override |
| (World Construction Subsystem) |
| - Set by level Blueprint or data |
| - Destroyed on world teardown |
| - Override found? ---> Return it |
+------------------------------------------+
| (not found)
v
+------------------------------------------+
| 2. Project Settings |
| (Developer Settings, persistent) |
| - Configured in Project Settings UI |
| - One slot per gameplay class type |
| - Setting found? ---> Load and return |
+------------------------------------------+
| (not found)
v
+------------------------------------------+
| 3. No Construction DA configured |
| - Return null (safe default) |
| - System operates without PGX |
| construction -- standard UE |
| behavior preserved |
+------------------------------------------+
This is a static resolution pattern. There is no subsystem-driven lifecycle, no delegates, no runtime state machine. The resolver is a utility that reads configuration and returns a Data Asset. It is called once during actor construction. If configuration changes after construction, already-existing actors are not affected -- only newly constructed ones.
This deliberate simplicity is the point. Construction is a build-time concern, not a runtime concern. Making it reactive would add complexity without practical benefit.
Each gameplay class category in Unreal Engine has a dedicated Construction DA:
Abstract Base (shared fields)
|
+-- Actor Construction Generic actors
+-- GameMode Construction Game mode classes + default class assignments
+-- PlayerController Construction Input and camera config
+-- GameState Construction Shared game state + initial tags
+-- PlayerState Construction Per-player state + initial tags
+-- Character Construction Movement speeds + ability config
+-- Pawn Construction Basic pawn settings
+-- HUD Construction Widget layers + main HUD class
Every Construction DA provides:
| Field | Purpose |
|---|---|
| Components | Array of components to inject at construction time |
| Required Components | Components that must already exist on the actor (validation) |
| System Configs | Map of system tag to config Data Asset references (per-system overrides) |
| Granted Tags | Gameplay tags to grant to the constructed actor |
| Save Participation | Whether this actor participates in the save system |
| Save Domain | Which save domain this actor belongs to |
| GameFlow Subscription | Whether to listen for game state changes via message bus |
| Level Transition Subscription | Whether to listen for level flow notifications |
| Loading Screen Subscription | Whether to listen for loading screen state changes |
| Type | Additional Fields |
|---|---|
| GameMode | Default Pawn class, Player Controller class, Player State class, Game State class, HUD class, Spectator class |
| PlayerController | Input config reference, Camera config reference, mouse cursor visibility |
| GameState | Initial state tags |
| PlayerState | Initial player tags |
| Character | Ability config reference, default walk speed, default run speed |
| Pawn | Use default movement flag |
| HUD | Main HUD widget class, widget layers (class + Z-order + identification tag per layer) |
| Actor | (inherits only shared fields -- for generic actors) |
The component injection system evaluates four conditions per component:
For each component in the Construction DA:
+-- Is the component class valid?
| No --> Skip (log warning)
| Yes --> Continue
|
+-- Does RequiredCapability match a platform capability?
| Tag empty --> Always inject
| Tag present --> Check against Profile system
| Not supported --> Skip silently
|
+-- Does the network role match?
| Client-only + running on server --> Skip
| Server-only + running on client --> Skip
| Neither flag set --> Inject everywhere
|
+-- Inject component
Attach to actor, apply name, ready for use
Each component entry has an optional capability tag. When a tag is set, the component is only injected if the platform's Profile reports that capability as enabled.
Example: an HRTF audio component has a VR-Audio capability tag. On a VR platform, the Profile reports VR-Audio as enabled, and the component is injected. On a desktop platform, VR-Audio is not reported, and the component is silently skipped.
If the Profile system is not present (e.g., in a test environment), all capabilities are treated as enabled. The permissive default ensures the system never blocks functionality unnecessarily.
Two flags per component control network role:
| Client-Only | Server-Only | Result |
|---|---|---|
| false | false | Inject on all roles |
| true | false | Inject only on owning client |
| false | true | Inject only on server |
| true | true | Invalid combination (logged, treated as "all roles") |
A single Construction DA can mix client-only, server-only, and universal components. An HUD component is client-only. A replication manager is server-only. A health component is universal.
The Project Settings panel exposes 8 construction slots -- one per gameplay class type:
Project Settings > PGX > Framework Construction
+----------------------------------------------------------+
| |
| Actor: |
| Construction DA: [DA_ActorConstruction_Default v] |
| |
| GameMode: |
| Class Source: [Default v] |
| Construction DA: [DA_GameMode_Default v] |
| |
| PlayerController: |
| Class Source: [Blueprint v] |
| Blueprint Class: [BP_MyController v] |
| Construction DA: [DA_Controller_Main v] |
| |
| Character: |
| Class Source: [C++ Class v] |
| C++ Class: [AMyCharacter v] |
| Construction DA: [DA_Character_Default v] |
| |
| ... (remaining 4 slots) |
| |
| [x] Verbose Construction Log |
| [x] Validate On Startup |
+----------------------------------------------------------+
Each slot (except Actor, which is DA-only) has a Class Source selector:
| Source | What It Means |
|---|---|
| Default | Use the PGX base class. Zero configuration needed. Works out of the box. |
| C++ Class | Use a specific C++ class. A dropdown appears for class selection. |
| Blueprint | Use a specific Blueprint class. An asset picker appears for Blueprint selection. |
The Class Source drives conditional visibility: when set to C++, only the C++ class picker is visible. When set to Blueprint, only the Blueprint picker is visible. When set to Default, neither picker is shown. This is progressive disclosure -- simple things are simple.
When "Validate On Startup" is enabled, the editor checks all construction slots at editor launch:
- C++ source selected but no class assigned? Warning.
- Blueprint source selected but no Blueprint assigned? Warning.
- Default GameMode in Maps & Modes does not derive from PGX base? Warning.
Warnings appear as editor notifications and in the log. They catch configuration mistakes before play-testing begins.
A World-scoped subsystem holds 8 override slots (one per type). When a slot is set, it takes priority over the corresponding Project Settings slot.
Per-Level Override Flow:
Level Blueprint (or data-driven setup)
|
v
Set override on World Construction Subsystem
(e.g., "for this arena level, use the Spectator Pawn DA")
|
v
Subsystem stores override (Transient -- not serialized)
|
v
When actors construct, Resolver checks World override first
|
v
On world teardown, subsystem and all overrides are destroyed
(no stale state leaks to next level)
Per-level overrides are Transient. They exist only for the lifetime of the world. When the player travels to a new level, the subsystem is destroyed and recreated with empty slots. This prevents stale configuration from bleeding between levels.
All 8 DA types are Blueprint-subclassable. Studios can create custom Construction DA types for domain-specific needs:
PGX Base Types Studio Extensions
+----------------------+ +---------------------------+
| GameMode Constr. | | FPS GameMode Constr. |
| - Pawn class | <- | - Round Config |
| - Controller class | | - Weapon Pool Config |
| - Spectator class | | - Respawn Rules |
+----------------------+ +---------------------------+
+----------------------+ +---------------------------+
| Character Constr. | | RPG Character Constr. |
| - Walk speed | <- | - Class Archetype |
| - Run speed | | - Starting Abilities |
| - Ability config | | - Dialogue Voice Set |
+----------------------+ +---------------------------+
The extension pattern is straightforward: inherit from the base type, add fields, create instances in the Content Browser. The Construction Resolver does not need to know about custom subtypes -- it resolves by the base type and the custom fields come along for the ride.
All 8 DA types appear in the Content Browser's create menu under the PGX Construction category. Each has a dedicated factory, a system-specific color, and a descriptive tooltip.
An automated check runs at editor launch (when enabled) that validates:
- All class source/class assignment combinations are consistent
- The Game Mode configured in Maps & Modes is compatible with PGX construction
- No slots have contradictory settings
Results appear as editor toast notifications -- visible without opening any inspector panel.
Component injection respects platform capabilities. A component tagged with a VR capability is only injected on VR platforms. A component tagged with a high-end graphics capability is only injected on platforms that report that capability.
The Profile system provides the capability query. The Construction system consumes it. If Profile is unavailable, all capabilities are treated as available (permissive default).
The "Save Participation" flag on a Construction DA tells the Save system to track this actor's state. The Save Domain field assigns it to a specific save domain. No additional wiring needed -- the construction configuration is the single source of truth for save enrollment.
The subscription flags (GameFlow, LevelFlow, Loading) register the constructed actor for message bus notifications from those systems. Instead of each actor manually subscribing, the Construction DA declares the intent, and the construction process handles the wiring.
The Construction System addresses one of the most persistent pain points in UE5 development: the gap between "I want to change which components my character has" and "I need to recompile." Data-driven actor architecture is not a new idea, but it is almost always implemented ad-hoc, per-project, with different patterns for each class category.
PGX provides a uniform pattern for all 8 gameplay class types. One abstract base with shared fields. Type-specific extensions where they make sense. A two-level resolution priority. Platform and network role filtering. Startup validation. Blueprint extensibility.
The key design decision is what the system does NOT do: it does not manage runtime state, it does not react to changes, it does not add complexity beyond what construction requires. It is a configuration system that reads a Data Asset, applies it, and gets out of the way. That simplicity is its strength.
For teams that swap class configurations weekly during prototyping, iterate on component sets daily, and test across multiple platforms -- the Construction System turns a recompilation cycle into a dropdown change.
El Construction System resuelve el problema de asignacion hardcodeada de clases en UE5. En el motor base, cambiar el PlayerController, el Pawn, o los componentes de un actor requiere modificar constructores C++ y recompilar. PGX reemplaza esto con 8 tipos de Data Asset -- uno por categoria de clase gameplay (Actor, GameMode, PlayerController, GameState, PlayerState, Character, Pawn, HUD).
Cada DA define: componentes a inyectar, configs de sistema, gameplay tags a otorgar, flags de participacion en save, y campos especificos por tipo (velocidades de movimiento para Character, capas de widget para HUD, clases por defecto para GameMode).
La resolucion sigue prioridad de dos niveles: override por-nivel (via World Subsystem) >> Project Settings >> null (comportamiento UE estandar). Los 3 modos de Class Source (Default/C++/Blueprint) proporcionan disclosure progresivo -- el dev solo ve lo que necesita.
La inyeccion de componentes respeta capacidades de plataforma (via Profile) y rol de red (client-only, server-only, universal). La validacion al startup detecta configuraciones inconsistentes antes de hacer play-test.
Los 8 tipos de DA son Blueprint-subclassable -- los estudios pueden extenderlos con campos especificos de su juego sin modificar el framework. El patron es deliberadamente simple: leer configuracion, aplicarla, y salir del camino. Sin estado runtime, sin delegates, sin maquinas de estado. La configuracion es una preocupacion de build-time, y el sistema lo trata asi.
- 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