Skip to main content

🪞 Lesson 2.2: The Reflection System — UCLASS, USTRUCT, UENUM

Reflection is the single most important idea in Unreal C++. It's how a compiled language gains the ability to inspect itself at runtime — and it's the reason the editor, Blueprint, saving, and networking can all touch your code. Get this, and the whole engine clicks into place.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Define reflection and explain why C++ needs help to get it
  • Describe what the Unreal Header Tool (UHT) generates and when
  • Use UCLASS, USTRUCT, UENUM, and GENERATED_BODY correctly
  • Explain what powers Cast<T> and the runtime type system
  • List the four engine systems reflection unlocks

Estimated Time: 60 minutes

Engine Version: Unreal Engine 5.8

In This Lesson

What Reflection Means

Reflection is a program's ability to examine its own structure at runtime: to ask "what class is this object? what properties does it have? what functions can I call by name?" Languages like C# and Java have this built in. Standard C++ does not — once compiled, a C++ type's names and structure are erased; there's just memory and machine code.

Unreal needs reflection badly. The editor must draw a property panel from a class it didn't know about at compile time. A save file must serialize objects by walking their properties. Blueprint must call C++ functions by name. Networking must know which properties to replicate. None of that is possible without runtime type information.

📖 The core idea

Unreal adds reflection to C++ with a build step. You annotate your code with macros (UCLASS, UPROPERTY…), and a tool reads those annotations before compilation and generates extra C++ that records your type's structure. That generated data is the reflection system.

The Unreal Header Tool

The Unreal Header Tool (UHT) runs as part of every build, before the C++ compiler. It scans your headers for reflection macros and generates a companion file — the .generated.h you always include last — plus a .gen.cpp that registers your types.

