Skip to content

Blueprint API Design

PlatanoGames edited this page Feb 26, 2026 · 3 revisions

Blueprint API Design -- Progressive Disclosure for Node Graphs

The Problem

When a framework exposes its full C++ surface to Blueprints, the result is predictable: hundreds of nodes in the palette, no clear hierarchy, no sense of priority. A designer looking for "how do I play a sound" scrolls through 50 audio nodes spanning playback, mixing, backend configuration, pool management, telemetry, and debug snapshots. They pick one that looks right, wire it up wrong, and spend an hour debugging what should have been a two-minute task.

The typical "solution" is documentation -- a PDF or wiki page that says "use these 3 nodes for common tasks." But documentation is external to the tool. The node graph itself should communicate what matters and what is specialized.

A second problem is structural. In many frameworks, Blueprint-callable functions exist on both the subsystem class and a helper library. The same operation appears twice in the palette with slightly different signatures. Which one should the designer use? Nobody knows. Both work. Both have subtle differences in error handling.

PGX solves both problems with a uniform API design that applies to every system.


The 4-Tier Category Structure

Every PGX system organizes its Blueprint nodes into exactly four categories, using UE5's built-in category hierarchy. The categories appear in the palette as collapsible groups, sorted by specificity:

PGX | Save                  <-- Core (3-4 nodes)
PGX | Save | Query          <-- Read-only state inspection
PGX | Save | Advanced       <-- Batch ops, registration, cache
PGX | Save | Debug          <-- Telemetry, history, snapshots

Core (PGX|{System})

The 3-4 functions you use every day. For Save, that means "save," "load," and "get slot info." For Audio, that means "play sound," "stop sound," and "set volume." For the Message bus, that means "broadcast" and "listen."

A designer who never opens the other three categories can build a complete game.

Query (PGX|{System}|Query)

Read-only getters that inspect system state without changing it. "Is channel active?" "How many listeners?" "What is the current game flow state?" "Is a loading screen showing?"

These nodes have no side effects. They are safe to call from any context, any tick, any thread (where applicable). They are the diagnostic layer that enables data-driven Blueprint logic.

Advanced (PGX|{System}|Advanced)

Batch operations, manual registration, cache control, and configuration changes. "Register a handler table." "Invalidate the registry cache." "Re-ingest all definitions." "Execute a sequence of events."

These nodes exist for power users who need fine-grained control. They assume the user understands the system's internal model (databases, caches, handler lifecycles). They are not dangerous, but they are not self-explanatory.

Debug (PGX|{System}|Debug)

Telemetry export, execution history, blackbox records, and statistical snapshots. "Get handler telemetry." "Dump blackbox to string." "Export report." "Get message system stats."

These nodes are for development and profiling. They produce output useful in the editor and during playtesting. In shipping builds, some of these may return empty data or be compiled out entirely.

Category Summary Across Systems

System         | Core  | Query | Advanced | Debug | Total
---------------+-------+-------+----------+-------+------
Save           |   4   |   8   |    8     |   -   |  20+
GameFlow       |   3   |   4   |    5     |   3   |  15+
Audio          |   4   |   6   |    8     |   4   |  22+
PSO            |   3   |   5   |    4     |   3   |  15+
Loading        |   3   |   4   |    4     |   2   |  13+
LevelFlow      |   3   |   4   |    5     |   2   |  14+
Message        |   3   |   4   |    -     |   -   |   7+
EventHandler   |   2   |  10   |    9     |   6   |  27
Data Registry  |   3   |   7   |    1     |   2   |  13
---------------+-------+-------+----------+-------+------
Total          |  28+  |  52+  |   44+    |  22+  | 146+

The pattern scales. Every new system follows the same structure. A designer who learns the four tiers for one system understands them all.


Single Library: One Entry Point Per System

