5.3 C++ in Unreal

Phase 5 · Unreal Engine (alternative) · Study time: 60–100 h

Writing gameplay and systems in Unreal C++, the UPROPERTY / UFUNCTION macros, and how C++ and Blueprints work together.

You already know standard C++: classes, pointers, the stack and the heap, templates, the standard library. This lesson is about what changes when you write C++ inside Unreal Engine. Short answer: the language does not change. A large framework sits on top of it, and that framework talks to your code through macros. This lesson walks through those macros, Unreal's own types, its memory model, and how a small piece of your C++ ends up controllable from Blueprint (Unreal's visual scripting language).

1. Unreal C++ Is Still C++, Plus a Framework

Everything you already know about C++ still applies inside Unreal Engine: classes, inheritance, templates, references, the standard library, new and delete, all of it still compiles. Unreal does not invent a new language. What it adds is a big framework of base classes (UObject, AActor, UActorComponent, and more) plus a set of macros that plug your classes into Unreal's own systems: the editor, the garbage collector, save games, network replication, and Blueprint.

Think of it like this: standard C++ is the language. Unreal C++ is that same language used inside a very large, opinionated library — similar to how learning C did not change when you started using a graphics library on top of it. The difference is that Unreal's "library" also includes code generation tools that read your class and add extra C++ behind the scenes. That part is new, and it is the subject of this lesson.

Standard C++ (language + STL) | v Unreal Engine C++ framework (UObject, UCLASS/UPROPERTY/UFUNCTION macros, TArray/TMap/FString, Garbage Collector, Blueprint) | v Your game code (Actors, Components, Structs, ...)

Compare a plain C++ class to an Unreal Actor class (an Actor is Unreal's base class for "a thing that can be placed in a level"):

// Plain standard C++ - nothing Unreal-specific
class Player
{
public:
    void TakeDamage(float Amount);
private:
    float Health = 100.0f;
};
// Unreal C++ - same idea, plugged into the framework
UCLASS()
class MYGAME_API APlayerCharacter : public AActor
{
    GENERATED_BODY()

public:
    APlayerCharacter();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
    float Health = 100.0f;

    UFUNCTION(BlueprintCallable, Category = "Health")
    void TakeDamage(float Amount);
};

Four new things appear: the class inherits from AActor instead of nothing; there is a UCLASS() macro right above the class; there is a GENERATED_BODY() macro as the first line inside the class; and MYGAME_API appears before the class name (a macro that exports the class so other modules can use it — more on modules in section 11). None of this is new syntax. They are all just macros, and macros are plain text substitution, something you already know from the C chapters.

Tip If you have used Unity, think of UCLASS() plus GENERATED_BODY() as doing a similar job to Unity recognizing a class as a MonoBehaviour and showing its fields in the Inspector — except Unreal needs you to say it explicitly with macros, because plain C++ has no reflection built in the way C# does.

2. Reflection, Recap: How Unreal Reads Your Macros

Reflection means code being able to inspect its own types while the program runs — to ask "what fields does this class have?" or "call this function by its name, given as a string." Plain C++ cannot do this. There is no built-in way, at runtime, to list the members of a class or call a function by name.

Unreal needs this ability for many things: showing your class's fields in the editor's Details panel, letting Blueprint call your functions, saving and loading your objects, replicating data over the network, and letting the garbage collector find which objects point to which other objects. Since C++ does not give this to Unreal for free, Unreal builds it itself, using a code generation step that runs before the real compiler.

That step is called UnrealHeaderTool (UHT). Every time you build your project, UHT scans your header files for the macros you wrote (UCLASS, USTRUCT, UENUM, UPROPERTY, UFUNCTION), and for each header it generates an extra file of plain C++ code that registers your class, its properties, and its functions into Unreal's reflection system. That generated file is named YourClassName.generated.h, and your header must #include it — always as the last include in the file.

MyActor.h (contains UCLASS / UPROPERTY / UFUNCTION macros) | | UnrealHeaderTool (UHT) scans the macros v MyActor.generated.h (plain C++, written FOR you, not BY you) | | #include "MyActor.generated.h" (last line of includes) v Normal C++ compiler builds everything together | v Reflection data now exists for AMyActor, so: - the Garbage Collector can find its UPROPERTY pointers - the editor Details panel can show its UPROPERTY fields - Blueprint can call its UFUNCTION functions

You never open or edit a .generated.h file. It is regenerated every build. If you forget to include it, or forget GENERATED_BODY(), the build fails with a compiler error pointing at the missing generated code — that is usually the first error a beginner hits, and now you know why it happens.

Common mistake Putting #include "MyActor.generated.h" anywhere except the very last include in the header. UHT expects it last; putting it earlier (or forgetting it) causes a compile error.

3. UCLASS, USTRUCT, UENUM — Marking Your Types

These three macros tell UHT "this type should exist in Unreal's reflection system." Each one goes on a different kind of type:

UCLASS()
class MYGAME_API AEnemyBase : public AActor
{
    GENERATED_BODY()
};

USTRUCT(BlueprintType)
struct FLootEntry
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString ItemName;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 DropChance = 0;
};

