Skip to main content

📝 Lesson 22: C++ and Unreal Engine Development

Bridge modern C++ with Unreal Engine's actor, component, and gameplay framework to build real, high-performance games.

🎯 Learning Objectives

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

  • Explain why Unreal exposes C++ through the UCLASS(), UPROPERTY(), and UFUNCTION() macros and the reflection system built by the Unreal Header Tool.
  • Set up a C++ Unreal Engine project on Windows, macOS, or Linux and generate project files for your IDE.
  • Create AActor, APawn, and ACharacter subclasses and place them correctly in the UObject class hierarchy.
  • Build reusable UActorComponents (like a HealthComponent) and attach them to actors instead of relying on deep inheritance.
  • Wire up the gameplay framework (GameMode, GameState, PlayerController, PlayerState) and bind player input with both legacy Axis/Action bindings and the UE5 Enhanced Input system.
  • Configure collision responses and handle hit/overlap events for physics-driven gameplay.
  • Implement server-authoritative networking with replicated properties and RPCs.
  • Apply performance best practices such as object pooling and async asset loading, and use Unreal's debugging and profiling tools.

Estimated Time: 120–150 minutes

Project: Build a networked weapon system with pooled projectiles, a reusable HealthComponent, and replicated damage.

In This Lesson

Why C++ and Unreal Engine?

Unreal Engine is like a high-performance sports car, and C++ is its engine. While you can drive it with Blueprints (visual scripting), knowing C++ gives you access to the engine room where you can fine-tune every aspect of performance and create systems that aren't possible with Blueprints alone.

Setting Up Unreal Engine for C++

Before diving into code, let's set up your development environment properly. Think of this as preparing your workshop before building a masterpiece.

graph TD A["Install Visual Studio/Xcode"] --> B[Install Unreal Engine] B --> C[Create C++ Project] C --> D[Generate Project Files] D --> E[Open in IDE] E --> F[Hot Reload Ready!] style A fill:#3498db,color:#111827 style B fill:#e74c3c,color:#111827 style C fill:#2ecc71,color:#111827 style F fill:#f39c12,color:#111827

Platform-Specific Setup

// Windows: Visual Studio 2022
// Required Components:
// - Game development with C++
// - .NET desktop development
// - Windows 10 or 11 SDK

// macOS: Xcode
// - Latest version from App Store
// - Command Line Tools

// Linux:
// - Use Epic's bundled clang cross-toolchain
//   (exact clang version depends on the engine release)

Unreal Engine C++ Fundamentals

Unreal Engine extends C++ with its own macro system and conventions. It's like C++ with superpowers designed specifically for game development.

Your First Unreal C++ Class

// MyActor.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"  // Always last include!

UCLASS()  // Macro that makes this visible to Unreal
class MYPROJECT_API AMyActor : public AActor
{
    GENERATED_BODY()  // Required boilerplate

public:
    AMyActor();

    // Called every frame
    virtual void Tick(float DeltaTime) override;

protected:
    // Called when the game starts
    virtual void BeginPlay() override;

    // Properties visible in editor
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "My Variables")
    float Speed = 100.0f;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "My Variables")
    int32 Health = 100;

    // Function callable from Blueprints
    UFUNCTION(BlueprintCallable, Category = "My Functions")
    void TakeDamage(int32 DamageAmount);

private:
    float TimeLived = 0.0f;
};

// MyActor.cpp
#include "MyActor.h"

AMyActor::AMyActor()
{
    // Set this actor to call Tick() every frame
    PrimaryActorTick.bCanEverTick = true;
}

void AMyActor::BeginPlay()
{
    Super::BeginPlay();

    UE_LOG(LogTemp, Warning, TEXT("Actor spawned with health: %d"), Health);
}

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

    TimeLived += DeltaTime;

    // Move the actor
    FVector NewLocation = GetActorLocation();
    NewLocation.X += Speed * DeltaTime;
    SetActorLocation(NewLocation);
}