In PGX, each system exposes exactly one Blueprint Function Library. That library is the sole class with Blueprint-callable functions for that system. The subsystem class has no Blueprint-visible methods.

                     +-------------------------+
                     |   Blueprint Graph        |
                     |                          |
                     |   [ Play Sound ]         |
                     |   [ Get Flow State ]     |
                     |   [ Broadcast Message ]  |
                     +----------+--------------+
                                |
                                v
                     +-------------------------+
                     |   Blueprint Library      |  <-- sole entry point
                     |   (static functions)     |
                     +----------+--------------+
                                |
                                v
                     +-------------------------+
                     |   Subsystem             |  <-- C++ only
                     |   (instance methods)    |
                     +-------------------------+

Why this matters:

  1. No duplicate nodes. The designer sees one "Play Sound" node, not two versions with different signatures.

  2. Clean separation. The subsystem can evolve its internal API (rename methods, change signatures, refactor internals) without breaking any Blueprint graph. The library provides a stable public surface.

  3. Consistent error handling. The library functions perform null checks, world context validation, and subsystem availability checks before forwarding to the subsystem. Every Blueprint node has the same safety guarantees.

  4. Palette cleanliness. Only one class per system appears in the function list. No "SubsystemName" vs. "LibraryName" confusion.


Wildcard Struct Pins: Connect Any Data Inline

Many PGX operations accept a typed payload -- a struct containing event-specific data. The Message bus broadcasts payloads. The EventHandler passes context and payloads to handlers. The challenge: how does a Blueprint user pass an arbitrary struct without a generic "make struct" intermediary node?

PGX uses UE5's Custom Thunk mechanism to create wildcard struct pins:

+-----------------------+
| Execute Event         |
|                       |
|  Event Tag  o------   |      +------------------+
|  Instigator o------   |      | Make MyPayload   |
|  Payload    o<=========+=====>  Health: 100      |
|             (morphs   |      |  DamageType: Fire|
|              to match |      +------------------+
|              connected|
|              struct)  |
+-----------------------+

When the designer connects a "Make Struct" node to the Payload pin, the pin morphs to display that struct's type. The connection is type-safe -- the receiving system extracts the payload using the expected type and gets a null result if the types do not match at runtime.

This is the same UX pattern that Epic uses in their Lyra sample project for the gameplay message system. PGX extends it with additional validation and editor-time type hints.


Typed Return Pins: The Output Matches Your Selection

When a Blueprint function returns an asset reference and the caller specifies a subclass, the return pin should reflect that subclass -- not the base type.

+--------------------------+
| Resolve Registry Asset   |
|                          |
|  Database Tag  o------   |
|  Item Tag      o------   |
|  Expected Class o------  |   <-- user picks "WeaponDefinition"
|                          |
|  Return Value  o---------+-->  output pin type: WeaponDefinition*
|  (auto-typed)            |     (not DataAsset*, not Object*)
+--------------------------+

Without this feature, the designer would need to add a Cast node after every resolve call. With it, the output pin already has the correct type, and downstream nodes see the specific properties and functions of that class.

This uses UE5's DeterminesOutputType metadata, which tells the Blueprint compiler to infer the return type from a class parameter on the same node.


Async Nodes: Long Operations Without Blocking

Some operations take multiple frames -- async saves, async loads, network requests. PGX exposes these as latent Blueprint nodes with multiple output execution pins:

+---------------------------+
| Quick Save Async          |
|                           |
|  Slot Name    o------     |
|                           |
|  On Completed o---------->  (fires when save finishes)
|  On Failed    o---------->  (fires on error)
|                           |
|  Result       o-----------> Save Result Enum
+---------------------------+

These nodes appear in the Blueprint palette automatically (no special setup). They use UE5's async action infrastructure with proper lifecycle management -- the action is destroyed when the graph deactivates or the owning actor is destroyed.

The Message bus also provides an async listener node:

+---------------------------+
| Listen For Messages       |
|                           |
|  Channel      o------     |
|  Payload Type o------     |  <-- optional: filters by struct type
|  Match Type   o------     |  <-- exact or partial tag matching
|                           |
|  On Message   o---------->  (fires each time a message arrives)
|                           |
|  Channel      o----------->  FGameplayTag
|  Sender       o----------->  UObject*
|  Payload      o----------->  (typed wildcard)
+---------------------------+