UENUM(BlueprintType)
enum class EEnemyState : uint8
{
    Idle,
    Chasing,
    Attacking,
    Dead
};

Notice the naming prefixes — this is not decoration, Unreal's own tools expect it:

Every UCLASS and USTRUCT body must start with GENERATED_BODY() — that macro is where UHT inserts the plumbing (constructors, serialization helpers, reflection registration) that makes the rest of the macros work. Without it, none of the UPROPERTY or UFUNCTION lines below it will do anything.

Tip BlueprintType on a USTRUCT or UENUM means "Blueprint is allowed to create variables of this type." Without it, the struct or enum still compiles fine in C++, but Blueprint graphs cannot use it directly.

4. UPROPERTY — Exposing Data Fields

UPROPERTY() goes above a class or struct member variable and tells Unreal "track this field." Tracking means three separate things, and you choose which ones you want using specifiers inside the parentheses: whether the Garbage Collector should follow this field if it is a pointer, whether the editor should show it, and whether Blueprint can read or write it.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float MoveSpeed = 300.0f;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stats")
int32 Score = 0;

UPROPERTY(EditDefaultsOnly, Category = "Combat")
float BaseDamage = 10.0f;

Common specifiers:

A field with no UPROPERTY() at all is invisible to every one of those systems: no editor, no Blueprint, and — this matters a lot — no garbage collector tracking if it is a pointer to a UObject. Section 8 comes back to why that last part can crash your game.

Tip If you have used Unity's [SerializeField], UPROPERTY(EditAnywhere) is doing a similar job: it exposes a private-feeling field to the editor without making it a public C++ field to every other class.

5. UFUNCTION — Exposing Functions

UFUNCTION() goes above a member function and, like UPROPERTY, tells Unreal to register it in reflection. The most common reason to do this is to let Blueprint call your C++ function.

UFUNCTION(BlueprintCallable, Category = "Combat")
void Fire();

UFUNCTION(BlueprintPure, Category = "Health")
float GetHealthPercent() const;

UFUNCTION(BlueprintImplementableEvent, Category = "Combat")
void OnWeaponFired();

There is a fourth, closely related option worth knowing: BlueprintNativeEvent, which is like BlueprintImplementableEvent but C++ DOES provide a default body (written as OnWeaponFired_Implementation()), which Blueprint can optionally override. This lesson will not use it, but you will see it in other people's code.

Common mistake Marking a function BlueprintPure when it actually changes game state (like incrementing a counter). Blueprint may evaluate a pure node zero, one, or many times depending on how many other nodes need its output — side effects inside a "pure" function lead to confusing, inconsistent bugs.

6. Unreal's Own Types: FString, FVector, TArray, TMap

Unreal ships its own string type, math types, and container types, and uses them everywhere instead of std::string, std::vector, and friends. You will use these constantly, so see them working together first, then section 7 explains why they exist at all.

FString Name = TEXT("Slime");
FVector Location(100.0f, 0.0f, 50.0f);

TArray<int32> Scores;
Scores.Add(10);
Scores.Add(20);

TMap<FString, int32> Inventory;
Inventory.Add(TEXT("Potion"), 3);
Inventory.Add(TEXT("Sword"), 1);

UE_LOG(LogTemp, Warning, TEXT("Name: %s, Score count: %d"), *Name, Scores.Num());

