📝 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(), andUFUNCTION()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, andACharactersubclasses and place them correctly in theUObjectclass hierarchy. - Build reusable
UActorComponents (like aHealthComponent) 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.
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.
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.
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.
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:
- Base weapon class with ammo management
- Different weapon types (rifle, shotgun, rocket launcher)
- Projectile pooling for performance
- Network replication for multiplayer
- 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:
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
neworNewObject<T>(). - Networking is server-authoritative: mark properties
Replicated, implementGetLifetimeReplicatedProps, 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
- cppreference.com
- Unreal Engine Documentation — Programming with C++
- Unreal Engine Documentation — Gameplay Framework
🚀 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.