void AMyActor::TakeDamage(int32 DamageAmount)
{
    Health -= DamageAmount;

    if (Health <= 0)
    {
        UE_LOG(LogTemp, Error, TEXT("Actor died!"));
        Destroy();
    }
}

Core Unreal Engine Classes

Unreal Engine provides a rich hierarchy of classes. Understanding these is like knowing the different LEGO blocks available for building your game.

graph TD A[UObject] --> B[AActor] A --> C[UActorComponent] B --> D[APawn] B --> E[AController] D --> F[ACharacter] E --> G[APlayerController] E --> H[AAIController] C --> I[USceneComponent] I --> J[UPrimitiveComponent] J --> K[UStaticMeshComponent] J --> L[USkeletalMeshComponent] style A fill:#3498db,color:#111827 style B fill:#2ecc71,color:#111827 style D fill:#e74c3c,color:#111827 style F fill:#f39c12,color:#111827

Essential Base Classes

// UObject - Base class for all Unreal objects
// Provides reflection, serialization, and garbage collection

// AActor - Can be placed in the world
class MYPROJECT_API AMyGameActor : public AActor
{
    GENERATED_BODY()

public:
    // Components (UE5 prefers TObjectPtr for UPROPERTY object members)
    UPROPERTY(VisibleAnywhere)
    TObjectPtr<UStaticMeshComponent> MeshComponent;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class USphereComponent> CollisionComponent;

    AMyGameActor();
};

// APawn - Actor that can be possessed by a controller
class MYPROJECT_API AMyPawn : public APawn
{
    GENERATED_BODY()

public:
    virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;

    void MoveForward(float Value);
    void MoveRight(float Value);
};

// ACharacter - Pawn with built-in movement
class MYPROJECT_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()

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

    UFUNCTION(BlueprintImplementableEvent, Category = "Combat")
    void OnDeath();  // Implemented in Blueprint

    UFUNCTION(BlueprintNativeEvent, Category = "Combat")
    void TakeDamage(float Damage);  // Can be overridden in Blueprint
    virtual void TakeDamage_Implementation(float Damage);
};

Component System

Components are like LEGO pieces you attach to actors. They provide specific functionality like rendering, physics, or audio.

Creating Custom Components

// HealthComponent.h
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYPROJECT_API UHealthComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    UHealthComponent();

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

    UPROPERTY(BlueprintReadOnly, Category = "Health")
    float CurrentHealth;

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

    UFUNCTION(BlueprintCallable, Category = "Health")
    void Heal(float HealAmount);

    // Events
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, Health);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);

    UPROPERTY(BlueprintAssignable, Category = "Health")
    FOnHealthChanged OnHealthChanged;

    UPROPERTY(BlueprintAssignable, Category = "Health")
    FOnDeath OnDeath;

protected:
    virtual void BeginPlay() override;
};

// HealthComponent.cpp
UHealthComponent::UHealthComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
}

void UHealthComponent::BeginPlay()
{
    Super::BeginPlay();
    CurrentHealth = MaxHealth;
}

void UHealthComponent::TakeDamage(float DamageAmount)
{
    CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.0f, MaxHealth);
    OnHealthChanged.Broadcast(CurrentHealth);

    if (CurrentHealth <= 0.0f)
    {
        OnDeath.Broadcast();
    }
}

void UHealthComponent::Heal(float HealAmount)
{
    CurrentHealth = FMath::Clamp(CurrentHealth + HealAmount, 0.0f, MaxHealth);
    OnHealthChanged.Broadcast(CurrentHealth);
}

Gameplay Framework

Unreal provides a complete gameplay framework. Think of it as the rules and structure of a sports game - you have players, rules, and a playing field.

