Skip to content

Graph Generation ProOnly

Graph Generation is a C++ interface for building and editing state machine blueprints without the graph editor. It mirrors what a user does by hand, placing states and transitions, exposing and wiring variables, building stacks, nesting and collapsing state machines, so the same operations can be driven from tooling, importers, or procedural generators.

The interface is ISMGraphGeneration. It works alongside ISMAssetManager, which creates and compiles the blueprint assets the generation calls operate on. Both are reached through ISMAssetToolsModule.

Editor only

Graph Generation lives in the editor-only SMAssetTools module and manipulates blueprint graphs. Call it from an editor or uncooked module (editor utilities, commandlets, asset tooling). It is not available in a packaged runtime.

Manual authoring is still the norm

Most state machines are best authored by hand in the graph editor. Reach for Graph Generation when you need to produce or modify assets programmatically, such as importing from an external format, generating variants in bulk, or building authoring tools on top of Logic Driver.

Build.cs setup

Add the asset tools module to your editor module. SMAssetTools publicly depends on SMSystem and SMSystemEditor, so the state, transition, and graph-node types come along with it. UnrealEd provides FKismetEditorUtilities for compiling, and BlueprintGraph provides FEdGraphPinType if you author node-class variables.

MyEditorModule.Build.cs
PrivateDependencyModuleNames.AddRange(new string[]
{
    "SMSystem",
    "SMSystemEditor",
    "SMAssetTools",
    "UnrealEd",        // FKismetEditorUtilities::CompileBlueprint
    "BlueprintGraph",  // FEdGraphPinType, for node-class variables
});

Accessing the interfaces

Both interfaces are returned as shared pointers from the module. Load the module once and hold the interfaces for the duration of your operation.

#include "ISMAssetToolsModule.h"
#include "ISMAssetManager.h"
#include "ISMGraphGeneration.h"

ISMAssetToolsModule& AssetTools = ISMAssetToolsModule::Get();
TSharedPtr<ISMAssetManager> AssetManager = AssetTools.GetAssetManagerInterface();
TSharedPtr<ISMGraphGeneration> GraphGen = AssetTools.GetGraphGenerationInterface();

Example node classes

The walkthrough below builds an enemy GuardAI (Idle → Patrol → Chase) using these two example node classes. USMExampleState exposes a plain property, an array, and an input/output variable pair; USMExampleTransition drives its evaluation from an exposed bool.

// State class: a plain property, an array, and an input/output variable pair.
UCLASS(Blueprintable, BlueprintType)
class USMExampleState : public USMStateInstance
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite, Category = "Example")
    FString DisplayLabel;

    UPROPERTY(BlueprintReadWrite, Category = "Example")
    TArray<FString> Waypoints;

    /** Output variable: written when the state ends, readable downstream. */
    UPROPERTY(BlueprintReadWrite, Category = "Example", meta = (LD_PropertyDirection = "Output"))
    int32 EnemiesSpotted = 0;

    /** Input variable: receives a value wired from an upstream output. */
    UPROPERTY(BlueprintReadWrite, Category = "Example", meta = (LD_PropertyDirection = "Input"))
    int32 InitialTargetCount = 0;
};

// Transition class: an exposed bool drives the evaluation result.
UCLASS(Blueprintable, BlueprintType)
class USMExampleTransition : public USMTransitionInstance
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite, Category = "Example")
    bool bCanAdvance = true;

protected:
    virtual bool CanEnterTransition_Implementation() const override { return bCanAdvance; }
};

Walkthrough: build a state machine from scratch

This example creates a new state machine asset, places three states (using USMExampleState) wired in a line from the entry point, adds transitions, and compiles. Each step links to the API call it uses.

1. Create the blueprint asset

CreateStateMachineBlueprint mints a new USMBlueprint. The name is adjusted automatically on collision.

#include "Blueprints/SMBlueprint.h"

ISMAssetManager::FCreateStateMachineBlueprintArgs BlueprintArgs;
BlueprintArgs.Name = TEXT("GuardAI");
BlueprintArgs.Path = TEXT("/Game/StateMachines");
// BlueprintArgs.ParentClass = UMyStateMachineInstance::StaticClass(); // optional

