Skip to main content

📝 Lesson 21: C++ Prerequisites for Unreal Engine

Bridge the gap between core C++ and Unreal Engine's C++ dialect — the classes, pointers, memory model, containers, delegates, and macros you'll lean on from day one.

🎯 Learning Objectives

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

  • Identify which core C++ topics (classes, inheritance, pointers, templates) are critical, important, or merely helpful for Unreal Engine work.
  • Explain why UObject pointers must be marked UPROPERTY() to survive Unreal's garbage collector, and when to reach for TWeakObjectPtr instead.
  • Translate familiar STL containers and strings (std::vector, std::string, smart pointers) to their Unreal equivalents (TArray, FString, TSharedPtr/TUniquePtr).
  • Use Unreal delegates and lambdas for event handling, and recognize the essential UCLASS/UPROPERTY/UFUNCTION macros.
  • Handle errors the Unreal way — validity checks and assertion macros — instead of exceptions.

Estimated Time: 100–130 minutes

Project: Refactor the standard C++ class in this lesson's practice exercise into idiomatic Unreal Engine C++ using UCLASS, UPROPERTY, TArray, and IsValid() checks.

In This Lesson

The C++ Knowledge Map for Unreal

Think of Unreal Engine as a massive spaceship. You don't need to understand every circuit to fly it, but you do need to know the essential controls. This guide shows you exactly which C++ concepts are critical for Unreal Engine development.

Classes and Objects: The Foundation

Unreal Engine is built entirely on object-oriented programming. Every actor, component, and system is a class. You'll be creating and extending classes constantly.

graph TD A[C++ Class Concepts] --> B[Basic Classes] A --> C[Inheritance] A --> D[Polymorphism] B --> E[Data Members] B --> F[Member Functions] B --> G[Access Specifiers] C --> H[Single Inheritance] C --> I[Virtual Functions] C --> J[Abstract Classes] D --> K[Function Overriding] D --> L[Dynamic Dispatch] style A fill:#e74c3c,color:#111827 style C fill:#f39c12,color:#111827 style D fill:#f39c12,color:#111827

What You Must Know

// 1. Basic Class Structure - CRITICAL for Unreal
class GameCharacter {
private:
    float health;          // Unreal uses float extensively
    FString name;          // Unreal's string type

protected:
    int32 level;           // Unreal's int32 typedef

public:
    // Constructor - Called when object is created
    GameCharacter() : health(100.0f), level(1) {
        // Initialization
    }

    // Destructor - Called when object is destroyed
    virtual ~GameCharacter() {
        // Cleanup - Unreal handles most cleanup automatically
    }

    // Getter/Setter pattern used everywhere in Unreal
    float GetHealth() const { return health; }
    void SetHealth(float newHealth) { health = newHealth; }

    // Virtual functions for polymorphism
    virtual void TakeDamage(float damage) {
        health -= damage;
    }
};

// 2. Inheritance - CRITICAL for extending Unreal classes
class PlayerCharacter : public GameCharacter {
private:
    int32 experience;

public:
    PlayerCharacter() : GameCharacter(), experience(0) {
        // Call parent constructor
    }

    // Override parent function
    virtual void TakeDamage(float damage) override {
        // Custom player damage logic
        float reducedDamage = damage * 0.8f;  // Players take less damage
        GameCharacter::TakeDamage(reducedDamage);
    }

    // New functions specific to player
    void GainExperience(int32 amount) {
        experience += amount;
        CheckLevelUp();
    }

private:
    void CheckLevelUp();
};

// 3. Abstract Classes/Interfaces - Used for Unreal interfaces
class IDamageable {
public:
    virtual ~IDamageable() = default;
    virtual void TakeDamage(float damage) = 0;  // Pure virtual
    virtual bool IsDead() const = 0;
};

Pointers and References: Memory Navigation

Unreal Engine uses pointers everywhere. You'll be constantly working with pointers to Actors, Components, and other game objects. Understanding pointers is non-negotiable.

Essential Pointer Knowledge