This node stays active as long as the Blueprint is alive, firing its output pin each time a matching message arrives on the specified channel. It automatically unregisters when the owning actor or widget is destroyed.


Progressive Disclosure: The DataAsset UX

Blueprint API design extends beyond function nodes. Every PGX system is configured through a Data Asset -- a single asset the developer creates in the Content Browser, fills in the Details panel, and forgets about.

The Details panel itself uses progressive disclosure:

+-------------------------------------------+
| PGX Audio Config                          |
|                                           |
| Master Volume      [====== 0.8 ========] |  <-- visible by default
| Music Volume       [====== 0.6 ========] |  <-- visible by default
| SFX Volume         [====== 1.0 ========] |  <-- visible by default
|                                           |
| v Advanced                                |  <-- collapsed by default
|   Max Concurrent Sounds    [ 32 ]         |
|   Pool Size                [ 64 ]         |
|   HRTF Enabled             [ x ]          |
|   Ducking Matrix           [...]          |
|   Backend Type             [Legacy  v]    |
|   Spatialization Plugin    [Default v]    |
|   ...18 more properties...                |
+-------------------------------------------+

Each Data Asset type has 2-5 essential properties visible by default and 10-30 advanced properties collapsed under UE5's "Advanced" dropdown. The classification follows a simple rule:

  • Visible: The asset will not work correctly without this value being set.
  • Advanced: Has a sensible default that works for 80% of projects.
  • Never Hidden: Looks advanced but causes silent bugs if misconfigured. These stay visible despite being "advanced" in nature. Two examples: conflict resolution policy (defaulting to "first wins" silently drops data), and "wait for PSO" (defaulting to false causes shader hitches).

The Class Override Pattern

Every PGX system that supports extension provides a slot for the developer's custom class. The mechanism varies by context but the mental model is always the same:

For Blueprint users:
  1. Open the Config Data Asset
  2. Check "Use Custom Class"
  3. Drag your Blueprint class into the slot

For C++ users:
  1. Open the Config Data Asset
  2. Select your C++ class from the dropdown

Result: PGX instantiates YOUR class instead of the default.
        No duplicate instances. No "two systems running."

This is critical for the Blueprint API because it means the designer never needs to know about PGX internals to extend behavior. They create their class, override the virtual functions they care about (which appear as "Event" nodes in their Blueprint graph), assign it in the Data Asset, and the framework handles instantiation, lifecycle, and cleanup.


Design Principles (Summary)

Principle Implementation
Progressive disclosure 4-tier categories: Core < Query < Advanced < Debug
Single entry point One Library per system, zero subsystem UFUNCTION
Type safety in graphs Wildcard struct pins, typed return pins
Non-blocking operations Async nodes with completion/failure pins
Zero-knowledge usable Data Assets with visible defaults + advanced collapse
Extend without internals Class override slots in Config DAs
Consistent across systems Same 4 categories, same patterns, same UX

Key Takeaways

  1. Four tiers solve the wall-of-nodes problem. Core for daily use, Query for inspection, Advanced for power users, Debug for profiling. A designer who only uses Core nodes can ship a game.

  2. One library, one truth. The Blueprint Library is the sole entry point. The subsystem is C++ only. No duplicate nodes, no signature confusion, stable public surface.

  3. Wildcard pins eliminate boilerplate. Payload pins morph to match the connected struct. Typed returns eliminate cast nodes. The graph stays clean.

  4. Data Assets are the universal config. Every system is configured through the same pattern: create asset, fill properties, advanced stuff is collapsed. No code needed for 80% of configuration.

  5. 170+ nodes, but you only see what you need. The total node count is high, but the 4-tier structure means any given task presents 3-10 relevant nodes, not 170.

Clone this wiki locally