Expected output (in Unreal's Output Log window):

LogTemp: Warning: Name: Slime, Score count: 2

What each type is for:

The * before Name in the UE_LOG call converts the FString into a raw character pointer, which is what the %s format specifier needs — FString overloads the dereference operator * for exactly this purpose.

Tip The naming pattern is consistent across the whole engine: T prefix means "template container" (TArray, TMap, TObjectPtr, section 9), F prefix means "plain struct" (FString, FVector), and int types are fixed-width (int32, uint8) instead of plain int, so their size never depends on platform or compiler.

7. Why Unreal Doesn't Just Use std::string and std::vector

This is a fair question if you already know the standard library well — why relearn container names for what looks like the same functionality? Four real reasons:

Reflection needs to understand the container

UHT (section 2) can generate reflection code for TArray, TMap, and friends because Unreal wrote both the containers and the tool that reads them, together, as one system. UHT has no idea what std::vector is; it cannot generate reflection data for it, which means a std::vector field cannot be a UPROPERTY at all — it will not show in the editor, will not be visible to Blueprint, and if it holds UObject* pointers, will not be tracked by the garbage collector.

std::vector<AActor*> Enemies; // UHT cannot see this -> NOT reflected, NOT visible to GC, NOT visible to Blueprint, NOT saved to disk UPROPERTY() TArray<TObjectPtr<AActor>> Enemies; // UHT DOES see this -> reflected, tracked by the Garbage Collector, visible in the editor and to Blueprint, can be saved

Garbage collector integration

Section 8 covers this in depth, but the short version: the garbage collector needs to walk every UPROPERTY container looking for UObject pointers inside it. TArray and TMap are built to support that walk; std::vector is not part of that system at all.

One memory system across every platform

Unreal ships on PC, consoles, and mobile, each with different memory constraints. Its containers all allocate through Unreal's own memory system (FMemory), which the engine's memory profiler and platform-specific allocators plug into. Using a separate, unrelated allocation path (raw std::vector) would sidestep those tools.

Blueprint needs a container it can display

A UPROPERTY(EditAnywhere) TArray<int32> shows up in the editor as an expandable list you can add rows to with a + button. That editor widget is built against TArray's reflection data specifically — there is no equivalent for a raw std::vector.

None of this means the standard library is banned. You can still use std::vector, std::unique_ptr, algorithms from the standard algorithm header, and so on inside your own C++ logic — Unreal will not stop you. The rule of thumb is: use Unreal's types for anything that touches UPROPERTY, Blueprint, the editor, or a UObject; standard library types are fine for purely internal C++ that never crosses that boundary.

8. UObject Memory and the Garbage Collector

Recall from the C chapters: every new needs a matching delete, or you leak memory; every delete must happen exactly once, or you get a double-free or a dangling pointer. Unreal removes that entire responsibility for one specific family of objects: anything derived from UObject (which includes every AActor, since AActor derives from UObject).

UObject-derived objects are managed by Unreal's Garbage Collector (GC): a system that periodically figures out which UObjects are still reachable and destroys the ones that are not, automatically. You do not call delete on them, and — this is the important, easy-to-get-wrong part — you do not create them with plain new either.

// WRONG - do not construct UObjects with raw new
AEnemyBase* Enemy = new AEnemyBase();

// RIGHT - Actors are created through the World
AEnemyBase* Enemy = GetWorld()->SpawnActor<AEnemyBase>(
    EnemyClass, SpawnLocation, SpawnRotation);

// RIGHT - non-Actor UObjects are created with NewObject
UInventoryComponent* Inv = NewObject<UInventoryComponent>(this);

SpawnActor and NewObject both register the new object with the engine's object system so the garbage collector knows it exists. Plain new skips that registration — the object might work for a while, but Unreal's systems (saving, replication, GC) do not know it exists, and you are back to managing its lifetime yourself, defeating the point of using UObject at all.

How does GC decide what is "still reachable"? It starts from a set of root objects (things like the currently loaded level, the game instance) and walks outward through every UPROPERTY field that is a pointer to a UObject — including pointers inside UPROPERTY TArrays and TMaps. Anything it reaches during that walk survives. Anything it does not reach gets destroyed.

Root Set (level, game instance, ...) | | walks UPROPERTY pointers v AEnemySpawner | | UPROPERTY TObjectPtr<AEnemyBase> TrackedTarget v AEnemyBase instance <-- reachable, GC keeps it alive AEnemyBase* RawPointer; // NOT a UPROPERTY -> invisible to the walk above -> if nothing else reflects it, GC destroys the object -> RawPointer is now DANGLING, using it crashes

This is exactly the raw-pointer danger you already learned in the C chapters (a pointer to memory that got freed), just triggered by Unreal's GC instead of by a manual delete. The fix is always the same: any class member that points at a UObject must be a UPROPERTY, even if you never plan to edit it in the editor or read it from Blueprint.

UCLASS()
class MYGAME_API AEnemySpawner : public AActor
{
    GENERATED_BODY()

private:
    // BAD: raw pointer, invisible to the garbage collector
    AEnemyBase* CurrentTarget;

public:
    // GOOD: UPROPERTY pointer, tracked by the garbage collector
    UPROPERTY()
    TObjectPtr<AEnemyBase> TrackedTarget;
};
Common mistake A bare UPROPERTY() with no specifiers, like the one above, is completely valid — it does not need EditAnywhere or BlueprintReadWrite to get GC tracking. Beginners sometimes skip UPROPERTY() entirely on internal pointers because "the editor doesn't need to see this one," which removes GC tracking along with everything else.

9. TObjectPtr and Safe Handling of UObject Pointers

Modern Unreal (UE5) prefers TObjectPtr<T> over a raw T* for any UPROPERTY that points at a UObject. You already used it in the previous section without a full explanation — here is one.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
TObjectPtr<AActor> TargetActor;

In everyday code, TObjectPtr<AActor> behaves like a raw AActor*: you dereference it with ->, compare it to nullptr, and pass it around the same way. What it adds on top:

Be clear about what TObjectPtr is NOT: it is not a reference-counted smart pointer like std::shared_ptr. It does not decide when the object dies. The Garbage Collector still owns that decision entirely, the same as with a raw UPROPERTY pointer — TObjectPtr only makes the reference easier for Unreal's tools to see and check.

Two related types worth knowing by name, even without using them in this lesson's example:

Tip Rule of thumb: UObject-derived pointer as a class member? Use UPROPERTY() TObjectPtr<T>. Plain non-UObject C++ object that needs shared ownership? Use TSharedPtr<T>, no UPROPERTY involved, no GC involved.

10. Exposing a Whole C++ Class to Blueprint

So far you have exposed individual fields and functions. You can also control whether the whole class can be used as a Blueprint parent or as a Blueprint variable type, using specifiers on UCLASS() itself.

UCLASS(Blueprintable, BlueprintType)
class MYGAME_API AInteractableDoor : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Door")
    bool bStartsLocked = false;

    UFUNCTION(BlueprintCallable, Category = "Door")
    void Unlock();
};