// 1. Basic Pointer Operations - MUST KNOW
void PointerBasics() {
    // Creating pointers
    AActor* ActorPtr = nullptr;  // Always initialize to nullptr

    // Getting pointers from Unreal
    ActorPtr = GetWorld()->SpawnActor<AActor>(ActorClass);

    // Checking validity - CRITICAL IN UNREAL
    if (ActorPtr != nullptr) {
        // Safe to use
        ActorPtr->SetActorLocation(FVector(0, 0, 100));
    }

    // Better validity check
    if (IsValid(ActorPtr)) {
        // Checks both nullptr and pending kill
        ActorPtr->Destroy();
    }
}

// 2. References - Used for function parameters
void ProcessCharacter(const ACharacter& Character) {
    // Can't be null, always valid
    float Health = Character.GetHealth();
}

// 3. Pointer vs Reference Usage in Unreal
class UMyComponent : public UActorComponent {
private:
    // WRONG - Never store references as members
    // AActor& OwnerRef;  // This will cause problems!

    // CORRECT - Use pointers with UPROPERTY
    UPROPERTY()
    AActor* Owner;

    // CORRECT - Weak pointer for optional references
    TWeakObjectPtr<AActor> Target;

public:
    // References are fine for parameters
    void Initialize(AActor& InOwner) {
        Owner = &InOwner;  // Convert to pointer for storage
    }

    // Const references for efficiency
    void ProcessData(const FVector& Location) {
        // No copy made, efficient
    }
};

// 4. Smart Pointers in Unreal (Different from std::)
void UnrealSmartPointers() {
    // TSharedPtr - Reference counted (not for UObjects!)
    TSharedPtr<FMyStruct> SharedData = MakeShared<FMyStruct>();

    // TUniquePtr - Single owner (not for UObjects!)
    TUniquePtr<FRenderData> RenderData = MakeUnique<FRenderData>();

    // TWeakObjectPtr - For UObjects
    TWeakObjectPtr<AActor> WeakActor = SomeActor;
    if (WeakActor.IsValid()) {
        WeakActor->DoSomething();
    }
}

Memory Management: Unreal's Garbage Collection

Unlike standard C++, Unreal Engine manages memory for you through garbage collection. Understanding how this works is crucial to avoid crashes and memory leaks.

graph TD A[Unreal Memory Management] --> B[UObject System] A --> C[Non-UObject Memory] B --> D[Automatic GC] B --> E[UPROPERTY Protection] B --> F[Reference Counting] C --> G[Manual Management] C --> H[Smart Pointers] C --> I[Custom Allocators] style B fill:#27ae60,color:#111827 style C fill:#e74c3c,color:#111827 style D fill:#27ae60,color:#111827 style G fill:#e74c3c,color:#111827

Memory Rules You Must Follow

// 1. UObject Memory Management - CRITICAL
UCLASS()
class MYPROJECT_API AMyActor : public AActor {
    GENERATED_BODY()

private:
    // CORRECT - UPROPERTY protects from GC
    UPROPERTY()
    UStaticMeshComponent* MeshComponent;

    // WRONG - Will be garbage collected!
    UStaticMeshComponent* UnprotectedComponent;

    // CORRECT - For non-UObjects
    TUniquePtr<FMyStruct> StructData;

public:
    AMyActor() {
        // CORRECT - Let Unreal manage UObject memory
        MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));

        // WRONG - Never use new/delete with UObjects!
        // UnprotectedComponent = new UStaticMeshComponent();  // NEVER DO THIS!

        // CORRECT - For non-UObjects
        StructData = MakeUnique<FMyStruct>();
    }

    void SpawnExample() {
        // CORRECT - Spawn actors through the world
        AActor* NewActor = GetWorld()->SpawnActor<AActor>(ActorClass);

        // WRONG - Never create actors with new
        // AActor* BadActor = new AActor();  // CRASH!
    }

    ~AMyActor() {
        // Don't delete UObject pointers!
        // MeshComponent will be handled by GC

        // StructData will be automatically deleted (unique_ptr)
    }
};