graph LR A[Game Mode] --> B[Game State] A --> C[Player Controller] C --> D[Player State] C --> E["Pawn/Character"] E --> F[Camera] A --> G[HUD] style A fill:#e74c3c,color:#111827 style C fill:#3498db,color:#111827 style E fill:#2ecc71,color:#111827

Game Mode Example

// MyGameMode.h
UCLASS()
class MYPROJECT_API AMyGameMode : public AGameModeBase
{
    GENERATED_BODY()

public:
    AMyGameMode();

    virtual void StartPlay() override;
    virtual void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) override;

    UFUNCTION(BlueprintCallable, Category = "Game")
    void RespawnPlayer(AController* Controller);

protected:
    UPROPERTY(EditDefaultsOnly, Category = "Game")
    TSubclassOf<APawn> DefaultPawnClass;

    UPROPERTY(EditDefaultsOnly, Category = "Game")
    float RespawnDelay = 3.0f;

    UPROPERTY()
    TArray<TObjectPtr<class APlayerStart>> PlayerStarts;

private:
    void FindPlayerStarts();
    APlayerStart* GetBestPlayerStart(AController* Controller);
};

// MyGameMode.cpp
AMyGameMode::AMyGameMode()
{
    // Set default classes
    DefaultPawnClass = AMyCharacter::StaticClass();
    PlayerControllerClass = AMyPlayerController::StaticClass();
    HUDClass = AMyHUD::StaticClass();
    GameStateClass = AMyGameState::StaticClass();
    PlayerStateClass = AMyPlayerState::StaticClass();
}

void AMyGameMode::StartPlay()
{
    Super::StartPlay();
    FindPlayerStarts();
}

void AMyGameMode::RespawnPlayer(AController* Controller)
{
    if (Controller && Controller->GetPawn())
    {
        Controller->GetPawn()->Destroy();
    }

    // Delay respawn
    FTimerHandle RespawnTimerHandle;
    FTimerDelegate RespawnDelegate;
    RespawnDelegate.BindLambda([this, Controller]()
    {
        if (Controller)
        {
            APlayerStart* SpawnPoint = GetBestPlayerStart(Controller);
            if (SpawnPoint)
            {
                FVector SpawnLocation = SpawnPoint->GetActorLocation();
                FRotator SpawnRotation = SpawnPoint->GetActorRotation();

                APawn* NewPawn = GetWorld()->SpawnActor<APawn>(
                    DefaultPawnClass, SpawnLocation, SpawnRotation
                );

                if (NewPawn)
                {
                    Controller->Possess(NewPawn);
                }
            }
        }
    });

    GetWorldTimerManager().SetTimer(
        RespawnTimerHandle, RespawnDelegate, RespawnDelay, false
    );
}

Input System

Handling input in Unreal is like setting up a control panel - you map physical inputs to game actions.

// MyPlayerController.h
UCLASS()
class MYPROJECT_API AMyPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    virtual void SetupInputComponent() override;

protected:
    // Movement
    void MoveForward(float Value);
    void MoveRight(float Value);
    void Turn(float Value);
    void LookUp(float Value);

    // Actions
    void Jump();
    void StopJumping();
    void Fire();
    void Reload();

    // Enhanced Input System (UE5)
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    TObjectPtr<class UInputMappingContext> DefaultMappingContext;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    TObjectPtr<class UInputAction> MoveAction;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    TObjectPtr<class UInputAction> LookAction;
};

// MyPlayerController.cpp
void AMyPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();

    // Traditional Input (UE4 style)
    InputComponent->BindAxis("MoveForward", this, &AMyPlayerController::MoveForward);
    InputComponent->BindAxis("MoveRight", this, &AMyPlayerController::MoveRight);
    InputComponent->BindAxis("Turn", this, &AMyPlayerController::Turn);
    InputComponent->BindAxis("LookUp", this, &AMyPlayerController::LookUp);

    InputComponent->BindAction("Jump", IE_Pressed, this, &AMyPlayerController::Jump);
    InputComponent->BindAction("Jump", IE_Released, this, &AMyPlayerController::StopJumping);
    InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::Fire);

    // Enhanced Input (UE5 style)
    if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
        ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
    {
        Subsystem->AddMappingContext(DefaultMappingContext, 0);
    }
}