Most AActor subclasses are Blueprintable by default because AActor itself already is, but writing it explicitly is good practice and is required for some UObject types that are not Blueprintable by default.

The usual workflow after this: a designer creates a Blueprint child (commonly named with a BP_ prefix, like BP_InteractableDoor), sets bStartsLocked to true for one particular door in the Details panel, and adds an Event Graph that calls Unlock() when the player presses a button — all without touching or recompiling C++.

C++ (compiled) Blueprint (visual, no recompile) -------------------- -------------------------------- AInteractableDoor ---> BP_InteractableDoor bStartsLocked (editable) bStartsLocked = true (this door) Unlock() (callable) Event Graph: On Button Pressed -> call Unlock() | v BP_InteractableDoor instance placed in the level
Tip C++ changes require recompiling the whole module and often restarting the editor. Blueprint changes apply instantly with a "Compile" click inside the Blueprint editor itself. This is exactly why gameplay tuning (numbers, small behavior tweaks) is usually pushed into Blueprint, while performance-critical or foundational logic stays in C++.

11. The Build System: Modules and .Build.cs

An Unreal project is not one giant pile of source files compiled all at once. It is split into modules — separately compiled units, each with its own name, its own folder under Source/, and its own list of dependencies. Your main game code is one module; each plugin is one or more additional modules; the engine itself is built from dozens of modules (Core, CoreUObject, Engine, and so on).

MyGame/ Source/ MyGame/ MyGame.Build.cs MyGame.h MyGame.cpp Enemy/ EnemyBase.h EnemyBase.cpp

Every module has exactly one .Build.cs file. This is NOT C++ — it is a small C# script, run by a separate program called UnrealBuildTool (UBT) before your C++ is compiled. Its job is to declare what your module depends on, so UBT knows which headers and libraries to make available.