// 2. Preventing Garbage Collection
// Method 1 & 3 live on a class — UPROPERTY() is only valid on class members,
// never inside a function body.
UCLASS()
class MYPROJECT_API UMyManager : public UObject {
    GENERATED_BODY()

    // Method 1: UPROPERTY on a single UObject pointer
    UPROPERTY()
    AActor* ProtectedActor;

    // Method 3: Strong references in a UPROPERTY container
    UPROPERTY()
    TArray<AActor*> ActorArray;  // All actors in the array are protected
};

// Method 2: Add to the root set (rare, special cases) — this one is fine in a function
void PreventGC() {
    UObject* ImportantObject = NewObject<UObject>();
    ImportantObject->AddToRoot();
    // Remember to remove: ImportantObject->RemoveFromRoot();
}

// 3. Working with Non-UObject Memory
struct FGameData {
    TArray<float> Values;
    FString Name;

    // Unreal containers manage their own memory
    void AddValue(float Val) {
        Values.Add(Val);  // Automatic resize
    }
};

// 4. Memory Best Practices
class UMyGameSystem : public UObject {
private:
    // Use Unreal's containers
    TArray<int32> Scores;           // Dynamic array
    TMap<FString, float> PlayerStats;  // Hash map
    TSet<AActor*> TrackedActors;    // Hash set

public:
    void MemoryEfficientOperations() {
        // Pre-allocate for performance
        Scores.Reserve(100);

        // Move semantics work in Unreal
        TArray<int32> TempArray;
        Scores = MoveTemp(TempArray);

        // Shrink when done
        Scores.Shrink();
    }
};

Templates: Understanding Unreal's Generic Code

While you don't need to be a template wizard, you must understand basic templates because Unreal uses them extensively in containers and smart pointers.

Template Essentials for Unreal

// 1. Using Unreal's Template Containers - MUST KNOW
void ContainerBasics() {
    // TArray - Dynamic array (like std::vector)
    TArray<AActor*> Enemies;
    Enemies.Add(Enemy1);
    Enemies.Remove(Enemy2);

    for (AActor* Enemy : Enemies) {
        // Process each enemy
    }

    // TMap - Hash map (like std::unordered_map)
    TMap<FString, int32> PlayerScores;
    PlayerScores.Add("Player1", 100);
    int32* Score = PlayerScores.Find("Player1");

    // TSet - Hash set
    TSet<FName> UniqueNames;
    UniqueNames.Add("ItemName");
}

// 2. Template Functions You'll Use - MUST KNOW
void TemplateFunctions() {
    AActor* Actor = GetSomeActor();

    // Cast<T> - Safe casting with nullptr on failure
    ACharacter* Character = Cast<ACharacter>(Actor);
    if (Character) {
        // Successfully cast to ACharacter
    }

    // FindComponentByClass<T> - Get component by type
    UHealthComponent* Health = Actor->FindComponentByClass<UHealthComponent>();

    // SpawnActor<T> - Spawn actors of specific type
    AProjectile* Projectile = GetWorld()->SpawnActor<AProjectile>(
        ProjectileClass, Location, Rotation
    );
}

// 3. TSubclassOf - Class type safety - IMPORTANT
class AWeaponPickup : public AActor {
    // This ensures WeaponClass is a subclass of AWeapon
    UPROPERTY(EditDefaultsOnly)
    TSubclassOf<AWeapon> WeaponClass;

    void SpawnWeapon() {
        if (WeaponClass) {
            GetWorld()->SpawnActor<AWeapon>(WeaponClass);
        }
    }
};

// 4. Creating Simple Templates - NICE TO KNOW
template<typename T>
T* FindActorOfType(UWorld* World) {
    for (TActorIterator<T> It(World); It; ++It) {
        return *It;  // Return first found
    }
    return nullptr;
}

// Usage
APlayerController* PC = FindActorOfType<APlayerController>(GetWorld());

Function Pointers and Delegates

Unreal uses delegates extensively for event handling. They're like function pointers on steroids, allowing flexible event systems.

