8# UE C++ Foundations
9
10You are an expert in Unreal Engine's C++ extensions and property system.
11
12## Context
13
14Read .agents/ue-project-context.md for engine version, coding conventions, and project-specific rules. Engine version matters: UE5 uses TObjectPtr<> where UE4 used raw UObject*, and GENERATED_BODY() replaces GENERATED_USTRUCT_BODY() in structs.
15
16## Before You Start
17
18Ask which area the user needs help with if unclear:
19- **Macros & Reflection** — UCLASS, UPROPERTY, UFUNCTION, USTRUCT, UENUM
20- **Containers** — TArray, TMap, TSet, TOptional
21- **Delegates** — static, dynamic, multicast, binding patterns
22- **Strings** — FName, FString, FText conversion and formatting
23- **Memory & GC** — TObjectPtr, TWeakObjectPtr, TSharedPtr, GC roots
24- **Logging** — UE_LOG, log categories, verbosity
25- **Subsystems** — GameInstance, World, LocalPlayer subsystems
26
27---
28
29## UObject Macros & Reflection
30
31All UE reflection macros require GENERATED_BODY() inside the class/struct and the corresponding .generated.h include.
32
33### UCLASS()
34
35| Specifier | Effect |
36|-----------|--------|
37| Blueprintable | Blueprint subclassing allowed |
38| BlueprintType | Usable as Blueprint variable |
39| Abstract | Cannot be instantiated |
40| NotBlueprintable | Blocks Blueprint subclassing |
41| Config=<Name> | Loads UPROPERTY(Config) from <Name>.ini |
42| Transient | Not saved/serialized |
43| Within=<OuterClass> | Outer must be of given type |
44
45```cpp
46UCLASS(Blueprintable, BlueprintType)
47class MYGAME_API UMyDataObject : public UObject
48{
49 GENERATED_BODY()
50public:
51 UMyDataObject();
52};
53```
54
55Full specifier list: [references/property-specifiers.md](references/property-specifiers.md).
56
57### UPROPERTY()
58
59```cpp
60UCLASS(Blueprintable)
61class MYGAME_API AMyCharacter : public ACharacter
62{
63 GENERATED_BODY()
64public:
65 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats")
66 float MaxHealth = 100.f;
67
68 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Stats")
69 float CurrentHealth;
70
71 UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Config")
72 int32 MaxLevel = 50;
73
74 UPROPERTY(ReplicatedUsing=OnRep_Health, Category="Replication")
75 float ReplicatedHealth;
76
77 UPROPERTY(Transient) // Not serialized; GC still tracks
78 TObjectPtr<UParticleSystemComponent> CachedFX;
79
80 UPROPERTY(SaveGame, BlueprintReadWrite, Category="Persistence")
81 int32 PlayerScore;
82
83 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats",
84 meta=(ClampMin="0.0", ClampMax="1.0"))
85 float DamageMultiplier = 1.f;
86
87 UFUNCTION()
88 void OnRep_Health();
89
90 virtual void GetLifetimeReplicatedProps(
91 TArray<FLifetimeProperty>& OutLifetimeProps) const override;
92};
93```
94
95### UFUNCTION()
96
97```cpp
98UFUNCTION(BlueprintCallable, Category="Actions")
99void PerformAttack(float Damage);
100
101UFUNCTION(BlueprintPure, Category="Queries")
102float GetHealthPercent() const;
103
104UFUNCTION(BlueprintNativeEvent, Category="Events") // C++ provides _Implementation
105void OnDamageTaken(float Amount);
106virtual void OnDamageTaken_Implementation(float Amount);
107
108UFUNCTION(BlueprintImplementableEvent, Category="Events") // Blueprint must implement
109void OnLevelUp(int32 NewLevel);
110
111UFUNCTION(Server, Reliable, WithValidation) // RPC: runs on server
112void ServerFireWeapon(FVector Origin, FVector Direction);
113void ServerFireWeapon_Implementation(FVector Origin, FVector Direction);
114bool ServerFireWeapon_Validate(FVector Origin, FVector Direction);
115
116UFUNCTION(Client, Reliable) // RPC: runs on owning client
117void ClientShowDamageNumber(float Amount);
118void ClientShowDamageNumber_Implementation(float Amount);
119
120UFUNCTION(NetMulticast, Reliable) // RPC: runs on all
121void MulticastPlayEffect(FVector Location);
122void MulticastPlayEffect_Implementation(FVector Location);
123
124UFUNCTION(Exec) // Console command (~ in-game)
125void DebugResetStats(); // Works on PC, Pawn, HUD, GM, GI, CheatManager
126```
127
128### USTRUCT() and UENUM()
129
130```cpp
131// UE5: always GENERATED_BODY() — never GENERATED_USTRUCT_BODY()
132USTRUCT(BlueprintType)
133struct MYGAME_API FWeaponStats
134{
135 GENERATED_BODY()
136 UPROPERTY(EditAnywhere, BlueprintReadWrite) float BaseDamage = 10.f;
137 UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.5f;
138};
139
140// DataTable row
141USTRUCT(BlueprintType)
142struct MYGAME_API FEnemyTableRow : public FTableRowBase
143{
144 GENERATED_BODY()
145 UPROPERTY(EditAnywhere, BlueprintReadWrite) FName EnemyID;
146 UPROPERTY(EditAnywhere, BlueprintReadWrite) TSoftClassPtr<AActor> SpawnClass;
147};
148
149UENUM(BlueprintType)
150enum class EWeaponState : uint8
151{
152 Idle UMETA(DisplayName="Idle"),
153 Firing UMETA(DisplayName="Firing"),
154 Reloading UMETA(DisplayName="Reloading"),
155};
156```
157
158---
159
160## UE Containers
161
162See [references/container-patterns.md](references/container-patterns.md) for full API and performance guide.
163
164### TArray — Ordered Dynamic Array
165
166```cpp
167TArray<FString> Names;
168Names.Add(TEXT("Alpha"));
169Names.Emplace(TEXT("Beta")); // Construct in-place (avoids copy)
170Names.Reserve(100); // Pre-allocate
171
172FString First = Names[0];
173bool bHas = Names.Contains(TEXT("Alpha"));
174int32 Idx = Names.Find(TEXT("Beta")); // INDEX_NONE if absent
175FString* Ptr = Names.FindByPredicate([](const FString& S){ return S.StartsWith(TEXT("A")); });
176
177Names.Sort([](const FString& A, const FString& B){ return A.Len() < B.Len(); });
178Names.Remove(TEXT("Alpha")); // Order-preserving O(n)
179Names.RemoveAtSwap(0); // Fast O(1), destroys order
180
181for (const FString& N : Names) { /* do NOT add/remove during ranged-for */ }
182for (int32 i = Names.Num()-1; i >= 0; --i) { if (Names[i].IsEmpty()) Names.RemoveAt(i); }
183```
184
185### TMap — Hash Map
186
187```cpp
188TMap<FName, int32> ItemCounts;
189ItemCounts.Add(FName("Sword"), 3);
190
191int32& Ref = ItemCounts.FindOrAdd(FName("Sword")); // Insert default if absent
192int32* Ptr = ItemCounts.Find(FName("Axe")); // nullptr if absent
193bool bHas = ItemCounts.Contains(FName("Shield"));
194ItemCounts.Remove(FName("Shield"));
195
196for (const TPair<FName, int32>& Pair : ItemCounts) { /* ... */ }
197```
198
199### TSet — Hash Set
200
201```cpp
202TSet<FName> Tags;
203Tags.Add(FName("Flying"));
204bool bFlying = Tags.Contains(FName("Flying"));
205TSet<FName> Intersect = Tags.Intersect(OtherTags);
206TSet<FName> Union = Tags.Union(OtherTags);
207```
208
209### TOptional
210
211```cpp
212TOptional<float> MaybeHP;
213if (MaybeHP.IsSet()) { float H = MaybeHP.GetValue(); }
214float Safe = MaybeHP.Get(0.f); // Default if not set
215MaybeHP = 75.f;
216MaybeHP.Reset();
217```
218
219### TVariant
220
221```cpp
222// Type-safe tagged union — avoids unsafe casts
223TVariant<int32, float, FString> Value;
224Value.Set<FString>(TEXT("Hello"));
225
226if (Value.IsType<FString>())
227{
228 const FString& Str = Value.Get<FString>();
229}
230
231// Visit — use explicit overloads; LexToString(V) fails when V is FString.
232Visit(TOverloaded{
233 [](int32 V) { UE_LOG(LogTemp, Log, TEXT("%d"), V); },
234 [](float V) { UE_LOG(LogTemp, Log, TEXT("%f"), V); },
235 [](const FString& V) { UE_LOG(LogTemp, Log, TEXT("%s"), *V); },
236}, Value);
237```
238
239---
240
241## Delegates
242
243See [references/delegate-patterns.md](references/delegate-patterns.md) for all declaration macros and binding methods.
244
245### Choosing the Right Type
246
247| Type | Bindings | Blueprint | When to Use |
248|------|----------|-----------|-------------|
249| DECLARE_DELEGATE | 1 | No | Internal single-owner callback |
250| DECLARE_MULTICAST_DELEGATE | N | No | Internal multi-listener events |
251| DECLARE_DYNAMIC_DELEGATE | 1 | Yes | Blueprint-assignable single callback |
252| DECLARE_DYNAMIC_MULTICAST_DELEGATE | N | Yes | Blueprint-bindable events (most common) |
253
254### Declaration, Binding, Invocation
255
256```cpp
257// File scope (before UCLASS)
258DECLARE_DELEGATE_OneParam(FOnItemPickedUp, AActor*);
259DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, float);
260DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangedDynamic, float, CurrentHealth, float, MaxHealth);
261
262UCLASS()
263class AMyActor : public AActor
264{
265 GENERATED_BODY()
266public:
267 UPROPERTY(BlueprintAssignable, Category="Events")
268 FOnHealthChangedDynamic OnHealthChanged; // Dynamic multicast in UPROPERTY
269};
270
271// Static single delegate
272FOnItemPickedUp D;
273D.BindUObject(this, &AMyCharacter::HandlePickup);
274D.BindLambda([this](AActor* Item){ UE_LOG(LogTemp, Log, TEXT("%s"), *Item->GetName()); });
275D.ExecuteIfBound(SomeActor);
276// Multicast: add/broadcast/remove
277FDelegateHandle H = HealthDelegate.AddUObject(this, &AMyHUD::OnHealthChanged);
278HealthDelegate.Broadcast(75.f, 100.f);
279HealthDelegate.Remove(H);
280// Dynamic multicast
281OnHealthChanged.AddDynamic(this, &AMyCharacter::HandleHealthChange);
282OnHealthChanged.RemoveDynamic(this, &AMyCharacter::HandleHealthChange);
283OnHealthChanged.Broadcast(75.f, 100.f);
284```
285
286---
287
288## String Types
289
290| Type | Use For | Comparison | Mutable |
291|------|---------|-----------|---------|
292| FName | Identifiers, asset names, tags | O(1) integer | No |
293| FString | General-purpose strings, file paths | O(n) | Yes |
294| FText | Player-visible display strings | — | No |
295
296```cpp
297// FName — global name table, case-insensitive O(1) compare
298FName Tag("WeaponTag_Rifle");
299FString S = Tag.ToString();
300FName N = FName(*S);
301
302// FString — heap string, Printf for formatting
303FString Msg = FString::Printf(TEXT("HP: %.1f"), Health);
304UE_LOG(LogTemp, Log, TEXT("%s"), *Msg); // * dereferences to TCHAR*
305int32 Num = FCString::Atoi(*FString("42"));
306
307// FText — localized display text
308// LOCTEXT requires a namespace defined in the same translation unit:
309#define LOCTEXT_NAMESPACE "MyGame"
310FText Label = LOCTEXT("Key", "Assault Rifle");
311FText Fmt = FText::Format(LOCTEXT("HP", "HP: {0}/{1}"),
312 FText::AsNumber(Cur), FText::AsNumber(Max));
313#undef LOCTEXT_NAMESPACE // Or: NSLOCTEXT("MyGame", "Key", "...") without a define
314```
315
316**Conversion:** Name.ToString() → FString, FName(*Str) ← FString, FText::FromString(Str), Text.ToString().
317
318---
319
320## Memory & Garbage Collection
321
322UE's GC tracks every UObject* reachable from a root. Unreachable objects are destroyed.
323
324```cpp
325// UE5: TObjectPtr<> for UPROPERTY member UObject pointers
326UPROPERTY()
327TObjectPtr<UStaticMeshComponent> MeshComp; // GC-tracked, lazy-load aware
328// Without UPROPERTY — invisible to GC, pointer may dangle
329UMyObject* UnsafePtr; // BAD
330// TWeakObjectPtr — non-owning, safe (becomes null after GC)
331TWeakObjectPtr<AMyActor> WeakRef;
332if (WeakRef.IsValid()) { WeakRef->DoSomething(); }
333// TSharedPtr/TWeakPtr — for non-UObject plain C++ types ONLY
334TSharedPtr<FMyData> Data = MakeShared<FMyData>();
335TWeakPtr<FMyData> Weak = Data;
336if (TSharedPtr<FMyData> Pinned = Weak.Pin()) { Pinned->Process(); }
337// NEVER use TSharedPtr for UObject-derived types
338// GC root: AddToRoot (use sparingly)
339UMyObject* Obj = NewObject<UMyObject>();
340Obj->AddToRoot();
341// ...
342Obj->RemoveFromRoot();
343// FGCObject: preferred for non-UObject C++ classes holding UObject refs
344// UE 5.3+: use TObjectPtr<> — raw UObject* crashes with incremental GC.
345class FMyManager : public FGCObject
346{
347public:
348 virtual void AddReferencedObjects(FReferenceCollector& Collector) override
349 {
350 Collector.AddReferencedObject(ManagedObject);
351 }
352 virtual FString GetReferencerName() const override { return TEXT("FMyManager"); }
353private:
354 TObjectPtr<UMyObject> ManagedObject = nullptr;
355};
356```
357
358---
359
360## Logging
361
362```cpp
363// MyGameLog.h / .cpp
364DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);
365DEFINE_LOG_CATEGORY(LogMyGame);
366
367// Single-file: DEFINE_LOG_CATEGORY_STATIC(LogLocal, Log, All);
368
369UE_LOG(LogMyGame, Log, TEXT("Loaded: %s"), *LevelName);
370UE_LOG(LogMyGame, Warning, TEXT("HP low: %.1f"), Health);
371UE_LOG(LogMyGame, Error, TEXT("Spawn failed: %s"), *ClassName);
372UE_CLOG(Health < 0.f, LogMyGame, Error, TEXT("Negative HP: %.1f"), Health);
373```
374
375| Verbosity | Visible | When |
376|-----------|---------|------|
377| Fatal | Always | Crash-level |
378| Error | Always | Operation failed |
379| Warning | Always | Unexpected but recoverable |
380| Log | Non-shipping | Standard trace |
381| Verbose | -LogCmds | Fine-grained trace |
382
383---
384
385## Subsystems
386
387Auto-registered singletons — no manual AddToRoot needed.
388
389| Subsystem | Owner | Persists Level Load | Per Player |
390|-----------|-------|---------------------|------------|
391| UGameInstanceSubsystem | UGameInstance | Yes | No |
392| UWorldSubsystem | UWorld | No | No |
393| ULocalPlayerSubsystem | ULocalPlayer | Yes | Yes |
394| UEngineSubsystem | UEngine | Yes (whole session) | No |
395
396```cpp
397UCLASS()
398class MYGAME_API UInventorySubsystem : public UGameInstanceSubsystem
399{
400 GENERATED_BODY()
401public:
402 virtual void Initialize(FSubsystemCollectionBase& Collection) override;
403 virtual void Deinitialize() override;
404
405 UFUNCTION(BlueprintCallable) void AddItem(FName ItemID, int32 Count);
406private:
407 TMap<FName, int32> Inventory;
408};
409
410// Access — each subsystem type has a different accessor
411UInventorySubsystem* Inv = GetGameInstance()->GetSubsystem<UInventorySubsystem>();
412USpawnSubsystem* Sp = GetWorld()->GetSubsystem<USpawnSubsystem>();
413UUIStateSubsystem* UI = GetLocalPlayer()->GetSubsystem<UUIStateSubsystem>();
414UMyEngineSubsystem* ES = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();
415```
416
417Subsystems have Initialize() and Deinitialize() -- override for setup/teardown. UGameInstanceSubsystem persists across map changes; UWorldSubsystem reinitializes per world. Call GetSubsystem<T>() via the owning context (GetGameInstance(), GetWorld(), GetLocalPlayer()).
418
419---
420
421## Replicated Properties
422
423Both UPROPERTY specifier AND GetLifetimeReplicatedProps are required:
424
425```cpp
426UPROPERTY(ReplicatedUsing = OnRep_Health)
427float Health;
428
429UFUNCTION()
430void OnRep_Health(); // Called on clients when server updates Health
431
432void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutProps) const
433{
434 Super::GetLifetimeReplicatedProps(OutProps);
435 DOREPLIFETIME_CONDITION(AMyActor, Health, COND_OwnerOnly);
436 // COND_None, COND_OwnerOnly, COND_SkipOwner, COND_SimulatedOnly, COND_InitialOnly
437}
438```
439
440---
441
442## Conditional Compilation Guards
443
444```cpp
445#if WITH_EDITOR
446// Editor-only properties — stripped from shipping builds
447UPROPERTY(EditAnywhere, Category="Debug")
448bool bShowDebugSpheres = false;
449
450virtual void PostEditChangeProperty(FPropertyChangedEvent& E) override;
451#endif
452
453#if !UE_BUILD_SHIPPING
454// Available in Development + Debug, stripped from Shipping
455void DrawDebugInfo();
456#endif
457```
458
459---
460
461## Common Mistakes
462
463**Raw UObject* member without UPROPERTY — dangling pointer:**
464```cpp
465UMyObject* Obj; // BAD — GC invisible
466UPROPERTY() TObjectPtr<UMyObject> Obj; // GOOD
467```
468
469**Modify TArray during ranged-for — undefined behavior:**
470```cpp
471for (const AActor* A : Actors) { Actors.Remove(A); } // CRASH
472for (int32 i = Actors.Num()-1; i >= 0; --i) { if (ShouldRemove(Actors[i])) Actors.RemoveAt(i); }
473```
474
475**TSharedPtr on a UObject — GC + refcount conflict:**
476```cpp
477TSharedPtr<UMyObject> P = MakeShared<UMyObject>(); // BAD — leaks or double-free
478UPROPERTY() TObjectPtr<UMyObject> P; // GOOD
479```
480
481**Missing GetLifetimeReplicatedProps:**
482```cpp
483void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
484{
485 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
486 DOREPLIFETIME(AMyActor, ReplicatedHealth);
487 DOREPLIFETIME_CONDITION(AMyActor, TeamScore, COND_OwnerOnly);
488}
489```
490
491**GENERATED_USTRUCT_BODY() in UE5 structs — use GENERATED_BODY() instead.**
492
493**AddDynamic with a non-UFUNCTION — compile error or crash at runtime.**
494
495---
496
497## Related Skills
498
499- **ue-module-build-system** — Build.cs, module dependencies, include paths, PCH configuration
500- **ue-actor-component-architecture** — AActor/UActorComponent lifecycle, spawning, tick groups, component setup
501