USMBlueprint* NewBP = AssetManager->CreateStateMachineBlueprint(BlueprintArgs);
if (!NewBP)
{
    return;
}

2. Create the entry state

CreateStateNode places a state. Set bIsEntryState to wire it to the graph's entry point so it becomes the initial state. The templated overload casts the result for you.

#include "Graph/Nodes/SMGraphNode_StateNode.h"

ISMGraphGeneration::FCreateStateNodeArgs StateArgs;
StateArgs.StateInstanceClass = USMExampleState::StaticClass();   // omit to default to USMStateInstance
StateArgs.StateName = TEXT("Idle");
StateArgs.bIsEntryState = true;            // wire to the entry point
StateArgs.NodePosition = FVector2D(128.f, 0.f);

USMGraphNode_StateNode* IdleState =
    GraphGen->CreateStateNode<USMGraphNode_StateNode>(NewBP, StateArgs);

Node layout

States flow rightward from the entry node. Advance NodePosition.X for each state so the generated graph reads left to right, the same convention the editor uses.

3. Create more states and wire transitions

Create the remaining states (they reuse StateArgs, so they keep USMExampleState), then connect them with CreateTransitionEdge. Assign a TransitionInstanceClass to evaluate the transition through a node class, or set bDefaultToTrue for an always-true transition (valid only when no transition class is assigned).

Transitions need a condition to fire

A transition with no condition and no transition class evaluates to false, so the state machine would never leave the source state. Set bDefaultToTrue for an always-true transition, assign a TransitionInstanceClass, or author a condition in the transition graph. This walkthrough uses bDefaultToTrue so the machine advances on its own.

#include "Graph/Nodes/SMGraphNode_TransitionEdge.h"

// Patrol
StateArgs.StateName = TEXT("Patrol");
StateArgs.bIsEntryState = false;
StateArgs.NodePosition = FVector2D(384.f, 0.f);
USMGraphNode_StateNode* PatrolState =
    GraphGen->CreateStateNode<USMGraphNode_StateNode>(NewBP, StateArgs);

// Chase
StateArgs.StateName = TEXT("Chase");
StateArgs.NodePosition = FVector2D(640.f, 0.f);
USMGraphNode_StateNode* ChaseState =
    GraphGen->CreateStateNode<USMGraphNode_StateNode>(NewBP, StateArgs);

// Idle -> Patrol, evaluated by the transition class (its bCanAdvance).
ISMGraphGeneration::FCreateTransitionEdgeArgs TransitionArgs;
TransitionArgs.TransitionInstanceClass = USMExampleTransition::StaticClass();
TransitionArgs.FromStateNode = IdleState;
TransitionArgs.ToStateNode = PatrolState;
GraphGen->CreateTransitionEdge(NewBP, TransitionArgs);

// Patrol -> Chase, always passes (no transition class, so bDefaultToTrue applies).
ISMGraphGeneration::FCreateTransitionEdgeArgs ChaseArgs;
ChaseArgs.FromStateNode = PatrolState;
ChaseArgs.ToStateNode = ChaseState;
ChaseArgs.bDefaultToTrue = true;
GraphGen->CreateTransitionEdge(NewBP, ChaseArgs);

Wiring a chain in one step

Instead of creating each state standalone and adding a separate transition, set FCreateStateNodeArgs::FromPin to the previous state's output pin. The new state is wired with a transition from that state automatically, the same as dragging a new state off an existing one in the editor.

StateArgs.FromPin = IdleState->GetOutputPin();
USMGraphNode_StateNode* PatrolState =
    GraphGen->CreateStateNode<USMGraphNode_StateNode>(NewBP, StateArgs);

4. Compile

