From Blueprint to Bytecode I - But what is Blueprint?
Curiosity calls adventurers to embark towards an ancient castle - Blueprint. We've heard a lot about how to wield it's magical power over the decades. But little did we know what's actually beneath it. So buckle up, because we are going deep.
The content in this post is based on Unreal Engine 5.4.0
If I made a mistake, please comment below and help future readers!
Preface
Unreal Engine is renowned for its powerful visual scripting system—Blueprint (formerly Kismet
). There are countless tutorials and guides available on how to use Blueprint, but fewer resources explain how it actually works behind the scenes. When we drag and drop nodes in the visual graph, hit the compile button, and see the “Good to go” message, it’s easy to think everything’s just ready to run. But what’s really happening under the hood? How does the Blueprint Virtual Machine (BPVM
) interpret and execute the graph? This series of posts will dig into these questions. So, buckle up and let’s dive in.
Previous Researches
Epic has published a document that offers a brief overview of how the Blueprint compilation process works, but it only scratches the surface and doesn’t dive into the details. Fortunately, the community has contributed some great research as well. For BPVM, there’s the Blueprint VM, and for Blueprint Compilation, there are Blueprint I, Blueprint II, and Blueprint III.
The blogs linked above are written in Chinese. For English readers, a translation tool like ChatGPT could be helpful. {: .prompt-tip}
While these posts are high-quality and definitely worth reading, we’ll still walk through the key concepts and terminology in a more comprehensive way to establish a common understanding as we tackle the next challenges.
Blueprint System
When people talk about a “well-written Blueprint,” they’re typically referring to the visual scripting graph created within the Blueprint Editor. Internally, this graph is managed by a UEdGraph
object. However, it’s important to note that the graph (such as the Event Graph) is not the Blueprint Editor itself, but rather a part of it. The Blueprint Editor is a combination of multiple UEdGraph
s, panels, tools, and more.
To put it simply, creating a Blueprint Asset in the Content Browser starts with selecting a parent class and defining a new subclass through the Blueprint system. The Blueprint Editor lets us add functions and logic within UEdGraph
instances and set up various properties. When we click the compile button in the editor, it triggers the compilation process, processing the content in the UEdGraph
and encapsulating it into a reusable UBlueprintGeneratedClass
. This class contains bytecode that the engine executes during runtime, ensuring that the behaviors and logic we’ve defined are integrated into the game.
Blueprint Structure (Source: 1)
UBlueprint
When we create a new Blueprint based on a custom class type (e.g., ACustomClass
) from the Content Browser, we’re actually creating a Blueprint Asset—more specifically, a UBlueprint
object. This object exists solely within the editor environment. The resulting asset will have a .uasset
file extension on disk, which is the serialized form of the UBlueprint
object.
Let’s take a look at the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
/** Create a new Blueprint and initialize it to a valid state. */
UBlueprint* FKismetEditorUtilities::CreateBlueprint(UClass* ParentClass, UObject* Outer, const FName NewBPName, EBlueprintType BlueprintType, TSubclassOf<UBlueprint> BlueprintClassType, TSubclassOf<UBlueprintGeneratedClass> BlueprintGeneratedClassType, FName CallingContext)
{
// ... Other code
// Create new UBlueprint object
UBlueprint* NewBP = NewObject<UBlueprint>(Outer, *BlueprintClassType, NewBPName, RF_Public | RF_Standalone | RF_Transactional | RF_LoadCompleted);
// ... Other code
NewBP->ParentClass = ParentClass;
// ... Other code
return NewBP;
}
When we call FKismetEditorUtilities::CreateBlueprint()
, it immediately creates a UBlueprint
instance and sets NewBP->ParentClass
to the specified ParentClass
(in this case, ACustomClass
). This is why some documents describe the created Blueprint as a subclass of ACustomClass
. However, this statement is technically incorrect. What actually happens is that a UBlueprint
object is created, and it holds a pointer to ParentClass
(i.e., ACustomClass
), but it’s not a subclass of it.
UBlueprintGeneratedClass
As mentioned earlier, when executing a Blueprint’s logic, we’re not running the UBlueprint
object directly (since it only exists in the editor). Instead, we’re executing the compiled bytecode stored in a compiled version of the UBlueprint
object, which is known as UBlueprintGeneratedClass
.
Technically, the Blueprint Editor triggers the generation of the
UBlueprintGeneratedClass
, but that’s merely a placeholder. The actual compilation process is handled byFKismetCompilerContext
, which compiles the visual scripting nodes into executable code (bytecode) and writes this back into theUBlueprintGeneratedClass
.
Just as UBlueprint
is NOT a subclass of ACustomClass
, UBlueprintGeneratedClass
is also NOT a subclass of ACustomClass
. This means there’s no such thing as:
1
2
3
4
class UBlueprintGeneratedClass : public ACustomClass
{
// ...
};
Instead, the UBlueprintGeneratedClass
is already declared in BlueprintGeneratedClass.h
as:
1
2
3
4
class UBlueprintGeneratedClass : public UClass, public IBlueprintPropertyGuidProvider
{
// ...
};
The UBlueprintGeneratedClass
differs slightly from the UBlueprint
object. While the asset we see in the Content Browser is actually an instance of UBlueprint
(which is a UObject
and serialized as a .uasset
), the UBlueprintGeneratedClass
is just a class, not an instance. The relationship between UBlueprintGeneratedClass
and its parent class is managed using the SuperClass
mechanism. When setting the parent class, Unreal Engine uses SetSuperStruct()
, and when retrieving it, GetSuperClass()
is used. This allows the UBlueprintGeneratedClass
to appear as though it is a subclass of another UClass
.
Here’s the code right after the UBlueprint
instance is created:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Create a new Blueprint and initialize it to a valid state. */
UBlueprint* FKismetEditorUtilities::CreateBlueprint(UClass* ParentClass, UObject* Outer, const FName NewBPName, EBlueprintType BlueprintType, TSubclassOf<UBlueprint> BlueprintClassType, TSubclassOf<UBlueprintGeneratedClass> BlueprintGeneratedClassType, FName CallingContext)
{
// ... Other code
// Create SimpleConstructionScript and UserConstructionScript
if (FBlueprintEditorUtils::SupportsConstructionScript(NewBP))
{
// ... Other code
UBlueprintGeneratedClass* NewClass = NewObject<UBlueprintGeneratedClass>(
NewBP->GetOutermost(), *BlueprintGeneratedClassType, NewGenClassName, RF_Public | RF_Transactional);
NewBP->GeneratedClass = NewClass;
NewClass->ClassGeneratedBy = NewBP;
NewClass->SetSuperStruct(ParentClass);
// <<< Temporary workaround
}
// ... Other code
return NewBP;
}
UEdGraph
An UEdGraph
represents a graph, which is essentially a collection of nodes and connections. In the context of Blueprint, UEdGraph
is used to represent the data within the visual scripting graph, such as the Event Graph, Function Graph, and Macro Graph. The UEdGraph
manages the nodes and connections within the graph and provides the necessary interfaces for the Blueprint Editor to interact with it.
UEdGraph
has several subclasses, including UAIGraph
, UAnimationGraph
, UAnimationStateMachineGraph
, UMaterialGraph
, and more. Essentially, when you open an asset and see a space where you can drag and drop nodes, the underlying type is usually an UEdGraph
.
It’s important to note that UEdGraph
is just the representation of the graph, not its visual display. The actual visual representation of the graph we see in the editor is handled by a Slate UI widget called SGraphEditor
. This widget is responsible for rendering the graph and providing the frontend interactions.
UEdGraph (Source: 1)
UEdGraphNode
UEdGraphNode
is a fundamental class that represents a node in an Unreal Engine graph. It is part of the graph’s data structure and holds the logic and properties of individual nodes within the graph, whether it’s for Event Graphs, Function Graphs, or other types of graphs.
Each UEdGraphNode
contains the necessary logic and data to execute or represent an operation in the graph. For example, a node might represent a function call, a variable assignment, or an action like “Print String.”
There are various subclasses of UEdGraphNode
, such as UAnimStatesNode
, UNiagaraNode
, and more. These subclasses introduce functionality specific to the type of graph in use. It’s worth noting that Blueprint Graph nodes aren’t called UBlueprintNode
; they’re referred to as UK2Node
.
Just like UEdGraph
, UEdGraphNode
also has a visual representation, which is handled by SGraphNode
.
UEdGraphNode (Source: 1)
UEdGraphPin
UEdGraphNode
maintains connections to other nodes through UEdGraphPin
objects, which enable the flow of execution or data between nodes.
A UEdGraphPin
serves as a communication point between nodes. It can be either an input pin (where data flows into the node) or an output pin (where data flows out of the node).
Each pin can be connected to other pins, and the system enforces type compatibility between them. For example, connecting an integer pin to a float pin is allowed because an implicit cast can be performed, but connecting incompatible types is illegal.
Just like nodes, pins have a visual representation in the Blueprint Editor. Users can connect them using drag-and-drop interactions, which are managed by the SGraphPin
class.
UEdGraphSchema
UEdGraphSchema
defines the rules and conventions for a specific type of graph. It essentially acts as a blueprint (literally) for how nodes and pins interact, providing a way to describe valid node connections, node actions, and ensuring the graph behaves as expected.
Each type of graph (Blueprint, Animation, AI, etc.) has its own corresponding UEdGraphSchema
subclass. For example:
The UEdGraphSchema
can also define custom rules for creating and placing nodes within the graph. For instance, it determines which nodes are available when a user right-clicks to add a new node. Additionally, it defines the rules for linking pins between nodes, such as which types of pins can connect to each other or how connections should be made.
UEdGraphSchema (Source: 1)
FKismetCompilerContext
FKismetCompilerContext
is the core class responsible for compiling a Blueprint graph into executable bytecode that the Blueprint Virtual Machine (VM) can interpret. This class is the main driver of the compilation process, handling tasks like node translation, validation, and generating the intermediate representation.
The FKismetCompilerContext
first translates the visual scripting graphs (represented as UEdGraph
, UEdGraphNode
, and others) into an intermediate format consisting of FBlueprintCompiledStatement
objects. These statements will eventually be compiled into bytecode that the VM can execute. It manages the flow of the compilation process, ensuring that all nodes in the graph are correctly translated and connected.
FKismetCompilerContext (Source: 1)
FKismetFunctionContext
FKismetFunctionContext
represents the compilation context for a single function or graph within a Blueprint. It acts as a container for all the data needed to compile a specific function, such as variable definitions, control flow, and individual statements.
The FKismetFunctionContext
tracks the local state of a function during compilation, including variables, temporaries, and flow control structures. It ensures that all nodes within the function are translated into valid intermediate representations (FBlueprintCompiledStatement
).
FBlueprintCompiledStatement
FBlueprintCompiledStatement
is an intermediate representation of a single executable operation within a Blueprint graph. In other words, a function can have multiple FBlueprintCompiledStatement
objects.
Each FBlueprintCompiledStatement
represents a specific operation in the graph. These statements are generated during the compilation process and are later converted into VM bytecode. Below is a full list of FBlueprintCompiledStatement
types from BlueprintCompiledStatement.h
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//////////////////////////////////////////////////////////////////////////
// FBlueprintCompiledStatement
enum EKismetCompiledStatementType
{
KCST_Nop = 0,
// [wiring =] TargetObject->FunctionToCall(wiring)
KCST_CallFunction = 1,
// TargetObject->TargetProperty = [wiring]
KCST_Assignment = 2,
// One of the other types with a compilation error during statement generation
KCST_CompileError = 3,
// goto TargetLabel
KCST_UnconditionalGoto = 4,
// FlowStack.Push(TargetLabel)
KCST_PushState = 5,
// [if (!TargetObject->TargetProperty)] goto TargetLabel
KCST_GotoIfNot = 6,
// return TargetObject->TargetProperty
KCST_Return = 7,
// if (FlowStack.Num()) { NextState = FlowStack.Pop; } else { return; }
KCST_EndOfThread = 8,
// Comment
KCST_Comment = 9,
// NextState = LHS;
KCST_ComputedGoto = 10,
// [if (!TargetObject->TargetProperty)] { same as KCST_EndOfThread; }
KCST_EndOfThreadIfNot = 11,
// NOP with recorded address
KCST_DebugSite = 12,
// TargetInterface(TargetObject)
KCST_CastObjToInterface = 13,
// Cast<TargetClass>(TargetObject)
KCST_DynamicCast = 14,
// (TargetObject != None)
KCST_ObjectToBool = 15,
// TargetDelegate->Add(EventDelegate)
KCST_AddMulticastDelegate = 16,
// TargetDelegate->Clear()
KCST_ClearMulticastDelegate = 17,
// NOP with recorded address (never a step target)
KCST_WireTraceSite = 18,
// Creates simple delegate
KCST_BindDelegate = 19,
// TargetDelegate->Remove(EventDelegate)
KCST_RemoveMulticastDelegate = 20,
// TargetDelegate->Broadcast(...)
KCST_CallDelegate = 21,
// Creates and sets an array literal term
KCST_CreateArray = 22,
// TargetInterface(Interface)
KCST_CrossInterfaceCast = 23,
// Cast<TargetClass>(TargetObject)
KCST_MetaCast = 24,
KCST_AssignmentOnPersistentFrame = 25,
// Cast<TargetClass>(TargetInterface)
KCST_CastInterfaceToObj = 26,
// goto ReturnLabel
KCST_GotoReturn = 27,
// [if (!TargetObject->TargetProperty)] goto TargetLabel
KCST_GotoReturnIfNot = 28,
KCST_SwitchValue = 29,
KCST_DoubleToFloatCast = 30,
KCST_FloatToDoubleCast = 31,
//~ Kismet instrumentation extensions:
// Instrumented event
KCST_InstrumentedEvent,
// Instrumented event stop
KCST_InstrumentedEventStop,
// Instrumented pure node entry
KCST_InstrumentedPureNodeEntry,
// Instrumented wiretrace entry
KCST_InstrumentedWireEntry,
// Instrumented wiretrace exit
KCST_InstrumentedWireExit,
// Instrumented state push
KCST_InstrumentedStatePush,
// Instrumented state restore
KCST_InstrumentedStateRestore,
// Instrumented state reset
KCST_InstrumentedStateReset,
// Instrumented state suspend
KCST_InstrumentedStateSuspend,
// Instrumented state pop
KCST_InstrumentedStatePop,
// Instrumented tunnel exit
KCST_InstrumentedTunnelEndOfThread,
KCST_ArrayGetByRef,
KCST_CreateSet,
KCST_CreateMap,
};
FBPTerminal
FBPTerminal
represents a variable or expression used within a FBlueprintCompiledStatement
. It serves as a handle for data or objects in the Blueprint graph.
“Hello World” Literal FBPTerminal
FNodeHandlingFunctor
FNodeHandlingFunctor
is an abstraction used to handle the translation of specific node types during the compilation process. Each UEdGraphNode
type has an associated FNodeHandlingFunctor
that understands how to convert that node into an intermediate representation (FBlueprintCompiledStatement
).
FNodeHandlingFunctor (Source: 1)
Each UK2Node
subclass has a corresponding FNodeHandlingFunctor
subclass, which defines how that specific node should be compiled. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//////////////////////////////////////////////////////////////////////////
// FKCHandler_Select
class FKCHandler_Select : public FNodeHandlingFunctor
{
protected:
TMap<UEdGraphNode*, FBPTerminal*> DefaultTermMap;
public:
FKCHandler_Select(FKismetCompilerContext& InCompilerContext)
: FNodeHandlingFunctor(InCompilerContext);
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override;
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override;
};
As shown above, FKCHandle_Select
is associated with UK2Node_Select
. It implements two key functions: RegisterNets()
and Compile()
.
RegisterNets()
RegisterNets()
is responsible for registering the input and output pins of the node. It creates the necessary FBPTerminal
objects to represent these pins. For example, if anything is connected to the options pin or the index pin, they are registered during this step. This function is called in both PrecompileFunction()
and CreateLocalsAndRegisterNets()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* First phase of compiling a function graph
* - Prunes the 'graph' to only included the connected portion that contains the function entry point
* - Schedules execution of each node based on data dependencies
* - Creates a UFunction object containing parameters and local variables (but no script code yet)
*/
void FKismetCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags)
{
// ... Other Code
if (EntryPoints.Num())
{
Context.EntryPoint = EntryPoints[0];
// Register nets from function entry/exit nodes first, even for skeleton compiles (as they form the signature)
// We're violating the FNodeHandlingFunctor abstraction here because we want to make sure that the signature
// matches even if all result nodes were pruned:
bool bReturnNodeFound = false;
for (UEdGraphNode* Node : Context.SourceGraph->Nodes)
{
// ... Other Code
if (FNodeHandlingFunctor* Handler = NodeHandlers.FindRef(Node->GetClass()))
{
if (Handler->RequiresRegisterNetsBeforeScheduling())
{
Handler->RegisterNets(Context, Node);
}
}
}
// ... Other Code
}
}
void FKismetCompilerContext::CreateLocalsAndRegisterNets(FKismetFunctionContext& Context, FField**& FunctionPropertyStorageLocation)
{
// ... Other Code
if (bIsFullCompile)
{
// ... Other Code
// Register nets for any nodes still in the schedule (as long as they didn't get registered in the initial all-nodes pass)
for (UEdGraphNode* Node : Context.LinearExecutionList)
{
if (FNodeHandlingFunctor* Handler = NodeHandlers.FindRef(Node->GetClass()))
{
if (!Handler->RequiresRegisterNetsBeforeScheduling())
{
Handler->RegisterNets(Context, Node);
}
}
// ... Other Code
}
}
// ... Other Code
}
CreateLocalsAndRegisterNets()
can be called in either PrecompileFunction()
or CompileFunction()
. The reason we say “or” is that, normally, PrecompileFunction()
will call it, but if EInternalCompilerFlags::PostponeLocalsGenerationUntilPhaseTwo
is passed as a parameter, PrecompileFunction()
will skip this step. In that case, CompileFunction()
will call it later during “Phase Two.”
EInternalCompilerFlags::PostponeLocalsGenerationUntilPhaseTwo
is passed toCompileFunction()
inFBlueprintCompilationManagerImpl::FlushCompilationQueueImpl()
.BlueprintCompilationManager
is a large topic in itself, so we won’t dive into it here.
Compile()
The Compile()
function is responsible for generating the intermediate representation (FBlueprintCompiledStatement
) for the node, based on its input and output pins. Let’s take a closer look:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
// I. Declare FBPTerminals
UK2Node_Select* SelectNode = CastChecked<UK2Node_Select>(Node);
FBPTerminal* DefaultTerm = nullptr;
FBPTerminal* ReturnTerm = nullptr;
FBPTerminal* IndexTerm = nullptr;
{
// II. Try to get value for these terms
// ... Other Code
}
// III. Create FBlueprintCompiledStatement for SelectNode
FBlueprintCompiledStatement* SelectStatement = new FBlueprintCompiledStatement();
SelectStatement->Type = EKismetCompiledStatementType::KCST_SwitchValue;
// IV. Get the option pins
TArray<UEdGraphPin*> OptionPins;
SelectNode->GetOptionPins(OptionPins);
// V. Go through each option and add their value to SelectStatement
for (int32 OptionIdx = 0; OptionIdx < OptionPins.Num(); ++OptionIdx)
{
{
FBPTerminal* LiteralTerm = Context.CreateLocalTerminal(ETerminalSpecification::TS_Literal);
// ... Other Code
SelectStatement->RHS.Add(LiteralTerm);
}
{
UEdGraphPin* NetPin = OptionPins[OptionIdx] ? FEdGraphUtilities::GetNetFromPin(OptionPins[OptionIdx]) : nullptr;
FBPTerminal** ValueTermPtr = NetPin ? Context.NetMap.Find(NetPin) : nullptr;
FBPTerminal* ValueTerm = ValueTermPtr ? *ValueTermPtr : nullptr;
// ... Other Code
SelectStatement->RHS.Add(ValueTerm);
}
}
// VI. Add DefaultTerm to SelectStatement
SelectStatement->RHS.Add(DefaultTerm);
}
The Compile()
function for the SelectNode
essentially follows six steps:
- Declare
FBPTerminals
:- It starts by declaring the necessary
FBPTerminal
objects.
- It starts by declaring the necessary
- Get Values for Terms:
- It retrieves the values for these terms.
- Create
FBlueprintCompiledStatement
:- A
FBlueprintCompiledStatement
is created for the SelectNode, and its type is set toKCST_SwitchValue
.
- A
- Retrieve Option Pins:
- It then collects all the option pins.
- Process Each Option:
- For each option, its value is added to the
SelectStatement
.
- For each option, its value is added to the
- Add Default Term:
- Finally, the DefaultTerm is added to the
SelectStatement
.
- Finally, the DefaultTerm is added to the
Literal Term
- For each option, a
FBPTerminal
of type Literal is created. This represents the literal value associated with that option (e.g., the index value that matches the option). If the option pin is associated with an enum, the terminal uses the enum name; otherwise, it defaults to an index-based name.
Value Term
- For each option pin, the corresponding value term is retrieved from the context. If it isn’t found, an error is logged. The value is then added to the RHS (right-hand side) of the operation.
At this point, it’s clear that the Compile()
function for the SelectNode
creates a FBlueprintCompiledStatement
, sets its type to KCST_SwitchValue
, and then feeds all the necessary data into the SelectStatement
object.
FBlueprintCompileReinstancer
FBlueprintCompileReinstancer
is a utility class in Unreal Engine that helps with reinstancing objects when a Blueprint class is recompiled. Reinstancing is necessary because existing instances of a Blueprint class in the game world need to be updated to reflect the newly compiled version of the class.
When a Blueprint class is recompiled, changes to its structure (such as new variables or altered logic) can cause the existing instances in the game world to become out of sync with the new class definition. The FBlueprintCompileReinstancer
ensures that these instances are properly updated or replaced, preventing crashes or inconsistencies.
FKismetCompilerOptions
FKismetCompilerOptions
is a configuration class that defines various options and settings used during the Blueprint compilation process. It allows customization of how Blueprints are compiled, influencing behaviors like debugging, optimization, and error handling.
FKismetCompilerOptions
is passed to the FKismetCompilerContext
to control specific aspects of the compilation process, such as whether debug information is generated or whether strict validation is enforced.
The header file already gives us a very clear idea of what it does:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/** Options used for a specific invication of the blueprint compiler */
struct FKismetCompilerOptions
{
public:
/** The compile type to perform (full compile, skeleton pass only, etc) */
EKismetCompileType::Type CompileType;
/** Whether or not to save intermediate build products (temporary graphs and expanded macros) for debugging */
bool bSaveIntermediateProducts;
/** Whether to regenerate the skeleton first, when compiling on load we don't need to regenerate the skeleton. */
bool bRegenerateSkelton;
/** Whether or not this compile is for a duplicated blueprint */
bool bIsDuplicationInstigated;
/** Whether or not to reinstance and stub if the blueprint fails to compile */
bool bReinstanceAndStubOnFailure;
/** Whether or not to skip class default object validation */
bool bSkipDefaultObjectValidation;
/** Whether or not to update Find-in-Blueprint search metadata */
bool bSkipFiBSearchMetaUpdate;
/** Whether or not to use Delta Serialization when copying unrelated objects */
bool bUseDeltaSerializationDuringReinstancing;
/** Whether or not to skip new variable defaults detection */
bool bSkipNewVariableDefaultsDetection;
};
Skeleton Class
A Skeleton Class
is an intermediate representation of a Blueprint class used during the compilation process. It serves as a lightweight placeholder that contains only basic information about the class structure (such as variables and functions) but without full implementation details.
Think of the
SKEL
class as a more intelligent forward declaration or a header file. It is created before any bytecode is compiled and only provides metadata about the class structure. The key difference is that there’s no linkage process after compilation, unlike a traditional header file.
The Skeleton Class
exists to allow Blueprints to reference each other in a cyclic manner during compilation. For example, if two Blueprints reference each other, their Skeleton Classes
are created first, resolving dependency issues. It acts as a minimal version of the class that can be used in the editor before full compilation is completed.
When one Blueprint calls a function on another Blueprint class that hasn’t been fully compiled yet, the Skeleton Class is used to temporarily represent the target class.
CDO
The Class Default Object (CDO) is a special instance of a class that serves as the archetype for all instances of that class. It is automatically created by Unreal Engine for every class, including Blueprint classes.
The purpose of the CDO
is to store the default property values and settings for the class. When a new instance of the class is created, it is initialized using the values stored in the CDO
. In essence, the CDO
represents the canonical state and configuration of the class by default.
When you edit default properties in a Blueprint editor, you’re modifying the CDO
of the class. Similarly, when you create an instance of a Blueprint, it inherits its properties from the CDO
. If you edit a property of an instance and then choose to revert it, the modification is reset to the CDO’s
values.
Bonfire Lit, What’s Next?
Phew, that’s a lot of information to digest. We’ve covered the basic structure of the Blueprint system, including:
UBlueprint
UBlueprintGeneratedClass
UEdGraph
UEdGraphNode
UEdGraphPin
UEdGraphSchema
FKismetCompilerContext
FKismetFunctionContext
FBlueprintCompiledStatement
FBPTerminal
FNodeHandlingFunctor
FBlueprintCompileReinstancer
FKismetCompilerOptions
Skeleton Class
CDO
A secure chamber found and a bonfire lit, we’ve taken our first step into the dark castle of Blueprint. However, something seems to be hidden in the shadows.