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.
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.
// 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;
};
Unreal Engine uses pointers everywhere. You'll be constantly working with pointers to Actors, Components, and other game objects. Understanding pointers is non-negotiable.
// 1. Basic Pointer Operations - MUST KNOW
void PointerBasics() {
// Creating pointers
AActor* ActorPtr = nullptr; // Always initialize to nullptr
// Getting pointers from Unreal
ActorPtr = GetWorld()->SpawnActor(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 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 SharedData = MakeShareable(new FMyStruct());
// TUniquePtr - Single owner (not for UObjects!)
TUniquePtr RenderData = MakeUnique();
// TWeakObjectPtr - For UObjects
TWeakObjectPtr WeakActor = SomeActor;
if (WeakActor.IsValid()) {
WeakActor->DoSomething();
}
}
Unlike standard C++, Unreal Engine manages memory for you through garbage collection. Understanding how this works is crucial to avoid crashes and memory leaks.
// 1. UObject Memory Management - CRITICAL
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 StructData;
public:
AMyActor() {
// CORRECT - Let Unreal manage UObject memory
MeshComponent = CreateDefaultSubobject(TEXT("Mesh"));
// WRONG - Never use new/delete with UObjects!
// UnprotectedComponent = new UStaticMeshComponent(); // NEVER DO THIS!
// CORRECT - For non-UObjects
StructData = MakeUnique();
}
void SpawnExample() {
// CORRECT - Spawn actors through the world
AActor* NewActor = GetWorld()->SpawnActor(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
void PreventGC() {
// Method 1: UPROPERTY
UPROPERTY()
AActor* ProtectedActor;
// Method 2: Add to root (rare, special cases)
UObject* ImportantObject = NewObject();
ImportantObject->AddToRoot();
// Remember to remove: ImportantObject->RemoveFromRoot();
// Method 3: Strong references in containers
UPROPERTY()
TArray ActorArray; // All actors in array are protected
}
// 3. Working with Non-UObject Memory
struct FGameData {
TArray 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 Scores; // Dynamic array
TMap PlayerStats; // Hash map
TSet TrackedActors; // Hash set
public:
void MemoryEfficientOperations() {
// Pre-allocate for performance
Scores.Reserve(100);
// Move semantics work in Unreal
TArray TempArray;
Scores = MoveTemp(TempArray);
// Shrink when done
Scores.Shrink();
}
};
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.
// 1. Using Unreal's Template Containers - MUST KNOW
void ContainerBasics() {
// TArray - Dynamic array (like std::vector)
TArray Enemies;
Enemies.Add(Enemy1);
Enemies.Remove(Enemy2);
for (AActor* Enemy : Enemies) {
// Process each enemy
}
// TMap - Hash map (like std::unordered_map)
TMap PlayerScores;
PlayerScores.Add("Player1", 100);
int32* Score = PlayerScores.Find("Player1");
// TSet - Hash set
TSet UniqueNames;
UniqueNames.Add("ItemName");
}
// 2. Template Functions You'll Use - MUST KNOW
void TemplateFunctions() {
AActor* Actor = GetSomeActor();
// Cast - Safe casting with nullptr on failure
ACharacter* Character = Cast(Actor);
if (Character) {
// Successfully cast to ACharacter
}
// GetComponent - Get component by type
UHealthComponent* Health = Actor->GetComponent();
// SpawnActor - Spawn actors of specific type
AProjectile* Projectile = GetWorld()->SpawnActor(
ProjectileClass, Location, Rotation
);
}
// 3. TSubclassOf - Class type safety - IMPORTANT
class AWeaponPickup : public AActor {
// This ensures WeaponClass is a subclass of AWeapon
UPROPERTY(EditDefaultsOnly)
TSubclassOf WeaponClass;
void SpawnWeapon() {
if (WeaponClass) {
GetWorld()->SpawnActor(WeaponClass);
}
}
};
// 4. Creating Simple Templates - NICE TO KNOW
template
T* FindActorOfType(UWorld* World) {
for (TActorIterator It(World); It; ++It) {
return *It; // Return first found
}
return nullptr;
}
// Usage
APlayerController* PC = FindActorOfType(GetWorld());
Unreal uses delegates extensively for event handling. They're like function pointers on steroids, allowing flexible event systems.
// 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 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);
}
}
};
Unreal Engine uses macros extensively. You don't need to create complex macros, but you must understand how to use Unreal's existing ones.
// 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
}
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 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 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 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
}
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::exception("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
}
Before starting with Unreal Engine, make sure you can:
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!
Take this standard C++ code and convert it to Unreal Engine style:
// Standard C++ Version
#include
#include
#include
class GameObject {
private:
std::string name;
float health;
std::vector 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