void AMyPlayerController::MoveForward(float Value)
{
    if (APawn* ControlledPawn = GetPawn())
    {
        const FRotator YawRotation(0, GetControlRotation().Yaw, 0);
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        ControlledPawn->AddMovementInput(Direction, Value);
    }
}

Collision and Physics

Collision in Unreal is like setting up invisible force fields around objects that determine how they interact.

Collision Example

// Projectile.h
UCLASS()
class MYPROJECT_API AProjectile : public AActor
{
    GENERATED_BODY()

public:
    AProjectile();

    UPROPERTY(VisibleAnywhere, Category = "Components")
    TObjectPtr<class USphereComponent> CollisionComponent;

    UPROPERTY(VisibleAnywhere, Category = "Components")
    TObjectPtr<class UProjectileMovementComponent> ProjectileMovement;

    UPROPERTY(VisibleAnywhere, Category = "Components")
    TObjectPtr<UStaticMeshComponent> MeshComponent;

    UPROPERTY(EditDefaultsOnly, Category = "Damage")
    float Damage = 20.0f;

    UFUNCTION()
    void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
               UPrimitiveComponent* OtherComponent, FVector NormalImpulse,
               const FHitResult& Hit);

    UFUNCTION()
    void OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                       UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
                       bool bFromSweep, const FHitResult& SweepResult);
};

// Projectile.cpp
AProjectile::AProjectile()
{
    PrimaryActorTick.bCanEverTick = false;

    // Create collision component
    CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
    CollisionComponent->SetSphereRadius(15.0f);
    CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    CollisionComponent->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
    CollisionComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
    CollisionComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);
    CollisionComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block);
    RootComponent = CollisionComponent;

    // Create mesh
    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
    MeshComponent->SetupAttachment(RootComponent);
    MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);

    // Create projectile movement
    ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
    ProjectileMovement->UpdatedComponent = CollisionComponent;
    ProjectileMovement->InitialSpeed = 3000.0f;
    ProjectileMovement->MaxSpeed = 3000.0f;
    ProjectileMovement->bRotationFollowsVelocity = true;
    ProjectileMovement->bShouldBounce = false;
    ProjectileMovement->ProjectileGravityScale = 0.0f;

    // Bind collision events
    CollisionComponent->OnComponentHit.AddDynamic(this, &AProjectile::OnHit);
    CollisionComponent->OnComponentBeginOverlap.AddDynamic(this, &AProjectile::OnBeginOverlap);

    // Die after 3 seconds
    InitialLifeSpan = 3.0f;
}

void AProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
                       UPrimitiveComponent* OtherComponent, FVector NormalImpulse,
                       const FHitResult& Hit)
{
    // Apply damage
    if (OtherActor && OtherActor != GetOwner())
    {
        UGameplayStatics::ApplyPointDamage(
            OtherActor,
            Damage,
            GetActorLocation(),
            Hit,
            nullptr,
            this,
            UDamageType::StaticClass()
        );
    }

    // Spawn impact effect (UE5 uses Niagara; ImpactEffect is a UNiagaraSystem*)
    if (ImpactEffect)
    {
        UNiagaraFunctionLibrary::SpawnSystemAtLocation(
            GetWorld(),
            ImpactEffect,
            Hit.Location,
            Hit.Normal.Rotation()
        );
    }

    // Destroy projectile
    Destroy();
}

Networking and Replication

Unreal's networking is like a synchronized dance - the server leads, and clients follow, with the engine handling most of the complex synchronization.

