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).
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.
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.
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.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.
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.
#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.These three macros tell UHT "this type should exist in Unreal's reflection system." Each one goes on a different kind of type:
UCLASS() — a class that inherits (directly or indirectly) from UObject. Most gameplay classes you write are UCLASS types: actors, components, game modes, player controllers.USTRUCT() — a plain data-holding struct, NOT derived from UObject. Used for small bundles of related values, like a struct you already know from C++, but reflected.UENUM() — an enum whose values you want visible to the editor and to Blueprint, usually written as an enum class with a uint8 base for compactness.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:
A prefix — a class derived from AActor (a placeable, spawnable "thing" in the world).U prefix — a class derived from UObject but NOT from AActor (for example UActorComponent, or a plain gameplay object).F prefix — a plain struct, not derived from UObject at all (FVector, FString, and your own structs like FLootEntry).E prefix — an enum.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.
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.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:
EditAnywhere — editable in the editor, both on the class defaults and on each individual instance placed in a level.EditDefaultsOnly — editable only on the class defaults (in the Blueprint editor for a Blueprint child), NOT per-instance in a level.VisibleAnywhere — shown in the editor but greyed out, not editable there (still settable from C++).BlueprintReadWrite — Blueprint graphs can both read and write this value at runtime.BlueprintReadOnly — Blueprint graphs can read the value but cannot set it.Category = "Name" — groups the field under a heading in the editor's Details panel, purely organizational.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.
[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.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();
BlueprintCallable — appears in Blueprint as a node with an execution pin (a "do this, then continue" step). Use it for functions that DO something (fire a weapon, apply damage).BlueprintPure — appears in Blueprint as a node with no execution pin, just data outputs. Use it for functions that only COMPUTE and RETURN a value with no side effects (like GetHealthPercent above).BlueprintImplementableEvent — you declare the function in C++ but write NO body for it at all. Blueprint provides the implementation entirely. C++ calls OnWeaponFired() normally; whatever the Blueprint designer wired up in the Event Graph runs.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.
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.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:
FString — a mutable, dynamically-sized string, roughly like std::string. String literals are wrapped in TEXT("...") so they use the right character type on every platform.FVector — three floats (X, Y, Z), Unreal's 3D vector/point type, with operators for add, subtract, dot product, length, and so on already written for you.TArray<T> — a dynamic array, roughly like std::vector<T>: contiguous memory, grows as you Add(), indexable with [], size via .Num() instead of .size().TMap<K, V> — a hash map, roughly like std::unordered_map<K, V>: key-value pairs, .Add(Key, Value), lookup with .Find(Key).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.
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.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:
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.
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.
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.
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.
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.
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;
};
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.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:
AActor* to TObjectPtr<AActor> almost never requires changing the code that uses that field.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:
TWeakObjectPtr<T> — a non-owning reference that does NOT keep the object alive and automatically becomes null if GC destroys the object. Useful when you want to refer to something without affecting whether it survives.TSharedPtr<T> — a true reference-counted smart pointer, similar to std::shared_ptr, but only for plain C++ objects that are NOT UObject-derived. Never use it on a UObject — the GC and the ref-counting system would fight over who owns the object.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.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();
};
Blueprintable — this C++ class can be the parent of a new Blueprint class. In the editor: Content Browser -> Add -> Blueprint Class -> search for AInteractableDoor as the parent.BlueprintType — this class can be used as a variable type inside a Blueprint graph (for example, a variable slot that holds "a reference to an Interactable Door").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++.
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).
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[] { });
}
}
Core — the most basic Unreal types: FString, TArray, math types, memory system.CoreUObject — the UObject system itself: reflection, garbage collection, everything this lesson covers.Engine — gameplay classes: AActor, APawn, UWorld, components, and so on.InputCore — input key and axis definitions.PublicDependencyModuleNames — dependencies that are also visible to any OTHER module that depends on yours.PrivateDependencyModuleNames — dependencies used only inside your module, not passed along further.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.
.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.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:
PrimaryActorTick.bCanEverTick = true in the constructor turns Tick ON. It defaults to off for performance — every actor with Tick enabled costs a little time every single frame, so Unreal makes you opt in. (If you know Unity's MonoBehaviour.Update(): this is the same idea as Tick(float DeltaTime), except Unity calls Update automatically and Unreal requires this explicit switch.)BeginPlay() is Unreal's "start of play" hook, called once — like Unity's Start(). It stores the actor's starting position as the orbit center.Tick(float DeltaTime) runs every frame; DeltaTime is the time in seconds since the last frame, the same frame-independent-movement idea from earlier math/engine lessons.OrbitRadius and OrbitSpeed are EditAnywhere, BlueprintReadWrite, so a designer can tune them per level instance in the Details panel, or read/change them at runtime from a Blueprint graph, with no C++ recompile.ReverseDirection() is BlueprintCallable, so it can be wired to, for example, an "On Button Pressed" input event entirely inside Blueprint.Worked trace (no console output here — this is a visual behavior, so here is what you would observe in the editor):
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.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.
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.
GENERATED_BODY() as the first line of a UCLASS/USTRUCT body — produces a wall of confusing compiler errors pointing at the .generated.h file.#include "ClassName.generated.h" as the LAST include in the header.new/delete on a UObject/AActor instead of NewObject/SpawnActor and Destroy() — bypasses the engine's object system entirely.UObject with no UPROPERTY() — invisible to the Garbage Collector, can be destroyed while you still hold a "valid-looking" dangling pointer to it.PrimaryActorTick.bCanEverTick = true, then wondering why Tick() never runs.PublicDependencyModuleNames / PrivateDependencyModuleNames in .Build.cs — produces a linker error, not a reflection error.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?"
UObject) for Unreal's reflection system.UObject) for reflection.UCLASS/USTRUCT body; UHT fills in the plumbing behind it.ClassName.generated.h file before the real compiler runs.UFUNCTION specifiers controlling how a function appears and behaves in Blueprint.UObjects; destroys objects no longer reachable through UPROPERTY pointers from a root set.UPROPERTY that references a UObject; behaves like a raw pointer but adds tracking and debug checks.UObject that automatically becomes null when the object is destroyed by GC..Build.cs file.UCLASS specifiers controlling whether a class can be a Blueprint parent / used as a Blueprint variable type.AActor) for anything that can be placed and positioned in a level.Update(), off by default per actor.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.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.
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();
}
};
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.
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.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.