Unreal Engine is a commercial game engine using an Actor-Component architecture with C++ and Blueprints. It features production-grade rendering (Nanite, Lumen), a Gameplay Framework with built-in multiplayer replication, and comprehensive tooling for large-scale game development.
Unreal's core architecture is based on Actors (entities in the world) and Components (modular functionality).
Key concepts:
AGameModeBase for simple games; use AGameMode (subclass) when you need match state management (warmup, in-progress, post-match)Unreal provides an opinionated framework for game structure:
UGameInstance → Persistent across level loads
├── AGameModeBase → Game rules (server-only in MP)
│ └── AGameStateBase → Replicated game state
├── APlayerController → Player's input and camera
│ └── APlayerState → Replicated per-player state
├── APawn / ACharacter → Player's physical representation
└── AHUD → Player's heads-up display
When to use what:
| Class | Purpose | Lifetime |
|---|---|---|
UGameInstance |
Persistent data, level transitions, subsystems | Entire application |
AGameModeBase |
Spawn rules, basic game flow | Per level (server only) |
AGameMode (subclass) |
Adds match states (warmup, playing, post-match) | Per level (server only) |
AGameState |
Match score, timer, team info | Per level (replicated) |
APlayerController |
Input handling, UI ownership, camera | Per player connection |
APlayerState |
Player score, name, team | Per player (replicated) |
APawn/ACharacter |
Physical entity in world | Can respawn |
Unreal projects are organized into modules:
Source/
├── MyGame/ # Primary game module
│ ├── MyGame.Build.cs # Module build rules
│ ├── MyGame.h / MyGame.cpp # Module implementation
│ ├── Core/ # Core systems
│ ├── Gameplay/ # Gameplay mechanics
│ ├── UI/ # User interface
│ └── AI/ # AI systems
├── MyGameEditor/ # Editor-only module (optional)
│ └── MyGameEditor.Build.cs
└── MyGameTests/ # Test module
└── MyGameTests.Build.cs
Build.cs dependencies:
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
"GameplayAbilities", // If using GAS
"GameplayTags",
"GameplayTasks"
});
| Factor | C++ | Blueprints |
|---|---|---|
| Performance | Maximum performance | Slight overhead per node |
| Compilation | Slow compile, fast runtime | Instant iteration |
| Complexity | Any complexity | Gets unwieldy for complex logic |
| Team access | Programmers only | Designers and artists too |
| Engine access | Full API access | Most API exposed |
| Debugging | Standard C++ debuggers | Visual debugger in editor |
| Source control | Text-based diffs | Binary assets (harder to merge) |
Blueprint binary merge conflict warning:
Blueprints are binary assets (.uasset) and cannot be text-merged. Two developers editing the same Blueprint simultaneously will produce a merge conflict that must be resolved by discarding one person's work. Mitigations:
Recommended hybrid approach:
Pattern: Create C++ base classes, expose variables and functions to Blueprints, let Blueprints handle configuration and scripting
// C++ base class
UCLASS(Blueprintable)
class AWeaponBase : public AActor
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
float BaseDamage = 10.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
float FireRate = 0.5f;
// C++ implementation, callable from Blueprint
UFUNCTION(BlueprintCallable, Category = "Weapon")
void Fire();
// Blueprint can override this
UFUNCTION(BlueprintNativeEvent, Category = "Weapon")
void OnFire();
virtual void OnFire_Implementation();
};
// Then create BP_Shotgun, BP_Rifle, etc. in Blueprints
// inheriting from AWeaponBase
// Visible in Editor, read-only at runtime
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UStaticMeshComponent* Mesh;
// Editable per-instance in Editor
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float MaxHealth = 100.f;
// Editable only on the Blueprint default, not per-instance
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
TSubclassOf<AProjectile> ProjectileClass;
// Not visible in Editor, replicated in multiplayer
UPROPERTY(Replicated, BlueprintReadOnly, Category = "State")
int32 CurrentAmmo;
// Replicated with notification
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;
void OnRep_Health();
// Callable from Blueprints
UFUNCTION(BlueprintCallable, Category = "Combat")
void TakeDamage(float Amount, AActor* Instigator);
// Can be overridden in Blueprints
UFUNCTION(BlueprintNativeEvent, Category = "Combat")
void OnDeath();
// Pure function (no side effects, no exec pin in BP)
UFUNCTION(BlueprintPure, Category = "Stats")
float GetHealthPercent() const;
// Server RPC (multiplayer)
UFUNCTION(Server, Reliable)
void ServerFire(FVector Location, FRotator Rotation);
// Client RPC
UFUNCTION(Client, Reliable)
void ClientShowHitMarker();
// Multicast RPC
UFUNCTION(NetMulticast, Unreliable)
void MulticastPlayFireEffect();
// Use TObjectPtr for UPROPERTY members (UE5+ only — use raw pointers in UE4)
UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComp;
// Use TWeakObjectPtr for non-owning references
TWeakObjectPtr<AActor> TargetActor;
// Use TSoftObjectPtr for assets loaded on demand
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UTexture2D> IconTexture;
// Use TSharedPtr/TUniquePtr for non-UObject data
TSharedPtr<FMyDataStructure> SharedData;
TUniquePtr<FMyProcessor> Processor;
// NEVER use raw new/delete for UObjects — use NewObject/CreateDefaultSubobject
UMyComponent* Comp = CreateDefaultSubobject<UMyComponent>(TEXT("MyComp"));
UMyObject* Obj = NewObject<UMyObject>(this);
// Input Action setup (C++)
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* MoveAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* JumpAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputMappingContext* DefaultMappingContext;
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
auto* EIC = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
EIC->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
EIC->BindAction(JumpAction, ETriggerEvent::Started, this, &AMyCharacter::StartJump);
}
void AMyCharacter::Move(const FInputActionValue& Value)
{
FVector2D Input = Value.Get<FVector2D>();
AddMovementInput(GetActorForwardVector(), Input.Y);
AddMovementInput(GetActorRightVector(), Input.X);
}
Source/
├── MyGame/
│ ├── MyGame.Build.cs
│ ├── Core/
│ │ ├── MyGameInstance.h/.cpp
│ │ ├── MyGameMode.h/.cpp
│ │ └── MyGameState.h/.cpp
│ ├── Characters/
│ │ ├── MyCharacterBase.h/.cpp
│ │ └── Components/
│ │ ├── HealthComponent.h/.cpp
│ │ └── CombatComponent.h/.cpp
│ ├── Weapons/
│ │ ├── WeaponBase.h/.cpp
│ │ └── ProjectileBase.h/.cpp
│ ├── AI/
│ │ ├── AIControllerBase.h/.cpp
│ │ └── BehaviorTrees/
│ ├── UI/
│ │ ├── HUD/
│ │ └── Widgets/
│ ├── Data/
│ │ ├── DataTables/
│ │ └── DataAssets/
│ └── Utils/
Content/
├── Blueprints/
│ ├── Characters/
│ ├── Weapons/
│ ├── AI/
│ └── UI/
├── Maps/
├── Art/
│ ├── Characters/
│ ├── Environment/
│ ├── VFX/
│ └── Materials/
├── Audio/
│ ├── Music/
│ ├── SFX/
│ └── MetaSounds/
├── Data/
│ ├── DataTables/
│ └── Curves/
└── Input/
├── Actions/
└── Contexts/
Naming conventions:
PascalCase matching class nameA (Actor), U (UObject/Component), F (struct), E (enum), I (interface), T (template)BP_PascalCase (e.g., BP_PlayerCharacter)M_Name or MI_Name (instance)T_Name_Suffix (e.g., T_Brick_D for diffuse, _N for normal)WBP_NameDT_NameIA_NameIMC_NameTick sparingly — disable tick on actors that don't need per-frame updates (PrimaryActorTick.bCanEverTick = false)GetWorldTimerManager().SetTimer()TActorIterator in Tick — cache references or use subsystemsBlockAll defaultsTSoftObjectPtr) for assets not always neededAsync loading with FStreamableManager:
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
StreamableManager.RequestAsyncLoad(AssetPath,
FStreamableDelegate::CreateUObject(this, &AMyActor::OnAssetLoaded));
Level streaming for large worlds (World Partition in UE5)
Garbage collection — avoid creating many UObjects per frame
Object pooling for projectiles, effects, AI actors
FString operations in hot paths — use FName for comparisonsTInlineAllocator for small arrays: TArray<FHitResult, TInlineAllocator<4>>FORCEINLINE for small, frequently-called functionsParallelFor for parallelizable work| Plugin | Purpose | Source |
|---|---|---|
| Gameplay Ability System (GAS) | Abilities, attributes, effects framework | Engine built-in (module) |
| Common UI | Cross-platform UI framework with input routing | Engine plugin |
| Enhanced Input | Modern input handling | Engine built-in |
| Mass Entity (Mass AI) | Large-scale entity simulation (ECS-like) | Engine (production since UE 5.4) |
| Online Subsystem Steam | Steamworks integration | Engine built-in |
| Plugin | Purpose | Source |
|---|---|---|
| Gameplay Ability System | RPG stats, buffs, cooldowns, combos | Epic (included) |
| ALS (Advanced Locomotion System) | Production-quality character movement | Marketplace (community) |
| Narrative | Branching dialogue and quest system | Marketplace |
| Voxel Plugin | Voxel terrain generation and editing | Marketplace |
| Easy Multi Save | Save/load system with serialization | Marketplace |
| Plugin | Purpose | Source |
|---|---|---|
| MetaSounds | Procedural audio graph system | Engine built-in |
| FMOD for Unreal | Professional audio middleware | FMOD website (free for indie) |
| Wwise | AAA audio middleware | Audiokinetic website |
| Niagara | GPU particle and VFX system | Engine built-in |
| Water System | Ocean and water body rendering | Engine plugin |
| Plugin | Purpose | Source |
|---|---|---|
| Online Subsystem | Platform abstraction for networking | Engine built-in |
| Epic Online Services (EOS) | Auth, matchmaking, lobbies, voice, anti-cheat | Epic (free) |
| Steamworks | Steam API (achievements, leaderboards, P2P) | Engine + Steamworks SDK |
| PlayFab | Cloud backend (analytics, leaderboards, economy) | Microsoft (free tier) |
| Nakama | Open-source game server | Docker + Unreal SDK |
| Plugin | Purpose | Source |
|---|---|---|
| Gameplay Debugger | In-game AI and gameplay visualization | Engine built-in |
| Unreal Insights | Profiling and telemetry | Engine built-in |
| Gauntlet | Automated testing framework | Engine built-in |
| Horde | Distributed build system | Epic (for large teams) |
Subsystems are engine-managed singletons tied to a specific lifetime:
// Game Instance Subsystem — lives for entire application
UCLASS()
class UInventorySubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
UFUNCTION(BlueprintCallable)
void AddItem(FName ItemID, int32 Count = 1);
UFUNCTION(BlueprintPure)
int32 GetItemCount(FName ItemID) const;
private:
TMap<FName, int32> Inventory;
};
// Access from anywhere:
UInventorySubsystem* Inv = GetGameInstance()->GetSubsystem<UInventorySubsystem>();
Available lifetimes: UEngineSubsystem, UEditorSubsystem, UGameInstanceSubsystem, UWorldSubsystem, ULocalPlayerSubsystem
// Define row structure
USTRUCT(BlueprintType)
struct FWeaponData : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
float Damage = 10.f;
UPROPERTY(EditAnywhere)
float FireRate = 1.f;
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UStaticMesh> WeaponMesh;
UPROPERTY(EditAnywhere)
TSubclassOf<AProjectile> ProjectileClass;
};
// Look up at runtime
if (FWeaponData* Data = WeaponTable->FindRow<FWeaponData>(WeaponID, TEXT("")))
{
Damage = Data->Damage;
}
GAS is Unreal's built-in framework for RPG-style abilities:
Use GAS when: RPG systems, complex buff/debuff, ability cooldowns, attribute interactions, multiplayer-replicated combat.
Skip GAS when: Simple action games, platformers, puzzle games — the overhead isn't justified.
Unreal Engine uses a royalty model:
Cost implications:
Unreal may be the wrong choice when:
When choosing Unreal for a project, verify: