Earlier in this chapter you saw how Unreal objects work in C++: a class inheriting from AActor, variables and functions exposed with macros like UPROPERTY and UFUNCTION. This section covers the tool Unreal developers reach for just as often, sometimes more: Blueprints, Unreal's visual scripting system. By the end of this section you will be able to read a Blueprint graph the same way you read a page of C++, know when to reach for one over the other, and understand how the two talk to each other in a real project.
Every section below follows the same shape as earlier chapters: a small example, a trace of what actually happens, then a plain explanation. Blueprints do not print console output the way C++ does, so instead of "real output" you will see a worked trace of the graph — which node runs, in what order, with what values.
A Blueprint is Unreal Engine's visual scripting system. Instead of typing lines of C++ text, you place small boxes called nodes on a canvas and connect them with lines called wires. Each node does one small thing — "when the game starts," "add two numbers," "play a sound." The wires say what order things happen in, and what data flows where.
Under the hood, a Blueprint still becomes real bytecode (compiled instructions the engine's virtual machine runs) when you click Compile. You are not "faking" code — you are writing code with boxes and lines instead of text. Almost anything you can build in a Blueprint graph could, in theory, be rewritten as C++. The value of Blueprints is not that they do something C++ cannot do; it is that they are faster to read, faster to change, and do not require a C++ compiler and a project rebuild to test.
Both sides above do the same job. The left side is text you compile with a C++ compiler. The right side is a graph you compile by clicking a button in the editor — no separate build tool, no waiting for a whole C++ module to relink.
When you open a Blueprint (for example, double-click BP_Coin in the Content Browser), you see several panels. A beginner does not need to memorize all of them, but should recognize these:
Beginners sometimes assume Blueprints are a "toy" version of scripting for hobbyists, and that professional studios only use C++. That is wrong. Fortnite, Gears of War, and most AAA Unreal titles ship with thousands of Blueprint graphs in the final game. There are concrete reasons for this:
The real-world pattern is not "C++ or Blueprint." It is "C++ for the parts that need to be fast or reused everywhere, Blueprint for the parts that need to change often." Sections 8 and 9 cover this in detail.
Every chain of nodes in a Blueprint has to start somewhere. It starts at an Event node — a node with no execution input, only an execution output, that fires automatically when something happens. Event nodes are drawn in red in the editor.
The most common Event nodes a beginner will use:
Event BeginPlay — fires once, when this object is spawned into the level and ready. Equivalent to Unity's Start().Event Tick — fires every single frame, and hands you Delta Seconds (the time in seconds since the last frame). Equivalent to Unity's Update().Event ActorBeginOverlap / Event ActorEndOverlap — fires when this actor's collision starts or stops touching another actor's collision.InputAction Jump — fires when the player presses a bound key or button.Event Tick. Tick runs 30 to 240+ times per second depending on frame rate. Logic that does not need to run every single frame should not live in Tick — see Section 11.A node has up to four kinds of connection points, called pins:
The white triangle pins connect with execution wires (also called exec wires). These are the "and then" of the graph — they define order, exactly like semicolons and line breaks define order in C++. The colored circle pins connect with data wires — these are the "using this value" of the graph, exactly like a variable or expression passed as an argument in C++.
Pin color tells you the data type, the same way a C++ type tells you what a variable holds:
bool)int32); a different shade of green/teal = FloatA data input pin with no wire attached still works — it uses a default value typed directly into the pin (shown as a small text box on the node itself). Once you attach a wire, that default box disappears; the wire now supplies the value instead.
A Blueprint variable works like a member variable in a C++ class — it stores a value that belongs to this object, can be read or changed anywhere in the graph, and keeps its value between frames.
You create one in the My Blueprint panel by clicking the + next to Variables, naming it, and picking a type (Boolean, Integer, Float, Vector, Actor Reference, and so on — the same idea as choosing a type in C++, just from a dropdown instead of typing int32 or bool).
To use a variable in the graph, drag it from the My Blueprint panel onto the canvas. The editor asks whether you want a Get node (reads the value) or a Set node (writes the value).
Notice the Get node has no white triangle pins at all. Reading a variable does not "do" anything in terms of order — it just hands back a value instantly. This kind of node is called pure. The Set node changes something (it writes memory), so it needs an exec pin to say when in the sequence that write happens. This is called impure.
Two checkboxes matter for a variable, visible in the Details panel after you select it:
These four node types are the actual "logic" building blocks of a graph — the visual equivalents of function definitions, callbacks, if, and loops in C++.
A Blueprint Function is the same idea as a C++ function: a named, reusable block of nodes that takes inputs, optionally returns an output, and can be called from multiple places. You create one in the My Blueprint panel under Functions, then build its graph the same way you build the Event Graph, except a Function starts with an Entry node (its parameter list) instead of an Event node.
// The C++ equivalent of a Blueprint function "AddScore"
void AGameCharacter::AddScore(int32 Amount)
{
Score += Amount;
if (Score >= 100 && !bHasWon)
{
bHasWon = true;
OnPlayerWon();
}
}
A Function can also be marked Pure (no exec pins, like the Get node in Section 5) if it only computes and returns a value without changing anything — for example, a function that returns true if the player has enough gold to buy an item, without spending the gold.
A Custom Event node looks similar to a Function but is placed directly on the Event Graph, and can be called from other Blueprints or triggered by an Event Dispatcher (Section 10). Rule of thumb: use a Function when you need a return value or you are only calling it from inside this same Blueprint; use a Custom Event when something else needs to trigger it, or when it fits naturally as "a thing that happens" rather than "a value I compute."
C++ control-flow keywords like if, for, and while become specific nodes in Blueprint:
Branch — the Blueprint version of if. One Boolean data input pin, two exec output pins labelled True and False.ForLoop — runs its body once for each integer from a First Index to a Last Index, similar to for (int i = First; i <= Last; i++). Has a Loop Body exec pin (fires each time) and a Completed exec pin (fires once, after the loop ends).ForEachLoop — runs its body once per element of an array, similar to a range-based for (auto& Elem : Array).WhileLoop — runs its body repeatedly as long as a Boolean condition stays true, similar to while (condition) { ... }.Completed pin is harmless (it just means nothing happens after). What trips people up is forgetting that Loop Body fires once per element, before moving to the next one — including any exec chain hanging off it. If that chain does something slow, like spawning an actor, doing it 10,000 times in one frame inside a ForEachLoop will freeze the game for that frame.One more control-flow node worth knowing: Sequence. It has one exec input and several numbered exec outputs (Then 0, Then 1, Then 2...), all of which fire in order, one after another, on the same frame. It is not a loop — it is a way to say "do all of these, in this order," useful when a node has only one exec output pin but you need to kick off several different chains from it.
Now put Sections 3 through 6 together into one real example: a coin the player can walk over to collect. This graph lives inside a Blueprint called BP_Coin, an Actor with a sphere collision component and a static mesh (the coin's visual model).
The goal: when the player overlaps the coin, add 10 to the player's score, play a pickup sound, and remove the coin — but only once, even if the overlap event somehow fires twice.
Reading this the same way you would read C++ line by line:
NOT bCollected. The first time, bCollected is false, so NOT false is true, and execution goes down the True path.BP_Coin can get a reference to the player without hardcoding one).Every node here matches something you already know from C++: an if statement, a boolean flag used as a one-shot guard, a function call, and cleanup at the end of a scope. The visual form just draws the order and the data flow instead of you inferring it from indentation and semicolons.
Target pin unwired quietly assumes "Self" — the object that owns this graph. In [Call AddScore] above, forgetting to wire Target to Get PlayerCharacterRef would make the node try to call AddScore on the coin itself. Since BP_Coin has no such function, the graph fails to compile, and the error points at that pin.This is the most common question a beginner asks after seeing both. There is no single rule, but there are strong signals for each side.
In real Unreal projects, you almost never pick "all Blueprint" or "all C++" for an entire game. The standard pattern: write a C++ base class that does the heavy lifting, then create a Blueprint that inherits from it and fills in the specific, per-content details.
// AGameCharacter.h -- the C++ base class
UCLASS()
class MYGAME_API AGameCharacter : public ACharacter
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Score")
int32 Score = 0;
UFUNCTION(BlueprintCallable, Category = "Score")
void AddScore(int32 Amount);
// BlueprintImplementableEvent: C++ declares this, but the BODY
// is drawn as a graph inside a Blueprint subclass.
UFUNCTION(BlueprintImplementableEvent, Category = "Score")
void OnScoreChanged(int32 NewScore);
};
Three macros make C++ and Blueprint talk to each other, and every beginner should recognize them on sight:
UPROPERTY(BlueprintReadWrite) or (BlueprintReadOnly) (a macro that exposes a C++ member variable to the engine, and optionally to Blueprint) — a Blueprint graph can Get, and optionally Set, this variable exactly like one created directly in the Blueprint.UFUNCTION(BlueprintCallable) (a macro that exposes a C++ function to the engine, and optionally to Blueprint) — the function appears as a callable node in any Blueprint graph. This is exactly how AddScore in Section 7's example graph exists as a node even though its logic lives in C++.UFUNCTION(BlueprintImplementableEvent) — the reverse direction. C++ declares the function's name and signature but writes no body at all. A Blueprint subclass draws the node's body as a graph. This lets a designer decide "what happens when score changes" (flash the UI, play a jingle) without touching C++.This split means the programmer owns Score, AddScore, and anything that must be fast or correct. The designer owns what the screen does when the score changes, which is tuned constantly during playtesting and does not need a programmer in the loop for every change.
BlueprintNativeEvent instead of BlueprintImplementableEvent. It lets C++ provide a default body and lets a Blueprint override it if it wants to. Look this up once you are comfortable with the pattern above.The coin example in Section 7 called AddScore on "the player." How does BP_Coin get a hold of the player Blueprint to call a function on it? There are three common ways, from most rigid to most flexible.
If a Blueprint has a variable of type Actor Reference (or a more specific type like BP_PlayerCharacter) pointing at another specific object, it can call that object's functions directly — the same way a C++ pointer lets you call a method on another object. Simple, but tightly coupled: BP_Coin would need to know the exact class BP_PlayerCharacter exists, which breaks if you ever reuse BP_Coin in a project with a different player class.
A Blueprint Interface is a named list of functions with no implementation, that any Blueprint can "implement." This should look familiar — it is the same idea as a C++ abstract base class with pure virtual functions, or a C# interface. BP_Coin can call "Give Score" on whatever overlapped it, as long as that actor implements the BPI_ScoreReceiver interface, without ever knowing or caring what specific class it is.
// The C++ equivalent of a Blueprint Interface
UINTERFACE(BlueprintType)
class UScoreReceiver : public UInterface
{
GENERATED_BODY()
};
class IScoreReceiver
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Score")
void GiveScore(int32 Amount);
};
An Event Dispatcher is a Blueprint's way of saying "something happened, and I don't care who is listening." It is the same idea as a C++ multicast delegate or a C# event. The Blueprint that owns the dispatcher calls (fires) it; any number of other Blueprints can bind to it beforehand to be notified when it fires. The object firing the dispatcher does not need a reference to the listeners at all — the listeners subscribed to it, not the other way around.
// The C++ equivalent of a Blueprint Event Dispatcher
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCoinCollected, int32, Amount);
UCLASS()
class MYGAME_API ABP_Coin : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable, Category = "Score")
FOnCoinCollected OnCoinCollected;
};
Use a direct reference for a tight, one-off relationship (a door that only ever opens for one specific switch). Use an Interface when many different classes need to respond to the same call in their own way. Use an Event Dispatcher when the object that detects something should not need to know who cares about it — the coin should not need to know the HUD exists.
Blueprint nodes run slower than equivalent hand-written C++, because each node carries extra bookkeeping (the engine's virtual machine interprets the graph rather than running raw machine instructions). For most gameplay glue — reacting to an overlap once, calling a function when a button is pressed — this cost is far too small to matter. It becomes a real problem in a few specific situations:
Cast To node checks a type at runtime and costs a little time. Casting once outside a loop and reusing the result is far cheaper than casting inside a ForEachLoop body that runs thousands of times.// Getting overlapping actors returns an array -- cheap once,
// expensive if you call this every frame for many objects.
TArray<AActor*> OverlappingActors;
GetOverlappingActors(OverlappingActors, ABP_PlayerCharacter::StaticClass());
if (OverlappingActors.Num() > 0 && !bCollected)
{
bCollected = true;
}
stat unit, stat game, and the Unreal Insights tool) that shows exactly where frame time goes. Do not guess about performance — measure first, then optimize the actual bottleneck. This is the same "measure, don't guess" discipline from earlier chapters on debugging.Remember Section 9's hybrid pattern is itself a performance tool: when a Blueprint graph turns out to be both hot (runs often) and slow, the fix is often not to abandon Blueprint entirely — it is to move that one function into C++, expose it with BlueprintCallable, and keep everything around it exactly as it was.
BeginPlay, Tick, overlap, input).if; one Boolean input, True/False exec outputs.for (by index), range-based for (by element), and while.E key should open the door if the player has a key (a Boolean variable bHasKey on the player), or print the string "Locked!" if they do not. Pressing E while not near the door should do nothing. Describe (in words or an ASCII diagram like the ones in this section) which Event nodes you need and how the Branch logic should be wired.An InputAction event alone cannot tell whether the player is near the door — key presses are global. The graph needs a Boolean flag tracking "is the player currently in range," set by the door's own overlap events, then checked when the key is pressed.
Three separate Event nodes feed this graph: the two overlap events only maintain the bInRange flag and do nothing else; the input event does the actual work, but only after checking bInRange first, then bHasKey nested inside it. Two Branches in a row here behave like a C++ if (bInRange) { if (bHasKey) { ... } else { ... } }.
The [Call AddScore] node has its data pin filled in correctly (Amount: 10), but no execution wire runs into it. Execution flow goes straight from Set bCollected = true to Play Sound at Location, skipping Call AddScore entirely. A node with no exec wire connected into it never runs, no matter how correctly its data pins are filled in — data pins only supply values, they do not trigger anything by themselves.
The fix: drag a new execution wire from Set bCollected = true's exec output into Call AddScore's exec input, then from Call AddScore's exec output into Play Sound at Location's exec input, restoring the full chain: Branch (True) -> Set bCollected -> Call AddScore -> Play Sound -> Destroy Actor.
(a) C++. It runs constantly, for many agents, and is a stable algorithm once written -- the "low change, high performance need" case from Section 8.
(b) Blueprint. It is specific to one encounter, will be tweaked constantly during playtesting ("HP < 30%? Maybe 25% feels better"), and does not need to be fast -- it fires once.
(c) Hybrid. The core movement math belongs in a C++ base class so every character shares one correct, fast implementation. The per-character reactions (VFX, sound) are declared in C++ as BlueprintImplementableEvent functions and given a body in each character's Blueprint subclass -- the exact pattern from Section 9.
(d) Blueprint. It is one-off content for one level, likely built and tuned by a designer, with no performance concerns.
(e) C++. Serialization needs to be correct, fast, and stable -- a bug here can corrupt a player's save file, and the format does not change once it is settled.
You now know how to read a Blueprint graph the way you read C++: events start a chain, white exec wires set the order, colored data wires carry values, variables and functions work like their C++ counterparts, and Branches/loops are just if/for/while drawn as boxes. You also know the question that matters more than "Blueprint or C++": most real systems are both, a C++ base class carrying the fast and stable parts, a Blueprint subclass carrying the parts that change every playtest -- talking to each other through direct references, interfaces, or event dispatchers depending on how tightly they should be coupled.