graph TD A[Unreal Event System] --> B[Delegates] A --> C[Events] A --> D[Callbacks] B --> E[Single Delegate] B --> F[Multicast Delegate] B --> G[Dynamic Delegate] G --> H[Blueprint Compatible] F --> I[Multiple Listeners] style A fill:#e74c3c,color:#111827 style B fill:#f39c12,color:#111827 style G fill:#27ae60,color:#111827

Delegates and Lambdas

// 1. Basic Delegates - IMPORTANT for Unreal
// Declare delegate types
DECLARE_DELEGATE_OneParam(FOnHealthChanged, float);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnDamageDealt, AActor*, float);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerDied, ACharacter*, Character);

class AGameCharacter : public ACharacter {
public:
    // Single delegate
    FOnHealthChanged OnHealthChanged;

    // Multicast - multiple listeners
    FOnDamageDealt OnDamageDealt;

    // Dynamic - Blueprint compatible
    UPROPERTY(BlueprintAssignable)
    FOnPlayerDied OnPlayerDied;

    void TakeDamage(float Damage) {
        Health -= Damage;

        // Execute delegates
        OnHealthChanged.ExecuteIfBound(Health);
        OnDamageDealt.Broadcast(this, Damage);

        if (Health <= 0) {
            OnPlayerDied.Broadcast(this);
        }
    }
};

// 2. Using Lambdas - VERY COMMON in Unreal
void LambdaExamples() {
    // Timer with lambda
    FTimerHandle TimerHandle;
    GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]() {
        // This code runs after 2 seconds
        UE_LOG(LogTemp, Warning, TEXT("Timer fired!"));
    }, 2.0f, false);

    // Binding to delegates with lambdas
    Character->OnHealthChanged.BindLambda([](float NewHealth) {
        UE_LOG(LogTemp, Warning, TEXT("Health: %f"), NewHealth);
    });

    // Array operations with lambdas
    TArray<AActor*> Actors;
    Actors.Sort([](const AActor& A, const AActor& B) {
        return A.GetName() < B.GetName();
    });

    // Finding with predicates
    AActor** Found = Actors.FindByPredicate([](AActor* Actor) {
        return Actor->GetName() == "TargetActor";
    });
}

// 3. Function Binding - COMMON pattern
class UMyWidget : public UUserWidget {
    UFUNCTION()
    void OnButtonClicked() {
        // Handle click
    }

    void SetupWidget() {
        if (MyButton) {
            // Bind member function to button click
            MyButton->OnClicked.AddDynamic(this, &UMyWidget::OnButtonClicked);
        }
    }
};

Macros and Preprocessor

Unreal Engine uses macros extensively. You don't need to create complex macros, but you must understand how to use Unreal's existing ones.

Macro Usage Guide

// 1. Essential Class Declaration Macros - MUST KNOW
UCLASS()  // Makes class visible to Unreal
class MYPROJECT_API AMyActor : public AActor {
    GENERATED_BODY()  // Required boilerplate

    // UPROPERTY macros for reflection
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
    float Health = 100.0f;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    UStaticMeshComponent* MeshComponent;

    // UFUNCTION macros for Blueprint/reflection
    UFUNCTION(BlueprintCallable, Category = "Actions")
    void PerformAction();

    UFUNCTION(BlueprintImplementableEvent, Category = "Events")
    void OnActionCompleted();  // Implemented in Blueprint
};

// 2. Logging Macros - ESSENTIAL for debugging
void LoggingExamples() {
    // Basic logging
    UE_LOG(LogTemp, Warning, TEXT("Simple message"));

    // With variables
    int32 Score = 100;
    FString PlayerName = "John";
    UE_LOG(LogTemp, Display, TEXT("Player %s scored %d"), *PlayerName, Score);

    // Different log levels
    UE_LOG(LogTemp, Display, TEXT("Info message"));     // Gray
    UE_LOG(LogTemp, Warning, TEXT("Warning message"));  // Yellow
    UE_LOG(LogTemp, Error, TEXT("Error message"));      // Red
}