graph TD A[Server] --> B[Authoritative State] B --> C[Replicate to Client 1] B --> D[Replicate to Client 2] B --> E[Replicate to Client 3] C --> F[Client Prediction] D --> G[Client Prediction] E --> H[Client Prediction] F --> I[Correction if Needed] style A fill:#e74c3c,color:#111827 style B fill:#3498db,color:#111827

Networked Actor Example

// NetworkedCharacter.h
UCLASS()
class MYPROJECT_API ANetworkedCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    ANetworkedCharacter();

    // Replicated properties
    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Stats")
    float Health = 100.0f;

    UPROPERTY(ReplicatedUsing = OnRep_Armor, BlueprintReadOnly, Category = "Stats")
    float Armor = 50.0f;

    // RPC Functions (Remote Procedure Calls)
    UFUNCTION(Server, Reliable, WithValidation)
    void ServerTakeDamage(float DamageAmount, AController* InstigatedBy);

    UFUNCTION(NetMulticast, Reliable)
    void MulticastPlayHitEffect();

    UFUNCTION(Client, Reliable)
    void ClientNotifyKill(const FString& KillerName);

protected:
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

    UFUNCTION()
    void OnRep_Armor();

    virtual void BeginPlay() override;
};

// NetworkedCharacter.cpp
ANetworkedCharacter::ANetworkedCharacter()
{
    // Enable replication
    bReplicates = true;
    SetReplicateMovement(true);
}

void ANetworkedCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // Replicate to everyone
    DOREPLIFETIME(ANetworkedCharacter, Health);

    // Replicate with condition
    DOREPLIFETIME_CONDITION(ANetworkedCharacter, Armor, COND_OwnerOnly);
}

void ANetworkedCharacter::ServerTakeDamage_Implementation(float DamageAmount, AController* InstigatedBy)
{
    // Server-only logic
    if (!HasAuthority()) return;

    float ActualDamage = DamageAmount;

    // Apply armor reduction
    if (Armor > 0)
    {
        float ArmorAbsorbed = FMath::Min(Armor, DamageAmount * 0.5f);
        Armor -= ArmorAbsorbed;
        ActualDamage -= ArmorAbsorbed;
    }

    Health -= ActualDamage;

    // Play effect on all clients
    MulticastPlayHitEffect();

    if (Health <= 0)
    {
        // Notify killer
        if (APlayerController* KillerPC = Cast<APlayerController>(InstigatedBy))
        {
            if (APlayerState* VictimPS = GetPlayerState())
            {
                KillerPC->ClientNotifyKill(VictimPS->GetPlayerName());
            }
        }
    }
}

bool ANetworkedCharacter::ServerTakeDamage_Validate(float DamageAmount, AController* InstigatedBy)
{
    // Validate the RPC - prevent cheating
    return DamageAmount > 0 && DamageAmount <= 1000.0f;
}

void ANetworkedCharacter::MulticastPlayHitEffect_Implementation()
{
    // Play effect on all clients
    // This runs on server and all clients
}

void ANetworkedCharacter::OnRep_Armor()
{
    // Called on clients when Armor changes
    // Update UI or play effects
}

Best Practices and Performance

Writing efficient Unreal C++ code is like tuning a race car - every optimization counts when you're pushing for 60+ FPS.

Performance Best Practices

// Object Pooling Example
UCLASS()
class MYPROJECT_API AProjectilePool : public AActor
{
    GENERATED_BODY()

private:
    UPROPERTY()
    TArray<TObjectPtr<AProjectile>> PooledProjectiles;

    UPROPERTY(EditDefaultsOnly, Category = "Pool")
    TSubclassOf<AProjectile> ProjectileClass;

    UPROPERTY(EditDefaultsOnly, Category = "Pool")
    int32 PoolSize = 50;

public:
    virtual void BeginPlay() override;

    AProjectile* GetPooledProjectile();
    void ReturnProjectileToPool(AProjectile* Projectile);
};