Building the graph leaves the blueprint dirty. Compile it so the generated class reflects the new graph. Save it as you would any asset (for example UEditorAssetLibrary::SaveLoadedAsset, or the editor's Save All).

#include "Kismet2/KismetEditorUtilities.h"

FKismetEditorUtilities::CompileBlueprint(NewBP);

Batch then compile

Compiling is the expensive step. When generating many nodes or assets, do all the graph edits first and compile once at the end rather than after each change.

GuardAI: Idle, Patrol, and Chase placed as USMExampleState nodes

Full example

void BuildGuardAI()
{
    ISMAssetToolsModule& AssetTools = ISMAssetToolsModule::Get();
    TSharedPtr<ISMAssetManager> AssetManager = AssetTools.GetAssetManagerInterface();
    TSharedPtr<ISMGraphGeneration> GraphGen = AssetTools.GetGraphGenerationInterface();

    ISMAssetManager::FCreateStateMachineBlueprintArgs BlueprintArgs;
    BlueprintArgs.Name = TEXT("GuardAI");
    BlueprintArgs.Path = TEXT("/Game/StateMachines");
    USMBlueprint* NewBP = AssetManager->CreateStateMachineBlueprint(BlueprintArgs);
    if (!NewBP)
    {
        return;
    }

    auto MakeState = [&](const FString& Name, float X, bool bEntry)
    {
        ISMGraphGeneration::FCreateStateNodeArgs Args;
        Args.StateInstanceClass = USMExampleState::StaticClass();
        Args.StateName = Name;
        Args.bIsEntryState = bEntry;
        Args.NodePosition = FVector2D(X, 0.f);
        return GraphGen->CreateStateNode<USMGraphNode_StateNode>(NewBP, Args);
    };

    USMGraphNode_StateNode* Idle = MakeState(TEXT("Idle"), 128.f, true);
    USMGraphNode_StateNode* Patrol = MakeState(TEXT("Patrol"), 384.f, false);
    USMGraphNode_StateNode* Chase = MakeState(TEXT("Chase"), 640.f, false);

    // Idle -> Patrol is evaluated by the transition class.
    ISMGraphGeneration::FCreateTransitionEdgeArgs IdleToPatrol;
    IdleToPatrol.TransitionInstanceClass = USMExampleTransition::StaticClass();
    IdleToPatrol.FromStateNode = Idle;
    IdleToPatrol.ToStateNode = Patrol;
    GraphGen->CreateTransitionEdge(NewBP, IdleToPatrol);

    // Patrol -> Chase always passes.
    ISMGraphGeneration::FCreateTransitionEdgeArgs PatrolToChase;
    PatrolToChase.FromStateNode = Patrol;
    PatrolToChase.ToStateNode = Chase;
    PatrolToChase.bDefaultToTrue = true;
    GraphGen->CreateTransitionEdge(NewBP, PatrolToChase);

    FKismetEditorUtilities::CompileBlueprint(NewBP);
}

Setting exposed property values

SetNodePropertyValue writes a default value onto a node's exposed property. It works for public and non-public properties as well as custom graph properties such as Text Graphs. Values are passed as Unreal property-text strings. Use GET_MEMBER_NAME_CHECKED so the name tracks renames.

Using USMExampleState from Example node classes, set a scalar value:

ISMGraphGeneration::FSetNodePropertyArgs SetArgs;
SetArgs.PropertyName = GET_MEMBER_NAME_CHECKED(USMExampleState, DisplayLabel);
SetArgs.PropertyDefaultValue = TEXT("Watchtower");
GraphGen->SetNodePropertyValue(PatrolState, SetArgs);

Arrays

EArrayChangeType selects the structural operation. Add or insert grows the array, then set an element by index. The same enum supports RemoveElement, Clear, DuplicateElement, and MoveElement.

// Append an element.
ISMGraphGeneration::FSetNodePropertyArgs AddArgs;
AddArgs.PropertyName = GET_MEMBER_NAME_CHECKED(USMExampleState, Waypoints);
AddArgs.ArrayChangeType = ISMGraphGeneration::EArrayChangeType::AddElement;
GraphGen->SetNodePropertyValue(PatrolState, AddArgs);

// Set element 0.
ISMGraphGeneration::FSetNodePropertyArgs ElemArgs;
ElemArgs.PropertyName = GET_MEMBER_NAME_CHECKED(USMExampleState, Waypoints);
ElemArgs.PropertyIndex = 0;
ElemArgs.PropertyDefaultValue = TEXT("North");
GraphGen->SetNodePropertyValue(PatrolState, ElemArgs);

Nested struct members and arrays

FSetNodePropertyArgs::SubPath addresses a member inside a struct property, or an element of a nested array, using MemberA.MemberB[i].MemberC syntax. For a scalar write the path ends at the leaf; for a structural array mutation the path ends at the array itself and PropertyIndex / TargetIndex carry the indices.

Split struct parents first

Writing through a SubPath requires every struct parent in the path to be split into sub-pins (the same Split Struct Pin action available in the editor; see Struct Properties). Writing to an unsplit parent is rejected, because the value would land on a collapsed parent literal and get wiped on the next construction-script tick. Pass the optional OutError argument of SetNodePropertyValue to receive the reason when a SubPath fails to resolve, such as an unknown member or an unsplit parent. Other failures only log and leave it empty.

// Write propA[0].propB[1].propC (scalar leaf).
ISMGraphGeneration::FSetNodePropertyArgs DeepArgs;
DeepArgs.PropertyName = TEXT("propA");
DeepArgs.PropertyIndex = 0;
DeepArgs.SubPath = TEXT("propB[1].propC");
DeepArgs.PropertyDefaultValue = TEXT("42");
GraphGen->SetNodePropertyValue(StateNode, DeepArgs);

Resetting a property

ResetNodePropertyValue restores a property (or array element) to its class default, mirroring the editor's right-click Reset Pin to Default Value.

ISMGraphGeneration::FResetNodePropertyArgs ResetArgs;
ResetArgs.PropertyName = GET_MEMBER_NAME_CHECKED(USMExampleState, DisplayLabel);
GraphGen->ResetNodePropertyValue(PatrolState, ResetArgs);

Authoring a property graph directly

For logic beyond a default value, FindPropertyGraph resolves the USMPropertyGraph behind an exposed variable so you can wire K2 nodes against its result pin. The result carries the graph, the property K2 node, and the wire-into pin.

ISMGraphGeneration::FFindPropertyGraphArgs FindArgs;
FindArgs.VariableName = GET_MEMBER_NAME_CHECKED(USMExampleState, DisplayLabel);

ISMGraphGeneration::FFindPropertyGraphResult Result;
FString Error;
if (GraphGen->FindPropertyGraph(PatrolState, FindArgs, Result, &Error))
{
    // Result.Graph, Result.ResultNode, Result.ResultPin are now valid.
}

When wiring a Text Graph durably, enable graph-edit mode on its property graph first with SetPropertyGraphEditMode; the text graph compile pipeline only honors wired changes in edit mode.

Output variables

ConnectNodeVariableOutput wires an output variable to a destination, either another node's input variable in the same state machine, or a variable on the owning state machine blueprint. Endpoints are described with FNodeVariableEndpoint; StackIndex of INDEX_NONE addresses the primary node template.

ISMGraphGeneration::FConnectNodeVariableOutputArgs ConnectArgs;
ConnectArgs.FromOutputVariable.StateNode = PatrolState;
ConnectArgs.FromOutputVariable.VariableName =
    GET_MEMBER_NAME_CHECKED(USMExampleState, EnemiesSpotted);     // must be Output or Both

// Destination A: another node's input variable.
ISMGraphGeneration::FNodeVariableEndpoint ToInput;
ToInput.StateNode = ChaseState;
ToInput.VariableName =
    GET_MEMBER_NAME_CHECKED(USMExampleState, InitialTargetCount); // must be Input or Both
ConnectArgs.ToInputVariable = ToInput;

// Destination B (mutually exclusive): a variable on the owning blueprint.
// ConnectArgs.ToOwningBlueprintVariable = TEXT("LastScore");

GraphGen->ConnectNodeVariableOutput(NewBP, ConnectArgs);

In the GuardAI example this wires Patrol's EnemiesSpotted output to Chase's InitialTargetCount input, so each state shows its exposed variables and the output feeds the downstream input:

GuardAI with Patrol's EnemiesSpotted output wired to Chase's InitialTargetCount input

Exactly one destination must be set. The call is idempotent (re-establishing an existing wire returns true without duplicating nodes). DisconnectNodeVariableOutput breaks a previously-established wire using the same args.

State and transition stacks

Add additional node-class templates to a state's state stack with CreateStateStackInstance, or to a transition's transition stack with CreateTransitionStackInstance. Leave the index at INDEX_NONE to append.

ISMGraphGeneration::FCreateStateStackArgs StackArgs;
StackArgs.StateStackInstanceClass = UMyOtherStateInstance::StaticClass();
USMStateInstance* StackInstance =
    GraphGen->CreateStateStackInstance(PatrolState, StackArgs);

To address a specific stack entry in a later property write, set FSetNodePropertyArgs::NodeInstance to the returned stack instance.

Nested state machines and references

Pass a state-machine graph-node class to CreateStateNode to place an inline nested state machine or a reference. FCreateStateNodeArgs::StateMachineReferenceConfig configures a reference at creation, and ConfigureStateMachineReference reconfigures one afterward (swap the referenced blueprint, toggle the intermediate graph). See State Machine References.

#include "Graph/Nodes/SMGraphNode_StateMachineStateNode.h"

ISMGraphGeneration::FCreateStateNodeArgs RefArgs;
RefArgs.GraphNodeClass = USMGraphNode_StateMachineStateNode::StaticClass();
RefArgs.StateMachineReferenceConfig.ReferencedBlueprint = ExistingReferenceBP;
RefArgs.NodePosition = FVector2D(384.f, 0.f);
USMGraphNode_StateMachineStateNode* RefNode =
    GraphGen->CreateStateNode<USMGraphNode_StateMachineStateNode>(NewBP, RefArgs);

Local graph logic nodes

State and transition graphs can be seeded with Logic Driver's bound read and write nodes.

CreateLocalGraphReadNode spawns a read node (selected by ELocalGraphReadNodeType) into a state's OnStateBegin / OnStateUpdate / OnStateEnd graph or a transition's CanEnterTransition graph. Each type has its own compatibility rules; for example CanEvaluate is transition-only.

UEdGraph* TargetGraph = Transition->GetBoundGraph();

ISMGraphGeneration::FCreateLocalGraphReadNodeArgs ReadArgs;
ReadArgs.NodeType = ISMGraphGeneration::ELocalGraphReadNodeType::TimeInState;
ReadArgs.TargetGraph = TargetGraph;
ReadArgs.NodePosition = FVector2D(100.f, 50.f);
UEdGraphNode* ReadNode = GraphGen->CreateLocalGraphReadNode(NewBP, ReadArgs);

CreateLocalGraphWriteNode spawns a write node (ELocalGraphWriteNodeType, such as CanEvaluate) into a transition or conduit graph, optionally seeding its boolean default.

GetCompatibleLocalGraphNodeTypes reports which read and write node kinds a given bound graph accepts, without spawning anything, so you can discover the valid ELocalGraphReadNodeType / ELocalGraphWriteNodeType values for a graph before calling the create functions above.

CreateLocalGraphEventNode places a lifecycle event-entry node (selected by ELocalGraphEventNodeType) into a state, transition, conduit, or intermediate graph. The type covers nine kinds — initialize, shutdown, state update, state end, transition entered, transition pre-evaluate, transition post-evaluate, and root state machine start and stop — and each is placed only into a graph that accepts it, honoring the per-kind singleton limit. OnStateUpdate and OnStateEnd are placed in every state graph when it is created and cannot be deleted, so a request to spawn either one is rejected and the call returns null.

GetCompatibleLocalGraphEventNodeTypes reports which event-entry kinds a given bound graph accepts, so you can query the valid ELocalGraphEventNodeType values before spawning.

Transition events

ConfigureTransitionEvent binds, rebinds, or clears the delegate a transition listens to, and sets its update-trigger flags. Only the fields you set in FConfigureTransitionEventArgs are applied, matching a Details-panel edit. To set both a delegate owner and a property name in one call, supply both, as an owner change cascades a reset of the name.

ISMGraphGeneration::FConfigureTransitionEventArgs EventArgs;
EventArgs.DelegateOwnerInstance = ESMDelegateOwner::SMDO_Context;
EventArgs.DelegateOwnerClass = AMyActor::StaticClass();
EventArgs.DelegatePropertyName = TEXT("OnSomethingHappened");
GraphGen->ConfigureTransitionEvent(Transition, EventArgs);

Node-class variables

CreateNodeClassVariable adds a blueprint variable to a node-class blueprint (a USMNodeInstance subclass) and stamps its direction, hidden, and read-only flags, the one-shot equivalent of the Logic Driver Variable details panel. ConfigureNodeClassVariable updates those flags on an existing variable.

#include "EdGraphSchema_K2.h"

ISMGraphGeneration::FCreateNodeClassVariableArgs VarArgs;
VarArgs.VariableName = TEXT("Health");
VarArgs.VariableType.PinCategory = UEdGraphSchema_K2::PC_Int;
VarArgs.Direction = ESMGraphPropertyDirection::Output;
GraphGen->CreateNodeClassVariable(MyNodeClassBP, VarArgs);

Compile cost

When any of Direction, bHidden, or bReadOnly is set, the node-class blueprint is compiled in-call so the override can be stamped on the class default object. A plain variable add (no flags) is not compiled. Batch plain adds and compile once for best performance.

Map and Set types cannot be exposed

Map and Set variables cannot be exposed on the node, matching the editor's Variable Details panel filter (see Unsupported Containers). Setting Direction, bHidden, or bReadOnly on a Map or Set variable is rejected; omit those flags to add it as a plain, non-exposed blueprint variable.

Topology editing

These operations restructure an existing graph, mirroring the editor's right-click entries. The ones that can fail for structural reasons take an optional OutError you can surface to the user.

ISMGraphGeneration::FReplaceNodeArgs ReplaceArgs;
ReplaceArgs.Node = SomeState;
ReplaceArgs.TargetKind = ISMGraphGeneration::EReplaceNodeKind::Conduit;

FString Error;
if (USMGraphNode_Base* NewNode = GraphGen->ReplaceNode(ReplaceArgs, &Error))
{
    // SomeState was replaced with a conduit; transitions preserved.
}

API reference

Operation Call
Create blueprint asset ISMAssetManager::CreateStateMachineBlueprint
Create state / nested SM / reference CreateStateNode
Create transition CreateTransitionEdge
Create transition reroute CreateTransitionReroute
Set initial state SetInitialState
Set property value SetNodePropertyValue
Reset property value ResetNodePropertyValue
Resolve property graph FindPropertyGraph
Resolve root state machine graph GetRootStateMachineGraph
Toggle property-graph edit mode SetPropertyGraphEditMode
Connect / disconnect output variable ConnectNodeVariableOutput / DisconnectNodeVariableOutput
State / transition stack CreateStateStackInstance / CreateTransitionStackInstance
Configure state machine reference ConfigureStateMachineReference
Local graph read / write node CreateLocalGraphReadNode / CreateLocalGraphWriteNode
Compatible local graph node types GetCompatibleLocalGraphNodeTypes
Local graph event node CreateLocalGraphEventNode
Compatible local graph event node types GetCompatibleLocalGraphEventNodeTypes
Configure transition event ConfigureTransitionEvent
Node-class variable create / configure CreateNodeClassVariable / ConfigureNodeClassVariable
Remove node RemoveNode
Collapse to nested SM CollapseNodesToStateMachine
Merge states MergeStates
Replace node ReplaceNode
Convert to reference ConvertStateMachineToReference

See Using with C++ for general native usage of Logic Driver. For agent-driven or MCP-driven authoring over this same capability, see Logic Driver Assist.