using UnrealBuildTool;

public class MyGame : ModuleRules
{
    public MyGame(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[] {
            "Core", "CoreUObject", "Engine", "InputCore"
        });

        PrivateDependencyModuleNames.AddRange(new string[] { });
    }
}

This is also where the MYGAME_API macro from section 1 comes from: UBT generates one export macro per module (named after the module, here MYGAME_API), and you put it on any class you want visible from OTHER modules. On Windows this matters for DLL exports; leaving it off a class that another module needs produces a linker error ("unresolved external symbol"), not a reflection error — a good clue for telling the two failure kinds apart.

Common mistake Using a class or function from a module you forgot to list in .Build.cs. The compiler error looks nothing like a missing macro — it is usually a linker error about an unresolved symbol, because the header was found but the compiled code for that module was never linked in.

12. Full Example: A Tickable Actor Exposed to Blueprint

This section puts everything together: a small Actor that orbits around the point where it started, ticking every frame, with two numeric properties and one function all exposed to Blueprint.

// OrbitingCrystal.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "OrbitingCrystal.generated.h"

UCLASS(Blueprintable, BlueprintType)
class MYGAME_API AOrbitingCrystal : public AActor
{
    GENERATED_BODY()

public:
    AOrbitingCrystal();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbit")
    float OrbitRadius = 200.0f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Orbit")
    float OrbitSpeed = 90.0f; // degrees per second

    UFUNCTION(BlueprintCallable, Category = "Orbit")
    void ReverseDirection();

private:
    FVector OrbitCenter;
    float CurrentAngleDegrees = 0.0f;
};
// OrbitingCrystal.cpp
#include "OrbitingCrystal.h"

AOrbitingCrystal::AOrbitingCrystal()
{
    PrimaryActorTick.bCanEverTick = true;
}

void AOrbitingCrystal::BeginPlay()
{
    Super::BeginPlay();
    OrbitCenter = GetActorLocation();
}

void AOrbitingCrystal::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    CurrentAngleDegrees += OrbitSpeed * DeltaTime;

    float AngleRadians = FMath::DegreesToRadians(CurrentAngleDegrees);
    FVector Offset(FMath::Cos(AngleRadians) * OrbitRadius,
                    FMath::Sin(AngleRadians) * OrbitRadius,
                    0.0f);

    SetActorLocation(OrbitCenter + Offset);
}

void AOrbitingCrystal::ReverseDirection()
{
    OrbitSpeed = -OrbitSpeed;
}

Walking through it, connecting to earlier sections:

Worked trace (no console output here — this is a visual behavior, so here is what you would observe in the editor):

Place BP_OrbitingCrystal (a Blueprint child of AOrbitingCrystal) in the level at position (0, 0, 100). Press Play. t = 0.0s position = (0+200, 0, 100) [angle 0 deg] t = 1.0s position = (0, 0+200, 100) [angle 90 deg] t = 2.0s position = (0-200, 0, 100) [angle 180 deg] t = 3.0s position = (0, 0-200, 100) [angle 270 deg] t = 4.0s position = (0+200, 0, 100) [angle 360 deg, one full loop] Calling ReverseDirection() at any point flips the sign of OrbitSpeed, so the crystal immediately starts circling the other way, still at the same radius.
Tip Because OrbitRadius and OrbitSpeed are exposed, you can make five different Blueprint children of this one C++ class — a slow wide orbit, a fast tight orbit, and so on — without writing any extra C++ or Blueprint logic at all, just different Details-panel values.

13. The C++ <-> Blueprint Boundary and Common Pitfalls

Zooming back out, every macro in this lesson exists to build one thing: a controlled boundary between the C++ you write and the Blueprint graphs a designer builds on top of it. C++ decides what crosses that boundary (with UPROPERTY/UFUNCTION specifiers); Blueprint works only with what C++ chose to expose.