void AProjectilePool::BeginPlay()
{
    Super::BeginPlay();

    // Pre-spawn projectiles
    for (int32 i = 0; i < PoolSize; i++)
    {
        AProjectile* NewProjectile = GetWorld()->SpawnActor<AProjectile>(
            ProjectileClass, FVector::ZeroVector, FRotator::ZeroRotator
        );

        if (NewProjectile)
        {
            NewProjectile->SetActorHiddenInGame(true);
            NewProjectile->SetActorEnableCollision(false);
            NewProjectile->SetActorTickEnabled(false);
            PooledProjectiles.Add(NewProjectile);
        }
    }
}

// Async Loading Example
void AMyGameMode::LoadGameAssets()
{
    // Async load multiple assets
    TArray<FSoftObjectPath> AssetsToLoad;
    AssetsToLoad.Add(FSoftObjectPath("/Game/Weapons/Rifle.Rifle"));
    AssetsToLoad.Add(FSoftObjectPath("/Game/Characters/Enemy.Enemy"));

    FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
    StreamableManager.RequestAsyncLoad(
        AssetsToLoad,
        FStreamableDelegate::CreateUObject(this, &AMyGameMode::OnAssetsLoaded)
    );
}

Debugging and Profiling

Debugging in Unreal is like being a detective with superpowers - you have visual debugging, logging, and profiling tools at your disposal.

// Debug Drawing
void AMyCharacter::DrawDebugInfo()
{
    // Draw sphere at character location
    DrawDebugSphere(
        GetWorld(),
        GetActorLocation(),
        100.0f,           // Radius
        12,               // Segments
        FColor::Red,      // Color
        false,            // Persistent
        2.0f              // Lifetime
    );

    // Draw line to target
    if (CurrentTarget)
    {
        DrawDebugLine(
            GetWorld(),
            GetActorLocation(),
            CurrentTarget->GetActorLocation(),
            FColor::Green,
            false,
            2.0f,
            0,
            5.0f          // Thickness
        );
    }

    // Draw debug string
    DrawDebugString(
        GetWorld(),
        GetActorLocation() + FVector(0, 0, 100),
        FString::Printf(TEXT("Health: %.1f"), Health),
        nullptr,
        FColor::White,
        2.0f,
        true          // Draw shadow
    );
}

// Console Commands
static FAutoConsoleCommand DebugHealthCommand(
    TEXT("Debug.ShowHealth"),
    TEXT("Shows health values above all characters"),
    FConsoleCommandDelegate::CreateLambda([]()
    {
        for (TActorIterator<AMyCharacter> It(GWorld); It; ++It)
        {
            (*It)->bShowHealthDebug = !(*It)->bShowHealthDebug;
        }
    })
);

// Logging
UE_LOG(LogTemp, Display, TEXT("Character spawned at %s"), *GetActorLocation().ToString());
UE_LOG(LogTemp, Warning, TEXT("Low health: %f"), Health);
UE_LOG(LogTemp, Error, TEXT("Failed to find weapon class!"));

// Screen Messages
GEngine->AddOnScreenDebugMessage(
    -1,                    // Key (-1 = auto)
    5.0f,                  // Duration
    FColor::Yellow,        // Color
    FString::Printf(TEXT("Damage Dealt: %.1f"), Damage)
);

// Profiling
DECLARE_CYCLE_STAT(TEXT("MyCharacter Tick"), STAT_MyCharacterTick, STATGROUP_Game);

void AMyCharacter::Tick(float DeltaTime)
{
    SCOPE_CYCLE_COUNTER(STAT_MyCharacterTick);

    Super::Tick(DeltaTime);
    // Your tick code here
}

Practical Exercise: Complete Game System

🏋️ Build a Weapon System

Create a complete weapon system with the following features:

  1. Base weapon class with ammo management
  2. Different weapon types (rifle, shotgun, rocket launcher)
  3. Projectile pooling for performance
  4. Network replication for multiplayer
  5. UI integration for ammo display
