This page covers Unreal Engine-specific C++ patterns — the UObject system, reflection macros, engine APIs, and idioms that differ from standard C++.
For the full C++ language reference (STL, templates, smart pointers, move semantics, etc.) see Cpp.
For the full Unreal Engine reference (editor, Blueprints, Nanite, Lumen, AI, networking, etc.) see Unreal Engine.
UObject System & Reflection
Why UObject?
Unreal's UObject system provides:
Reflection — inspect class/property info at runtime
Serialization — save/load objects to disk automatically
Garbage Collection — automatic memory management (no delete needed)
Blueprint Exposure — expose C++ to visual scripting
Networking — automatic replication of marked properties
CDO (Class Default Object) — default values per class
Rule: Any class that needs these features must inherit from UObject.
Plain C++ structs/classes (no UObject) are fine for pure data/algorithms.
Class Hierarchy
UObject — base of all Unreal objects (GC, reflection)
├── UActorComponent — component attached to an Actor
│ ├── USceneComponent — component with a transform
│ │ ├── UMeshComponent
│ │ └── UCameraComponent
│ └── UMovementComponent
├── AActor — anything placed in a level
│ ├── APawn — actor that can be possessed
│ │ └── ACharacter — pawn with mesh + movement
│ ├── AController — controls a Pawn
│ │ ├── APlayerController
│ │ └── AAIController
│ ├── AGameModeBase — game rules
│ └── AInfo — non-visual actors (GameState, PlayerState)
└── UGameInstance — persistent across levels
UCLASS Macro
// UCLASS — registers class with Unreal's reflection system// Must be placed immediately before the class declaration// GENERATED_BODY() must be first line inside the classUCLASS() // minimal — just registersUCLASS(Blueprintable) // can be subclassed in BlueprintUCLASS(BlueprintType) // can be used as a variable type in BlueprintUCLASS(Abstract) // cannot be instantiated directlyUCLASS(NotBlueprintable) // C++ only, no Blueprint subclassingUCLASS(Config=Game) // reads/writes to config .ini fileUCLASS(Within=UGameInstance) // can only exist inside a GameInstanceUCLASS(Transient) // not saved to diskUCLASS(MinimalAPI) // only exports a few symbols (for modules)// Full exampleUCLASS(Blueprintable, BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))class MYGAME_API UMyComponent : public UActorComponent{ GENERATED_BODY()public: UMyComponent();};
UPROPERTY Specifiers — Complete Reference
// ── Editor Visibility ──────────────────────────────────────────UPROPERTY(EditAnywhere) // editable in editor (instance + CDO)UPROPERTY(EditDefaultsOnly) // editable only in Blueprint/CDO defaultsUPROPERTY(EditInstanceOnly) // editable only on placed instancesUPROPERTY(VisibleAnywhere) // visible but read-only in editorUPROPERTY(VisibleDefaultsOnly) // visible only in defaultsUPROPERTY(VisibleInstanceOnly) // visible only on instances// ── Blueprint Access ───────────────────────────────────────────UPROPERTY(BlueprintReadWrite) // read + write from BlueprintUPROPERTY(BlueprintReadOnly) // read only from BlueprintUPROPERTY(BlueprintAssignable) // delegate can be bound in BlueprintUPROPERTY(BlueprintCallable) // delegate can be called from Blueprint// ── Networking ─────────────────────────────────────────────────UPROPERTY(Replicated) // replicated to clientsUPROPERTY(ReplicatedUsing = OnRep_Health) // replicated with callbackUPROPERTY(NotReplicated) // explicitly not replicated// ── Serialization ──────────────────────────────────────────────UPROPERTY(Transient) // not saved to disk, not replicatedUPROPERTY(SaveGame) // included in SaveGame serializationUPROPERTY(Config) // read from .ini config fileUPROPERTY(GlobalConfig) // read from global config// ── Garbage Collection ─────────────────────────────────────────// Any UPROPERTY UObject* is automatically tracked by GC// Raw UObject* without UPROPERTY will be GC'd unexpectedly!UPROPERTY() UMyObject* SafeRef; // GC-trackedUMyObject* UnsafeRef; // DANGER — may become dangling pointer// ── Organization ───────────────────────────────────────────────UPROPERTY(Category = "Combat")UPROPERTY(Category = "Combat|Weapons") // subcategoryUPROPERTY(DisplayName = "Max HP")// ── Meta Specifiers ────────────────────────────────────────────UPROPERTY(meta = (ClampMin = "0.0", ClampMax = "100.0"))UPROPERTY(meta = (UIMin = "0", UIMax = "1"))UPROPERTY(meta = (AllowPrivateAccess = "true")) // expose private to BPUPROPERTY(meta = (MakeEditWidget)) // show transform widgetUPROPERTY(meta = (ExposeOnSpawn = "true")) // show on SpawnActor nodeUPROPERTY(meta = (InlineEditConditionToggle)) // checkbox to enable fieldUPROPERTY(meta = (EditCondition = "bEnabled")) // only editable if bEnabled// ── Combined Example ───────────────────────────────────────────UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Combat", meta = (ClampMin = "0", ClampMax = "1000"))float Health = 100.f;
UFUNCTION Specifiers — Complete Reference
// ── Blueprint ──────────────────────────────────────────────────UFUNCTION(BlueprintCallable, Category = "Combat")void Attack(); // callable from Blueprint (has exec pin)UFUNCTION(BlueprintPure, Category = "Stats")float GetHealthPercent() const; // no exec pin, no side effectsUFUNCTION(BlueprintImplementableEvent)void OnDeath(); // implemented in Blueprint, called from C++// Note: no C++ body needed (or allowed)UFUNCTION(BlueprintNativeEvent)void OnHit(float Damage); // C++ default + Blueprint can overridevirtual void OnHit_Implementation(float Damage); // C++ body goes here// ── Networking (RPCs) ──────────────────────────────────────────UFUNCTION(Server, Reliable, WithValidation)void ServerFire(FVector Direction);void ServerFire_Implementation(FVector Direction);bool ServerFire_Validate(FVector Direction) { return true; }UFUNCTION(Client, Reliable)void ClientShowEffect(FVector Location);void ClientShowEffect_Implementation(FVector Location);UFUNCTION(NetMulticast, Unreliable)void MulticastPlaySound(USoundBase* Sound);void MulticastPlaySound_Implementation(USoundBase* Sound);// ── Editor ─────────────────────────────────────────────────────UFUNCTION(CallInEditor, Category = "Tools")void BakeNavMesh(); // button appears in Details panelUFUNCTION(Exec)void SetGodMode(bool bEnabled); // console command: SetGodMode true// ── Meta ───────────────────────────────────────────────────────UFUNCTION(BlueprintCallable, meta = (DisplayName = "Take Damage (Custom)"))void TakeDamageCustom(float Amount);UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Result"))void CheckState(EMyState& Result); // creates separate exec pins per enum value
USTRUCT
// USTRUCT — plain data struct with reflection// No GC, no inheritance from UObject, no virtual functions// Great for: inventory items, stats, config data, network messagesUSTRUCT(BlueprintType)struct FWeaponStats{ GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) float Damage = 25.f; UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.5f; UPROPERTY(EditAnywhere, BlueprintReadWrite) float Range = 1000.f; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 MagazineSize = 30; // Constructors and methods are fine FWeaponStats() = default; FWeaponStats(float D, float FR) : Damage(D), FireRate(FR) {} bool IsValid() const { return Damage > 0.f && FireRate > 0.f; }};// UsageFWeaponStats Stats;Stats.Damage = 50.f;// In UPROPERTYUPROPERTY(EditAnywhere, BlueprintReadWrite)FWeaponStats WeaponStats;
UENUM
// UENUM — enum with reflection, usable in BlueprintUENUM(BlueprintType)enum class ECharacterState : uint8{ Idle UMETA(DisplayName = "Idle"), Walking UMETA(DisplayName = "Walking"), Running UMETA(DisplayName = "Running"), Jumping UMETA(DisplayName = "Jumping"), Attacking UMETA(DisplayName = "Attacking"), Dead UMETA(DisplayName = "Dead")};// UsageUPROPERTY(EditAnywhere, BlueprintReadWrite)ECharacterState State = ECharacterState::Idle;// Switchswitch (State){ case ECharacterState::Idle: HandleIdle(); break; case ECharacterState::Attacking: HandleAttack(); break; default: break;}// Get string nameFString StateName = UEnum::GetValueAsString(State); // "ECharacterState::Idle"
Unreal Container Types
TArray — Dynamic Array
// TArray<T> — Unreal's equivalent of std::vector// Supports UPROPERTY, replication, Blueprint exposureTArray<int32> Numbers = {1, 2, 3, 4, 5};TArray<AActor*> Actors;TArray<FString> Names;// Add / RemoveNumbers.Add(6);Numbers.AddUnique(3); // only adds if not presentNumbers.Insert(99, 2); // insert at index 2Numbers.Remove(3); // remove first occurrence of valueNumbers.RemoveAt(0); // remove at indexNumbers.RemoveAll([](int32 N) { return N < 3; }); // remove matchingNumbers.Empty(); // clear allNumbers.Reset(); // clear but keep allocation// Accessint32 First = Numbers[0];int32 Last = Numbers.Last();int32 Last2 = Numbers.Last(1); // second to lastint32 Num = Numbers.Num(); // countbool bEmpty = Numbers.IsEmpty();bool bValid = Numbers.IsValidIndex(3);// Searchbool bHas = Numbers.Contains(5);int32 Idx = Numbers.Find(5); // -1 if not foundint32 Idx2 = Numbers.IndexOfByKey(5);int32 Idx3 = Numbers.IndexOfByPredicate([](int32 N) { return N > 3; });// SortNumbers.Sort();Numbers.Sort([](int32 A, int32 B) { return A > B; }); // descendingNumbers.StableSort();// Iterationfor (int32 N : Numbers) { UE_LOG(LogTemp, Log, TEXT("%d"), N); }for (int32 i = 0; i < Numbers.Num(); i++) { Numbers[i] *= 2; }// FunctionalTArray<int32> Evens = Numbers.FilterByPredicate([](int32 N) { return N % 2 == 0; });// Append / CombineTArray<int32> More = {7, 8, 9};Numbers.Append(More);Numbers += More;// Reserve memoryNumbers.Reserve(100);Numbers.SetNum(10); // resize (fills with default)Numbers.SetNumZeroed(10); // resize and zero-fill// SliceTArray<int32> Slice = TArray<int32>(Numbers.GetData() + 2, 3); // 3 elements from index 2
TMap — Hash Map
// TMap<K,V> — Unreal's equivalent of std::unordered_mapTMap<FString, int32> Scores;TMap<int32, AActor*> ActorById;// Add / SetScores.Add(TEXT("Alice"), 95);Scores.Add(TEXT("Bob"), 87);Scores.Emplace(TEXT("Charlie"), 92);Scores.FindOrAdd(TEXT("Dave")) = 78; // add if missing, return ref// Accessint32* pScore = Scores.Find(TEXT("Alice")); // nullptr if not foundint32 Score = Scores.FindRef(TEXT("Alice")); // 0 if not foundint32& Ref = Scores[TEXT("Alice")]; // assert if not found// Checkbool bHas = Scores.Contains(TEXT("Alice"));int32 Num = Scores.Num();// RemoveScores.Remove(TEXT("Bob"));Scores.Empty();// Iteratefor (auto& [Key, Value] : Scores){ UE_LOG(LogTemp, Log, TEXT("%s: %d"), *Key, Value);}for (auto It = Scores.CreateIterator(); It; ++It){ UE_LOG(LogTemp, Log, TEXT("%s: %d"), *It.Key(), It.Value()); // It.RemoveCurrent(); // safe removal during iteration}// Keys / ValuesTArray<FString> Keys;Scores.GetKeys(Keys);TArray<int32> Values;Scores.GenerateValueArray(Values);
TSet — Hash Set
TSet<FString> Tags;Tags.Add(TEXT("Enemy"));Tags.Add(TEXT("Boss"));Tags.Add(TEXT("Enemy")); // duplicate — ignoredbool bHas = Tags.Contains(TEXT("Boss")); // trueTags.Remove(TEXT("Enemy"));int32 Num = Tags.Num();for (const FString& Tag : Tags) { UE_LOG(LogTemp, Log, TEXT("%s"), *Tag); }// Set operationsTSet<FString> A = {TEXT("a"), TEXT("b"), TEXT("c")};TSet<FString> B = {TEXT("b"), TEXT("c"), TEXT("d")};TSet<FString> Union = A.Union(B); // {a,b,c,d}TSet<FString> Intersect = A.Intersect(B); // {b,c}TSet<FString> Diff = A.Difference(B); // {a}
TOptional, TVariant, TSharedPtr
// TOptional<T> — may or may not have a value (like std::optional)TOptional<float> MaybeHealth;MaybeHealth = 75.f;if (MaybeHealth.IsSet()) UE_LOG(LogTemp, Log, TEXT("Health: %f"), MaybeHealth.GetValue());float Val = MaybeHealth.Get(100.f); // default if not setMaybeHealth.Reset(); // clear value// TSharedPtr<T> — ref-counted smart pointer for non-UObject types// Use for plain C++ classes that don't inherit UObjectTSharedPtr<FMyData> Data = MakeShared<FMyData>();TSharedRef<FMyData> Ref = MakeShared<FMyData>(); // never nullTWeakPtr<FMyData> Weak = Data; // non-owningif (TSharedPtr<FMyData> Locked = Weak.Pin()) // lock weak ptr{ Locked->DoSomething();}// TUniquePtr<T> — exclusive ownership (like std::unique_ptr)TUniquePtr<FMyData> Unique = MakeUnique<FMyData>();TUniquePtr<FMyData> Moved = MoveTemp(Unique); // transfer ownership
Unreal String Types
FString, FName, FText
// FString — mutable, heap-allocated, general purposeFString Name = TEXT("Alice");FString Greeting = FString::Printf(TEXT("Hello, %s! HP: %.1f"), *Name, 100.f);Name.Len(); // lengthName.IsEmpty(); // check emptyName.ToUpper(); // uppercase copyName.ToLower(); // lowercase copyName.Contains(TEXT("Ali")); // substring checkName.StartsWith(TEXT("Al"));Name.EndsWith(TEXT("ce"));Name.Replace(TEXT("Alice"), TEXT("Bob"));Name.Left(3); // "Ali"Name.Right(3); // "ice"Name.Mid(1, 3); // "lic"TArray<FString> Parts;Name.ParseIntoArray(Parts, TEXT(","), true); // split by delimiterFString Joined = FString::Join(Parts, TEXT(", "));// Convertint32 N = FCString::Atoi(*Name); // string to intfloat F = FCString::Atof(*Name); // string to floatFString FromInt = FString::FromInt(42);FString FromFloat = FString::SanitizeFloat(3.14f);// Dereference with * to get TCHAR*UE_LOG(LogTemp, Log, TEXT("Name: %s"), *Name);// FName — immutable, hashed, fast comparison, case-insensitive// Use for: asset names, bone names, socket names, parameter namesFName BoneName = TEXT("spine_01");FName SocketName = FName(TEXT("MuzzleSocket"));bool bSame = (BoneName == FName(TEXT("SPINE_01"))); // true (case-insensitive)FString AsString = BoneName.ToString();// FText — localized, display-only text// Use for: UI text, subtitles, anything shown to the playerFText DisplayName = FText::FromString(TEXT("Player One"));FText Formatted = FText::Format( LOCTEXT("ScoreFormat", "Score: {0}"), FText::AsNumber(Score));FString Raw = DisplayName.ToString(); // extract raw string// Summary:// FString — general string manipulation, file paths, debug// FName — identifiers, asset/bone/socket names (fast compare)// FText — player-facing text (localization support)
Memory Management & Garbage Collection
GC Rules
// Unreal uses a mark-and-sweep GC for UObjects// GC runs periodically — objects with no references are destroyed// RULE 1: UObject* must be in a UPROPERTY to be GC-safeUPROPERTY() UMyObject* SafeRef; // GC tracks thisUMyObject* UnsafeRef; // GC may destroy this unexpectedly!// RULE 2: Never use raw new/delete for UObjects// BAD:UMyObject* Obj = new UMyObject(); // wrong!delete Obj; // wrong!// GOOD:UMyObject* Obj = NewObject<UMyObject>(this); // correctObj->ConditionalBeginDestroy(); // request destruction (rarely needed)// RULE 3: Use TWeakObjectPtr for non-owning referencesTWeakObjectPtr<AActor> WeakRef = SomeActor;if (WeakRef.IsValid()){ WeakRef->DoSomething(); // safe — checks validity}// RULE 4: Use TObjectPtr<T> for UPROPERTY members (UE5+)UPROPERTY()TObjectPtr<UStaticMeshComponent> Mesh; // preferred over raw pointer in UE5// RULE 5: Actors are destroyed with Destroy(), not GCSomeActor->Destroy(); // removes from world, GC cleans up later
Object Creation
// NewObject — create UObject (not Actor)UMyObject* Obj = NewObject<UMyObject>(this); // outer = thisUMyObject* Obj2 = NewObject<UMyObject>(this, UMyObject::StaticClass());UMyObject* Obj3 = NewObject<UMyObject>(this, TEXT("MyObjName")); // named// CreateDefaultSubobject — create component in constructor ONLYMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));// SpawnActor — create Actor in the worldFActorSpawnParameters Params;Params.Owner = this;Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;AMyActor* Actor = GetWorld()->SpawnActor<AMyActor>( AMyActor::StaticClass(), SpawnLocation, SpawnRotation, Params);// Deferred spawn — set properties before BeginPlay firesAMyActor* Actor = GetWorld()->SpawnActorDeferred<AMyActor>( AMyActor::StaticClass(), SpawnTransform, Owner, Instigator);Actor->Health = 500.f;Actor->Team = ETeam::Red;UGameplayStatics::FinishSpawningActor(Actor, SpawnTransform);// Load class from soft reference (async-friendly)TSoftClassPtr<AMyActor> SoftClass;TSubclassOf<AMyActor> LoadedClass = SoftClass.LoadSynchronous();
Actor & Component Lifecycle
Full Lifecycle
class AMyActor : public AActor{ GENERATED_BODY()public: // ── Construction ────────────────────────────────────────── AMyActor(); // Called when CDO is created and when actor is placed in editor. // CreateDefaultSubobject goes here. No world access. virtual void PostInitProperties() override; // After properties are initialized from CDO. virtual void PostLoad() override; // After actor is loaded from disk (level load). // ── World Entry ─────────────────────────────────────────── virtual void PreInitializeComponents() override; // Before components are initialized. virtual void PostInitializeComponents() override; // After all components are initialized. Safe to access components. virtual void BeginPlay() override; // Actor is fully in the world. Game is running. // Call Super::BeginPlay() first! // ── Per Frame ───────────────────────────────────────────── virtual void Tick(float DeltaTime) override; // Called every frame if PrimaryActorTick.bCanEverTick = true. // Call Super::Tick(DeltaTime) first! // ── Destruction ─────────────────────────────────────────── virtual void EndPlay(const EEndPlayReason::Type Reason) override; // Actor is leaving the world (destroyed, level unloaded, PIE ended). // Reason: Destroyed, LevelTransition, EndPlayInEditor, Quit, RemovedFromWorld virtual void BeginDestroy() override; // GC is about to destroy this object. Don't access other UObjects here.};// Component lifecycleclass UMyComponent : public UActorComponent{ GENERATED_BODY()public: virtual void InitializeComponent() override; // after PostInitializeComponents virtual void BeginPlay() override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; virtual void EndPlay(const EEndPlayReason::Type Reason) override; virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override;};
Tick Configuration
AMyActor::AMyActor(){ // Enable tick (disabled by default for performance) PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = true; PrimaryActorTick.TickInterval = 0.1f; // tick every 0.1s instead of every frame PrimaryActorTick.TickGroup = TG_PostPhysics; // TG_PrePhysics, TG_DuringPhysics, TG_PostPhysics // Component tick MyComp->PrimaryComponentTick.bCanEverTick = true; MyComp->PrimaryComponentTick.TickInterval = 0.5f;}// Enable/disable tick at runtimeSetActorTickEnabled(false);SetActorTickInterval(0.2f);MyComp->SetComponentTickEnabled(false);// Add tick dependency (this ticks after OtherActor)AddTickPrerequisiteActor(OtherActor);AddTickPrerequisiteComponent(OtherComp);
Delegates & Events
Delegate Types
// Single-cast delegates (one binding at a time)DECLARE_DELEGATE(FOnSimpleEvent);DECLARE_DELEGATE_OneParam(FOnDamage, float);DECLARE_DELEGATE_TwoParams(FOnHit, AActor*, float);DECLARE_DELEGATE_RetVal(bool, FOnValidate);DECLARE_DELEGATE_RetVal_OneParam(bool, FOnValidateActor, AActor*);// Multi-cast delegates (multiple bindings)DECLARE_MULTICAST_DELEGATE(FOnGameStart);DECLARE_MULTICAST_DELEGATE_OneParam(FOnScoreChanged, int32);// Dynamic delegates (Blueprint-bindable, slower)DECLARE_DYNAMIC_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDeath, AActor*, Killer);// Dynamic multicast — use with UPROPERTY(BlueprintAssignable)UPROPERTY(BlueprintAssignable)FOnDeath OnDeath;
Binding & Broadcasting
// Single-cast bindingFOnDamage OnDamage;OnDamage.BindUObject(this, &AMyActor::HandleDamage);OnDamage.BindLambda([](float D) { UE_LOG(LogTemp, Log, TEXT("Damage: %f"), D); });OnDamage.BindStatic(&MyStaticFunction);OnDamage.BindRaw(RawPtr, &FMyClass::Method); // raw C++ pointer (no GC safety)// ExecuteOnDamage.ExecuteIfBound(25.f); // safe — checks if boundOnDamage.Execute(25.f); // asserts if not boundbool bBound = OnDamage.IsBound();OnDamage.Unbind();// Multi-cast bindingFOnScoreChanged OnScoreChanged;FDelegateHandle Handle = OnScoreChanged.AddUObject(this, &AMyHUD::UpdateScore);OnScoreChanged.AddLambda([](int32 S) { UE_LOG(LogTemp, Log, TEXT("Score: %d"), S); });// Broadcast to all bound functionsOnScoreChanged.Broadcast(NewScore);// Remove specific bindingOnScoreChanged.Remove(Handle);OnScoreChanged.RemoveAll(this); // remove all bindings from this object// Dynamic multicast (Blueprint-compatible)OnDeath.AddDynamic(this, &AMyActor::HandleDeath);OnDeath.RemoveDynamic(this, &AMyActor::HandleDeath);OnDeath.Broadcast(Killer);
#include "Async/Async.h"// Run on background threadAsync(EAsyncExecution::ThreadPool, [](){ // Heavy computation — runs on thread pool FPlatformProcess::Sleep(1.0f); UE_LOG(LogTemp, Log, TEXT("Background work done"));});// Run on background thread, then callback on game threadAsync(EAsyncExecution::ThreadPool, []() -> int32{ return HeavyComputation();}).Then([](TFuture<int32> Future){ int32 Result = Future.Get(); // Back on calling thread — may not be game thread});// Run on game thread (from any thread)AsyncTask(ENamedThreads::GameThread, [this](){ // Safe to access UObjects here Health = 100.f;});// TFuture / TPromiseTPromise<FString> Promise;TFuture<FString> Future = Promise.GetFuture();Async(EAsyncExecution::ThreadPool, [Promise = MoveTemp(Promise)]() mutable{ FString Result = DoWork(); Promise.SetValue(Result);});FString Value = Future.Get(); // blocks until ready
Latent Actions (Blueprint-compatible async)
// Latent action — async operation that shows as a node with exec pins in BlueprintUFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject"), Category = "Async")static void LoadDataAsync( UObject* WorldContextObject, FLatentActionInfo LatentInfo, FString DataPath, FString& OutData);// Implementationvoid UMyBlueprintLibrary::LoadDataAsync( UObject* WorldContextObject, FLatentActionInfo LatentInfo, FString DataPath, FString& OutData){ if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) { FLatentActionManager& LAM = World->GetLatentActionManager(); if (!LAM.FindExistingAction<FMyLatentAction>( LatentInfo.CallbackTarget, LatentInfo.UUID)) { LAM.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FMyLatentAction(LatentInfo, DataPath, OutData)); } }}
Interfaces
UInterface
// Declare interface — two classes neededUINTERFACE(MinimalAPI, Blueprintable)class UDamageable : public UInterface{ GENERATED_BODY()};class IDamageable{ GENERATED_BODY()public: // Pure virtual — must implement in C++ virtual void TakeDamage(float Amount) = 0; // BlueprintNativeEvent — C++ default, Blueprint can override UFUNCTION(BlueprintNativeEvent, BlueprintCallable) void OnDamaged(float Amount, AActor* DamageCauser); virtual void OnDamaged_Implementation(float Amount, AActor* DamageCauser) {} // BlueprintImplementableEvent — implemented in Blueprint only UFUNCTION(BlueprintImplementableEvent, BlueprintCallable) void OnHealed(float Amount);};// Implement in a classUCLASS()class AEnemy : public ACharacter, public IDamageable{ GENERATED_BODY()public: virtual void TakeDamage(float Amount) override; virtual void OnDamaged_Implementation(float Amount, AActor* Causer) override;};// Call interface function (works on any actor, even Blueprint-only)void AWeapon::Hit(AActor* Target){ // Check if implements interface if (Target->Implements<UDamageable>()) { IDamageable::Execute_OnDamaged(Target, Damage, this); // dynamic dispatch } // Or cast (C++ only) if (IDamageable* Damageable = Cast<IDamageable>(Target)) { Damageable->TakeDamage(Damage); }}
Subsystems
USubsystem Types
// Subsystems — auto-created singletons scoped to their owner// No manual registration needed — engine creates/destroys automatically// UGameInstanceSubsystem — lives as long as GameInstance (across levels)UCLASS()class UInventorySubsystem : public UGameInstanceSubsystem{ GENERATED_BODY()public: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; UFUNCTION(BlueprintCallable) void AddItem(FName ItemId, int32 Quantity); UFUNCTION(BlueprintCallable, BlueprintPure) int32 GetItemCount(FName ItemId) const;private: TMap<FName, int32> Inventory;};// Access from anywhereUGameInstance* GI = GetGameInstance();UInventorySubsystem* Inv = GI->GetSubsystem<UInventorySubsystem>();Inv->AddItem(TEXT("Sword"), 1);// UWorldSubsystem — one per World (level)UCLASS()class USpawnSubsystem : public UWorldSubsystem{ GENERATED_BODY()public: virtual void OnWorldBeginPlay(UWorld& InWorld) override; void RegisterSpawnPoint(ASpawnPoint* Point);};// AccessUSpawnSubsystem* Spawner = GetWorld()->GetSubsystem<USpawnSubsystem>();// ULocalPlayerSubsystem — one per local player// UEngineSubsystem — one for the entire engine lifetime
Networking — Replication In Depth
Replication Setup
AMyCharacter::AMyCharacter(){ bReplicates = true; // replicate actor existence SetReplicateMovement(true); // replicate transform}// GetLifetimeReplicatedProps — declare which properties replicatevoid AMyCharacter::GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps) const{ Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AMyCharacter, Health); // replicate to all DOREPLIFETIME_CONDITION(AMyCharacter, Ammo, COND_OwnerOnly); // only to owning client DOREPLIFETIME_CONDITION(AMyCharacter, bIsAiming, COND_SkipOwner); // all except owner DOREPLIFETIME_CONDITION(AMyCharacter, TeamColor, COND_InitialOnly); // only on initial replication DOREPLIFETIME_CONDITION_NOTIFY(AMyCharacter, Shield, COND_None, REPNOTIFY_Always); // always call OnRep even if value unchanged}
Network Roles & Authority
// Check network rolebool bIsServer = HasAuthority(); // true on serverbool bIsClient = !HasAuthority();bool bIsOwner = IsLocallyControlled(); // true on owning clientENetRole Role = GetLocalRole();// ROLE_Authority — server (owns the actor)// ROLE_AutonomousProxy — owning client (player's own character)// ROLE_SimulatedProxy — non-owning client (other players)// ROLE_None — not replicated// Common patternvoid AMyCharacter::TakeDamage(float Amount){ if (!HasAuthority()) return; // only server modifies health Health -= Amount; if (Health <= 0.f) Die();}// RPC patternsvoid AMyCharacter::Fire(){ if (IsLocallyControlled()) { PlayFireAnimation(); // local cosmetic ServerFire(GetAimDir()); // tell server }}void AMyCharacter::ServerFire_Implementation(FVector Dir){ // Server: authoritative logic SpawnProjectile(Dir); MulticastFireEffect(GetActorLocation()); // tell all clients}void AMyCharacter::MulticastFireEffect_Implementation(FVector Loc){ // All clients: cosmetic effects UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlash, Loc); UGameplayStatics::PlaySoundAtLocation(GetWorld(), FireSound, Loc);}
Asset Management
Soft References & Async Loading
// Hard reference — always loaded in memoryUPROPERTY(EditDefaultsOnly)UStaticMesh* HardMesh; // loads when this object loads// Soft reference — path only, load on demandUPROPERTY(EditDefaultsOnly)TSoftObjectPtr<UStaticMesh> SoftMesh;UPROPERTY(EditDefaultsOnly)TSoftClassPtr<AEnemy> SoftEnemyClass;// Synchronous load (blocks game thread — avoid in gameplay)UStaticMesh* Mesh = SoftMesh.LoadSynchronous();// Async load (non-blocking — preferred)void AMyActor::LoadMeshAsync(){ TArray<FSoftObjectPath> AssetsToLoad; AssetsToLoad.Add(SoftMesh.ToSoftObjectPath()); FStreamableManager& Streamable = UAssetManager::GetStreamableManager(); Streamable.RequestAsyncLoad(AssetsToLoad, FStreamableDelegate::CreateUObject(this, &AMyActor::OnMeshLoaded));}void AMyActor::OnMeshLoaded(){ if (UStaticMesh* Mesh = SoftMesh.Get()) { GetStaticMeshComponent()->SetStaticMesh(Mesh); }}// Primary Asset — managed by Asset Manager// Override UPrimaryDataAsset for items, characters, levelsUCLASS()class UItemDefinition : public UPrimaryDataAsset{ GENERATED_BODY()public: virtual FPrimaryAssetId GetPrimaryAssetId() const override { return FPrimaryAssetId(TEXT("Item"), GetFName()); } UPROPERTY(EditDefaultsOnly) FText DisplayName; UPROPERTY(EditDefaultsOnly) TSoftObjectPtr<UTexture2D> Icon;};
Common Unreal C++ Patterns
Component Pattern
// Prefer composition over inheritance — add components for featuresUCLASS()class UHealthComponent : public UActorComponent{ GENERATED_BODY()public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health") float MaxHealth = 100.f; UPROPERTY(BlueprintReadOnly, Replicated, Category = "Health") float CurrentHealth; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnHealthChanged, float, NewHealth, float, Delta); UPROPERTY(BlueprintAssignable) FOnHealthChanged OnHealthChanged; DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath); UPROPERTY(BlueprintAssignable) FOnDeath OnDeath; virtual void BeginPlay() override; UFUNCTION(BlueprintCallable, Category = "Health") void ApplyDamage(float Amount); UFUNCTION(BlueprintCallable, Category = "Health") void Heal(float Amount); UFUNCTION(BlueprintPure, Category = "Health") float GetHealthPercent() const { return CurrentHealth / MaxHealth; } UFUNCTION(BlueprintPure, Category = "Health") bool IsDead() const { return CurrentHealth <= 0.f; }private: void SetHealth(float NewHealth);};// Usage — any actor can have health by adding this component// AEnemy, APlayer, ADestructibleProp — all use the same component