C++ side (compiled, engineer-owned) ------------------------------------- AOrbitingCrystal (UCLASS, Blueprintable) OrbitRadius -- UPROPERTY(EditAnywhere, BlueprintReadWrite) OrbitSpeed -- UPROPERTY(EditAnywhere, BlueprintReadWrite) ReverseDirection() -- UFUNCTION(BlueprintCallable) ============ reflection boundary, built by UHT ============= Blueprint side (visual graph, designer-owned) ------------------------------------- BP_OrbitingCrystal (a Blueprint child of AOrbitingCrystal) - Details panel shows OrbitRadius / OrbitSpeed, editable - Event Graph has a "Reverse Direction" node that calls the C++ function directly - can add extra visual-only logic (particles, sound cues) with no C++ changes and no recompile

Everything on the C++ side of that boundary needs to be earned with a macro. Nothing crosses automatically. That is deliberate — it keeps the boundary small and explicit, so an engineer can look at a header file and know exactly what a designer is allowed to touch.

Recap of the mistakes that bite beginners most

Every one of these mistakes has the same underlying shape: some piece of Unreal's framework (GC, Blueprint, the editor, the linker) needed to know about something, and a missing macro or missing dependency kept it in the dark. When you get a strange Unreal error, the first question worth asking is "which of these systems doesn't know about my code, and why?"

Glossary

Exercises

Exercise 1 Write a USTRUCT called FQuestReward that Blueprint can use as a variable type. Give it two fields: ItemName (an FString) and Amount (an int32), both editable in the editor and readable/writable from Blueprint.
Show answer
USTRUCT(BlueprintType)
struct FQuestReward
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reward")
    FString ItemName;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reward")
    int32 Amount = 0;
};

BlueprintType on the USTRUCT() is what allows a Blueprint graph to declare a variable of type FQuestReward. Each field needs its own UPROPERTY(EditAnywhere, BlueprintReadWrite) — specifiers on the struct do not automatically apply to its fields.

Exercise 2 The code below compiles, but the game crashes randomly, always sometime after a level has been running for a while — never immediately. Find the bug and fix it.
UCLASS()
class MYGAME_API AQuestGiver : public AActor
{
    GENERATED_BODY()

private:
    UDialogueAsset* CurrentDialogue;

public:
    void StartQuest(UDialogueAsset* Dialogue)
    {
        CurrentDialogue = Dialogue;
    }

    void PlayLine()
    {
        // crashes here, sometime later
        CurrentDialogue->PlayNextLine();
    }
};
Show answer

CurrentDialogue is a raw pointer to a UObject (UDialogueAsset derives from UObject) with no UPROPERTY(). The Garbage Collector cannot see it, so if nothing ELSE holds a UPROPERTY reference to that same dialogue asset, GC eventually decides it is unreachable and destroys it — even though AQuestGiver is still "using" it through this invisible raw pointer. The next call to PlayLine() dereferences a dangling pointer: sometimes it crashes immediately, sometimes it reads garbage memory for a while first, which is why the crash feels random and delayed, exactly like a use-after-free bug from the C chapters.

UCLASS()
class MYGAME_API AQuestGiver : public AActor
{
    GENERATED_BODY()

private:
    UPROPERTY()
    TObjectPtr<UDialogueAsset> CurrentDialogue;

public:
    void StartQuest(UDialogueAsset* Dialogue)
    {
        CurrentDialogue = Dialogue;
    }

    void PlayLine()
    {
        CurrentDialogue->PlayNextLine();
    }
};

Adding UPROPERTY() makes CurrentDialogue part of the reflection data GC walks, so as long as AQuestGiver is alive and holds this reference, the dialogue asset counts as reachable and will not be destroyed out from under it.

Exercise 3 Write a UFUNCTION marked BlueprintCallable that takes a TArray<int32> (by const reference) and returns the sum of its elements as an int32. Then, in one or two sentences, explain why this function uses TArray<int32> instead of std::vector<int32>, given that it needs to be callable from Blueprint.
Show answer
UFUNCTION(BlueprintCallable, Category = "Math")
int32 SumValues(const TArray<int32>& Values) const;

int32 AMyScoreKeeper::SumValues(const TArray<int32>& Values) const
{
    int32 Total = 0;
    for (int32 Value : Values)
    {
        Total += Value;
    }
    return Total;
}

std::vector is invisible to UnrealHeaderTool, so a UFUNCTION parameter of type std::vector<int32> has no reflection data at all — Blueprint would have no array pin to connect to it, and the function likely would not even compile as a UFUNCTION. TArray<int32> is understood by UHT, so Blueprint sees a normal array input pin and can build or pass an array of integers straight into this function.

← Back to all chapters