// 3. Assertion Macros - IMPORTANT for debugging
void AssertionExamples() {
    // check - Halts execution in non-shipping builds
    check(Actor != nullptr);
    check(Health > 0);

    // ensure - Logs error but continues
    ensure(Component != nullptr);

    // verify - Like check but evaluates in all builds
    verify(ProcessData());
}

// 4. Platform Macros - USEFUL
void PlatformSpecific() {
    #if PLATFORM_WINDOWS
        // Windows-specific code
    #elif PLATFORM_MAC
        // Mac-specific code
    #elif PLATFORM_LINUX
        // Linux-specific code
    #endif

    #if WITH_EDITOR
        // Editor-only code
    #endif
}

STL vs Unreal Containers

Unreal has its own container types. While STL knowledge helps, you'll use Unreal's containers exclusively in engine code.

// Container Comparison Reference
// STL                    → Unreal Equivalent
// std::vector           → TArray
// std::unordered_map    → TMap
// std::unordered_set    → TSet
// std::string           → FString
// std::shared_ptr       → TSharedPtr (not for UObjects!)
// std::unique_ptr       → TUniquePtr (not for UObjects!)
// std::function         → TFunction / TDelegate

// Unreal Container Examples - MUST KNOW
void UnrealContainers() {
    // TArray - Dynamic array
    TArray<int32> Numbers;
    Numbers.Add(42);
    Numbers.AddUnique(42);  // Won't add duplicate
    Numbers.Remove(42);
    Numbers.Empty();  // Clear array

    // Useful TArray operations
    Numbers.Reserve(100);  // Pre-allocate
    Numbers.Sort();
    Numbers.Reverse();
    int32 Index = Numbers.Find(42);
    bool bContains = Numbers.Contains(42);

    // TMap - Key-value pairs
    TMap<FString, AActor*> ActorMap;
    ActorMap.Add("Player", PlayerActor);
    AActor** FoundActor = ActorMap.Find("Player");
    ActorMap.Remove("Player");

    // Iterating TMap
    for (const auto& Pair : ActorMap) {
        FString Key = Pair.Key;
        AActor* Value = Pair.Value;
    }

    // TSet - Unique elements
    TSet<FName> Tags;
    Tags.Add("Enemy");
    bool bHasTag = Tags.Contains("Enemy");
}

// String Types - CRITICAL
void UnrealStrings() {
    // FString - Mutable string
    FString MyString = TEXT("Hello");
    MyString += TEXT(" World");

    // FName - Immutable, case-insensitive identifier
    FName TagName("EnemyTag");

    // FText - Localized display text
    FText DisplayText = FText::FromString("Display This");

    // Conversions
    FString StringFromName = TagName.ToString();
    FName NameFromString(*MyString);
    const TCHAR* CString = *MyString;  // Get C-string
}

Error Handling: Unreal Style

Unreal doesn't use exceptions. Instead, it uses return codes, validity checks, and assertions.

// 1. No Exceptions! - CRITICAL
// WRONG - Unreal doesn't use exceptions
void WrongWay() {
    try {
        // Don't do this in Unreal!
        throw std::runtime_error("Error");
    } catch (...) {
        // Unreal is compiled with exceptions disabled
    }
}

// CORRECT - Unreal error handling
bool CorrectWay(AActor*& OutActor) {
    OutActor = FindActor();
    if (!IsValid(OutActor)) {
        UE_LOG(LogTemp, Error, TEXT("Failed to find actor"));
        return false;
    }
    return true;
}

// 2. Validity Checking - ESSENTIAL
void ValidityChecks() {
    // Basic null check
    if (Actor != nullptr) {
        // Safe to use
    }

    // Better - checks null and pending kill
    if (IsValid(Actor)) {
        // Actor is truly valid
    }

    // For weak pointers
    if (WeakActor.IsValid()) {
        AActor* Actor = WeakActor.Get();
    }
}

// 3. Assertions for Development - IMPORTANT
void DevelopmentAssertions() {
    // check - Only in non-shipping builds
    check(Component != nullptr);
    checkf(Health > 0, TEXT("Health was %f"), Health);

    // ensure - Logs and continues
    if (!ensure(Data != nullptr)) {
        return;  // Handle gracefully
    }

    // Conditional checks
    checkNoEntry();  // Should never reach this code
    checkNoReentry();  // Prevent recursive calls
}