graph LR A["You write MyActor.h
with UCLASS/UPROPERTY"] --> B["UHT scans headers"] B --> C["Generates MyActor.generated.h
+ registration code"] C --> D["C++ compiler compiles
your code + generated code"] D --> E["Runtime has full type info
(UClass objects)"]
Figure 1: UHT is a code generator that runs first. This two-phase build (UHT, then compiler) is why Unreal C++ differs from ordinary C++.

⚠️ This explains a whole class of errors

If you write GENERATED_BODY() but forget to #include "MyActor.generated.h", or put that include in the wrong place, UHT can't wire things up and you get confusing errors about the macro. The generated header is UHT's output for that file — it must be included, and included last.

At runtime, the result is that every reflected class has a corresponding UClass object describing it — its name, parent, properties, and functions. You can retrieve it with UMyActor::StaticClass(), and the engine uses it constantly under the hood.

UCLASS, USTRUCT, UENUM

Three macros mark the three kinds of reflected type. Each pairs with a body macro.

UCLASS — reflected classes (usually UObject-derived)

UCLASS()
class MYPROJECT_API AEnemy : public AActor
{
    GENERATED_BODY()   // required inside every UCLASS

public:
    UPROPERTY(EditAnywhere, Category = "Enemy")
    float MaxHealth = 100.0f;
};

USTRUCT — reflected plain-data structs

USTRUCT(BlueprintType)   // BlueprintType: usable as a Blueprint variable/pin
struct FDamageInfo
{
    GENERATED_BODY()     // structs use the SAME body macro

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Amount = 0.0f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName DamageType;
};

📖 UCLASS vs USTRUCT — the practical difference

A UCLASS (a UObject) is a first-class engine citizen: garbage-collected, networkable, referenced by pointer, can have functions exposed to Blueprint. A USTRUCT is a lightweight value type — copied around, not garbage-collected, no UFUNCTIONs. Use structs for bundles of data (a hit result, a stat block); use classes for things with identity and behavior.

UENUM — reflected enumerations

UENUM(BlueprintType)
enum class EWeaponType : uint8   // uint8 base is required for BlueprintType enums
{
    Pistol   UMETA(DisplayName = "Pistol"),
    Rifle    UMETA(DisplayName = "Rifle"),
    Shotgun  UMETA(DisplayName = "Shotgun")
};

✅ Pro Tip

Use enum class (scoped enums) with a uint8 base for anything exposed to Blueprint. UMETA(DisplayName = ...) controls the friendly name shown in editor dropdowns. We build data-driven systems on these in Module 7.

Cast and the Type System

Because reflection records each object's real class, Unreal can offer a safe runtime cast — Cast<T>. It converts a base pointer to a derived type only if the object truly is that type; otherwise it returns nullptr. No crash, no undefined behavior.

AActor* HitActor = /* something we got from a trace or overlap */;

// Is this actor actually an AEnemy? Cast tells us safely.
if (AEnemy* Enemy = Cast<AEnemy>(HitActor))
{
    // Inside this block, Enemy is a valid AEnemy* — use its API freely.
    Enemy->TakeDamage(25.0f);
}
// If HitActor wasn't an AEnemy, Cast returned nullptr and we skip the block.

⚠️ Cast, don't C-style cast

Never use a C-style cast ((AEnemy*)HitActor) on UObjects — it blindly reinterprets memory and will crash or corrupt if the type is wrong. Cast<T> consults the reflection system and is the only correct tool. This is a rule you'll apply in nearly every gameplay lesson.

The same type info powers IsA(), StaticClass(), class-based spawning, and the "class picker" dropdowns you see in the editor — all of it reading the UClass data UHT generated.

What Reflection Unlocks

Everything distinctive about Unreal C++ traces back to reflection. Four big systems depend on it:

graph TD R["Reflection data
(UClass, UProperty, UFunction)"] --> Editor["Editor: auto-generated
Details panels"] R --> BP["Blueprint: call C++ by name,
C++ nodes"] R --> Ser["Serialization: save/load,
copy/paste, undo"] R --> Net["Networking: replicate
marked properties"]
Figure 2: One system, four payoffs. This is why "just add a macro" gives you so much — you're feeding all four at once.
SystemWhat reflection providesCourse module
Editor UIDetails panel widgets from UPROPERTY2, 6
BlueprintC++ exposed as nodes via UFUNCTION6
SerializationSave/load, copy, undo walk properties7
NetworkingReplicate properties & call RPCs10
💬 The payoff mindset: when you write UPROPERTY(EditAnywhere, Replicated), you're not "configuring a variable" — you're registering it with the editor and the network system simultaneously. Reflection is the shared substrate.

Hands-on Exercise & Quiz

🏋️ Exercise: Model a small system

Objective: Choose the right reflected type for each piece and write the declarations.

Design the data for a pickup system: a kind of pickup (health, ammo, coin), a bundle describing one pickup's effect (a type and an amount), and the actor that sits in the world. Write the UENUM, USTRUCT, and UCLASS skeletons.

💡 Hint

Kind → UENUM(BlueprintType) enum class : uint8. Bundle of data → USTRUCT(BlueprintType) with two UPROPERTYs. World object → UCLASS deriving from AActor.

✅ Sample solution
UENUM(BlueprintType)
enum class EPickupKind : uint8
{
    Health, Ammo, Coin
};

USTRUCT(BlueprintType)
struct FPickupEffect
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    EPickupKind Kind = EPickupKind::Health;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 Amount = 25;
};

UCLASS()
class MYPROJECT_API APickup : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pickup")
    FPickupEffect Effect;
};

🎯 Quick Quiz

Question 1: Why does standard C++ need UHT to get reflection?

Question 2: You need a lightweight, copied value type bundling a few fields, usable in Blueprint. Use:

Question 3: Which converts AActor* to AEnemy* safely?

Summary

🎉 Key Takeaways

  • Reflection is runtime self-knowledge of types; standard C++ lacks it, so Unreal generates it.
  • The Unreal Header Tool (UHT) runs before the compiler, reads your macros, and emits the .generated.h plus registration code.
  • UCLASS (UObjects — identity, GC, Blueprint), USTRUCT (value-type data bundles), and UENUM (enums) mark reflected types; each needs GENERATED_BODY().
  • Cast<T> uses reflection for a safe runtime downcast (nullptr on mismatch) — never C-style cast a UObject.
  • Reflection is the shared substrate for the editor UI, Blueprint, serialization, and networking.

📚 Additional Resources

🚀 What's Next?

You've seen the macros; next we go deep on the two that shape your everyday code the most — the specifiers inside UPROPERTY and UFUNCTION that decide exactly how your members surface to the editor, Blueprint, and beyond.

🎉 Lesson complete!

The engine's nervous system is no longer a mystery.