// Challenge: Implement this weapon system
class MYPROJECT_API AWeapon : public AActor
{
    // TODO: Add components (mesh, audio)
    // TODO: Add firing mechanism
    // TODO: Add reload system
    // TODO: Add weapon switching
    // TODO: Add network replication
};

Hints:

  • Use object pooling for projectiles
  • Implement different fire modes with inheritance
  • Use RPCs for firing across network
  • Cache references to avoid FindComponent calls
  • Use timers for fire rate control

Resources and Next Steps

Your journey with Unreal Engine C++ has just begun! Here are paths to continue growing:

graph TD A[Master the Basics] --> B[Game Systems] B --> C[AI Programming] B --> D[Animation Systems] B --> E[Procedural Generation] C --> F[Behavior Trees] C --> G[Navigation Mesh] D --> H[Animation Blueprints] D --> I[IK Systems] E --> J[PCG Framework] E --> K[Runtime Mesh] style A fill:#2ecc71,color:#111827 style B fill:#3498db,color:#111827 style C fill:#e74c3c,color:#111827 style D fill:#f39c12,color:#111827 style E fill:#9b59b6,color:#ffffff

Key Takeaways

  • ✓ Unreal extends C++ with powerful game development features
  • ✓ The reflection system enables Blueprint integration
  • ✓ Component-based architecture promotes modularity
  • ✓ Built-in networking makes multiplayer accessible
  • ✓ Performance profiling tools help optimization
  • ✓ Visual debugging accelerates development

Remember: Unreal Engine C++ is a journey, not a destination. Each project teaches new techniques and patterns. Start small, experiment often, and don't forget to have fun creating amazing games!

🎯 Quick Quiz

Question 1: What does the GENERATED_BODY() macro do inside a UCLASS()-derived class declaration?

Question 2: In AProjectile's constructor, components like CollisionComponent and MeshComponent are created with CreateDefaultSubobject<T>(). Why not just use new?

Question 3: ANetworkedCharacter declares UFUNCTION(Server, Reliable, WithValidation) void ServerTakeDamage(...). Which two functions must you implement for this to compile and work correctly?

Summary

🎉 Key Takeaways

  • Unreal exposes C++ to the editor through UCLASS(), UPROPERTY(), and UFUNCTION() macros, processed by the Unreal Header Tool with GENERATED_BODY() supplying the boilerplate.
  • UObject is the root of Unreal's class hierarchy; AActor is placeable in the world, and UActorComponent adds modular behavior to actors.
  • Components (like a custom UHealthComponent) let you compose behavior instead of building deep, fragile inheritance chains.
  • The gameplay framework — GameMode, GameState, PlayerController, PlayerState, Pawn/Character — separates server-authoritative rules from per-player state.
  • Actor/component creation inside constructors must use CreateDefaultSubobject<T>(), never raw new or NewObject<T>().
  • Networking is server-authoritative: mark properties Replicated, implement GetLifetimeReplicatedProps, and use Server/NetMulticast/Client RPCs to cross the network boundary.
  • Object pooling, cached references, and async asset loading keep frame time low; UE_LOG, DrawDebug*, and the stat/profiler macros make performance and logic bugs visible.

📚 Additional Resources

🚀 What's Next?

You've reached the end of the course. You've gone from your very first Hello, World! all the way through pointers, classes, templates, move semantics, smart pointers, and into real Unreal Engine C++ — closing the loop on everything the earlier lessons set up. There's no next lesson queued up; head back to the Course Home and put it all together in a capstone project that forces you to combine pointers, RAII, templates, and operator overloading in one codebase.

🎉 You just leveled up from C++ to game C++!

Actors, components, replication, and RPCs are the exact machinery that ships real Unreal titles — and you've now written all of it. Next stop: the header discipline that keeps big C++ (and Unreal) projects compiling fast.