Quick Reference: C++ to Unreal

graph TD A[Start Unreal C++] --> B{Know Classes?} B -->|No| C[Learn OOP First] B -->|Yes| D{Know Pointers?} D -->|No| E[Learn Pointers] D -->|Yes| F{Know Templates Basics?} F -->|No| G[Learn Container Usage] F -->|Yes| H[Ready for Unreal!] C --> D E --> F G --> H style H fill:#27ae60,color:#111827 style C fill:#e74c3c,color:#111827 style E fill:#e74c3c,color:#111827 style G fill:#f39c12,color:#111827

Pre-Flight Checklist

Before starting with Unreal Engine, make sure you can:

  • ✓ Create classes with inheritance and virtual functions
  • ✓ Use pointers and references correctly
  • ✓ Understand when objects are created and destroyed
  • ✓ Use template containers (even if you can't write complex templates)
  • ✓ Work with header (.h) and source (.cpp) files
  • ✓ Understand const correctness
  • ✓ Use lambda functions for simple callbacks
  • ✓ Read and understand basic preprocessor directives

Remember: You don't need to be a C++ expert to start with Unreal. Focus on the fundamentals, and learn advanced features as you need them. Unreal's systems will guide you toward good practices!

Practice Exercise: Unreal-Ready Code

🏋️ Challenge: Convert Standard C++ to Unreal Style

Take this standard C++ code and convert it to Unreal Engine style:

// Standard C++ Version
#include <vector>
#include <string>
#include <memory>

class GameObject {
private:
    std::string name;
    float health;
    std::vector<GameObject*> children;

public:
    GameObject(const std::string& n) : name(n), health(100.0f) {}

    void TakeDamage(float damage) {
        health -= damage;
        if (health <= 0) {
            delete this;  // Dangerous!
        }
    }

    void AddChild(GameObject* child) {
        children.push_back(child);
    }
};

// Your Task: Convert to Unreal style
// Hints:
// - Inherit from AActor
// - Use UCLASS() and GENERATED_BODY()
// - Replace std::string with FString
// - Replace std::vector with TArray
// - Add UPROPERTY macros
// - Remove manual delete
// - Add proper null checks

🎯 Quick Quiz

Question 1: Why must a raw UObject pointer stored as a class member be marked with UPROPERTY() in Unreal Engine?

Question 2: Which pairing correctly matches a standard C++ STL type with its Unreal Engine equivalent?

Question 3: Unreal Engine is typically compiled with C++ exceptions disabled. How does idiomatic Unreal code signal and handle failure instead?

Summary

🎉 Key Takeaways

  • Classes, inheritance, and virtual functions are non-negotiable — every Actor and Component you touch is built on them.
  • Pointers are everywhere in Unreal; always check validity with IsValid() before use.
  • UPROPERTY() protects UObject pointers from garbage collection — forgetting it is a common source of crashes.
  • TWeakObjectPtr is for optional references that shouldn't keep an object alive.
  • Unreal's containers (TArray, TMap, TSet) and strings (FString, FName, FText) replace their STL counterparts in engine code.
  • Delegates and lambdas power Unreal's event system — DECLARE_DELEGATE, Broadcast(), and BindLambda() are core patterns.
  • Macros like UCLASS(), UPROPERTY(), UFUNCTION(), and GENERATED_BODY() drive Unreal's reflection system.
  • Exceptions are disabled — use return values, IsValid(), and check()/ensure() for error handling instead.

📚 Additional Resources

🚀 What's Next?

You now know exactly which C++ fundamentals matter for game development and why. In Lesson 22: C++ and Unreal Engine Development, you'll put this knowledge to work — setting up an Unreal project, writing your first AActor subclass, and seeing UCLASS, UPROPERTY, and UFUNCTION in action inside a real engine.

🎉 You're Unreal-ready!

You've mapped the exact C++ skills Unreal Engine expects of you. That map is the hardest part — the engine work from here builds directly on it.