ue-cpp-foundations
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE C++ Foundations
UE C++ 基础
You are an expert in Unreal Engine's C++ extensions and property system.
您是Unreal Engine C++扩展与属性系统方面的专家。
Context
上下文
Read for engine version, coding conventions, and project-specific rules. Engine version matters: UE5 uses where UE4 used raw , and replaces in structs.
.agents/ue-project-context.mdTObjectPtr<>UObject*GENERATED_BODY()GENERATED_USTRUCT_BODY()请阅读了解引擎版本、编码规范和项目特定规则。引擎版本十分重要:UE5使用,而UE4使用原生;结构体中替代了。
.agents/ue-project-context.mdTObjectPtr<>UObject*GENERATED_BODY()GENERATED_USTRUCT_BODY()Before You Start
开始之前
Ask which area the user needs help with if unclear:
- Macros & Reflection — UCLASS, UPROPERTY, UFUNCTION, USTRUCT, UENUM
- Containers — TArray, TMap, TSet, TOptional
- Delegates — static, dynamic, multicast, binding patterns
- Strings — FName, FString, FText conversion and formatting
- Memory & GC — TObjectPtr, TWeakObjectPtr, TSharedPtr, GC roots
- Logging — UE_LOG, log categories, verbosity
- Subsystems — GameInstance, World, LocalPlayer subsystems
如果不清楚用户需求,请询问他们需要帮助的领域:
- 宏与反射 — UCLASS、UPROPERTY、UFUNCTION、USTRUCT、UENUM
- 容器 — TArray、TMap、TSet、TOptional
- 委托 — 静态、动态、多播、绑定模式
- 字符串 — FName、FString、FText的转换与格式化
- 内存与垃圾回收 — TObjectPtr、TWeakObjectPtr、TSharedPtr、GC根对象
- 日志 — UE_LOG、日志分类、日志级别
- 子系统 — GameInstance、World、LocalPlayer子系统
UObject Macros & Reflection
UObject 宏与反射
All UE reflection macros require inside the class/struct and the corresponding include.
GENERATED_BODY().generated.h所有UE反射宏都要求在类/结构体内部添加,并包含对应的头文件。
GENERATED_BODY().generated.hUCLASS()
UCLASS()
| Specifier | Effect |
|---|---|
| Blueprint subclassing allowed |
| Usable as Blueprint variable |
| Cannot be instantiated |
| Blocks Blueprint subclassing |
| Loads UPROPERTY(Config) from |
| Not saved/serialized |
| Outer must be of given type |
cpp
UCLASS(Blueprintable, BlueprintType)
class MYGAME_API UMyDataObject : public UObject
{
GENERATED_BODY()
public:
UMyDataObject();
};Full specifier list: references/property-specifiers.md.
| 说明符 | 作用 |
|---|---|
| 允许创建Blueprint子类 |
| 可作为Blueprint变量使用 |
| 无法实例化 |
| 禁止创建Blueprint子类 |
| 从 |
| 不保存/序列化 |
| 外部对象必须为指定类型 |
cpp
UCLASS(Blueprintable, BlueprintType)
class MYGAME_API UMyDataObject : public UObject
{
GENERATED_BODY()
public:
UMyDataObject();
};完整说明符列表:references/property-specifiers.md。
UPROPERTY()
UPROPERTY()
cpp
UCLASS(Blueprintable)
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats")
float MaxHealth = 100.f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Stats")
float CurrentHealth;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Config")
int32 MaxLevel = 50;
UPROPERTY(ReplicatedUsing=OnRep_Health, Category="Replication")
float ReplicatedHealth;
UPROPERTY(Transient) // Not serialized; GC still tracks
TObjectPtr<UParticleSystemComponent> CachedFX;
UPROPERTY(SaveGame, BlueprintReadWrite, Category="Persistence")
int32 PlayerScore;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats",
meta=(ClampMin="0.0", ClampMax="1.0"))
float DamageMultiplier = 1.f;
UFUNCTION()
void OnRep_Health();
virtual void GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};cpp
UCLASS(Blueprintable)
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats")
float MaxHealth = 100.f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Stats")
float CurrentHealth;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Config")
int32 MaxLevel = 50;
UPROPERTY(ReplicatedUsing=OnRep_Health, Category="Replication")
float ReplicatedHealth;
UPROPERTY(Transient) // 不序列化;仍被GC追踪
TObjectPtr<UParticleSystemComponent> CachedFX;
UPROPERTY(SaveGame, BlueprintReadWrite, Category="Persistence")
int32 PlayerScore;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stats",
meta=(ClampMin="0.0", ClampMax="1.0"))
float DamageMultiplier = 1.f;
UFUNCTION()
void OnRep_Health();
virtual void GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};UFUNCTION()
UFUNCTION()
cpp
UFUNCTION(BlueprintCallable, Category="Actions")
void PerformAttack(float Damage);
UFUNCTION(BlueprintPure, Category="Queries")
float GetHealthPercent() const;
UFUNCTION(BlueprintNativeEvent, Category="Events") // C++ provides _Implementation
void OnDamageTaken(float Amount);
virtual void OnDamageTaken_Implementation(float Amount);
UFUNCTION(BlueprintImplementableEvent, Category="Events") // Blueprint must implement
void OnLevelUp(int32 NewLevel);
UFUNCTION(Server, Reliable, WithValidation) // RPC: runs on server
void ServerFireWeapon(FVector Origin, FVector Direction);
void ServerFireWeapon_Implementation(FVector Origin, FVector Direction);
bool ServerFireWeapon_Validate(FVector Origin, FVector Direction);
UFUNCTION(Client, Reliable) // RPC: runs on owning client
void ClientShowDamageNumber(float Amount);
void ClientShowDamageNumber_Implementation(float Amount);
UFUNCTION(NetMulticast, Reliable) // RPC: runs on all
void MulticastPlayEffect(FVector Location);
void MulticastPlayEffect_Implementation(FVector Location);
UFUNCTION(Exec) // Console command (~ in-game)
void DebugResetStats(); // Works on PC, Pawn, HUD, GM, GI, CheatManagercpp
UFUNCTION(BlueprintCallable, Category="Actions")
void PerformAttack(float Damage);
UFUNCTION(BlueprintPure, Category="Queries")
float GetHealthPercent() const;
UFUNCTION(BlueprintNativeEvent, Category="Events") // C++提供_Implementation实现
void OnDamageTaken(float Amount);
virtual void OnDamageTaken_Implementation(float Amount);
UFUNCTION(BlueprintImplementableEvent, Category="Events") // 必须由Blueprint实现
void OnLevelUp(int32 NewLevel);
UFUNCTION(Server, Reliable, WithValidation) // RPC:在服务器运行
void ServerFireWeapon(FVector Origin, FVector Direction);
void ServerFireWeapon_Implementation(FVector Origin, FVector Direction);
bool ServerFireWeapon_Validate(FVector Origin, FVector Direction);
UFUNCTION(Client, Reliable) // RPC:在所属客户端运行
void ClientShowDamageNumber(float Amount);
void ClientShowDamageNumber_Implementation(float Amount);
UFUNCTION(NetMulticast, Reliable) // RPC:在所有端运行
void MulticastPlayEffect(FVector Location);
void MulticastPlayEffect_Implementation(FVector Location);
UFUNCTION(Exec) // 控制台命令(游戏内按~调用)
void DebugResetStats(); // 适用于PC、Pawn、HUD、GM、GI、CheatManagerUSTRUCT() and UENUM()
USTRUCT() 和 UENUM()
cpp
// UE5: always GENERATED_BODY() — never GENERATED_USTRUCT_BODY()
USTRUCT(BlueprintType)
struct MYGAME_API FWeaponStats
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) float BaseDamage = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.5f;
};
// DataTable row
USTRUCT(BlueprintType)
struct MYGAME_API FEnemyTableRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FName EnemyID;
UPROPERTY(EditAnywhere, BlueprintReadWrite) TSoftClassPtr<AActor> SpawnClass;
};
UENUM(BlueprintType)
enum class EWeaponState : uint8
{
Idle UMETA(DisplayName="Idle"),
Firing UMETA(DisplayName="Firing"),
Reloading UMETA(DisplayName="Reloading"),
};cpp
// UE5:始终使用GENERATED_BODY() — 绝不要使用GENERATED_USTRUCT_BODY()
USTRUCT(BlueprintType)
struct MYGAME_API FWeaponStats
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) float BaseDamage = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.5f;
};
// DataTable行结构体
USTRUCT(BlueprintType)
struct MYGAME_API FEnemyTableRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FName EnemyID;
UPROPERTY(EditAnywhere, BlueprintReadWrite) TSoftClassPtr<AActor> SpawnClass;
};
UENUM(BlueprintType)
enum class EWeaponState : uint8
{
Idle UMETA(DisplayName="Idle"),
Firing UMETA(DisplayName="Firing"),
Reloading UMETA(DisplayName="Reloading"),
};UE Containers
UE 容器
See references/container-patterns.md for full API and performance guide.
完整API和性能指南请查看references/container-patterns.md。
TArray — Ordered Dynamic Array
TArray — 有序动态数组
cpp
TArray<FString> Names;
Names.Add(TEXT("Alpha"));
Names.Emplace(TEXT("Beta")); // Construct in-place (avoids copy)
Names.Reserve(100); // Pre-allocate
FString First = Names[0];
bool bHas = Names.Contains(TEXT("Alpha"));
int32 Idx = Names.Find(TEXT("Beta")); // INDEX_NONE if absent
FString* Ptr = Names.FindByPredicate([](const FString& S){ return S.StartsWith(TEXT("A")); });
Names.Sort([](const FString& A, const FString& B){ return A.Len() < B.Len(); });
Names.Remove(TEXT("Alpha")); // Order-preserving O(n)
Names.RemoveAtSwap(0); // Fast O(1), destroys order
for (const FString& N : Names) { /* do NOT add/remove during ranged-for */ }
for (int32 i = Names.Num()-1; i >= 0; --i) { if (Names[i].IsEmpty()) Names.RemoveAt(i); }cpp
TArray<FString> Names;
Names.Add(TEXT("Alpha"));
Names.Emplace(TEXT("Beta")); // 原地构造(避免拷贝)
Names.Reserve(100); // 预分配内存
FString First = Names[0];
bool bHas = Names.Contains(TEXT("Alpha"));
int32 Idx = Names.Find(TEXT("Beta")); // 不存在则返回INDEX_NONE
FString* Ptr = Names.FindByPredicate([](const FString& S){ return S.StartsWith(TEXT("A")); });
Names.Sort([](const FString& A, const FString& B){ return A.Len() < B.Len(); });
Names.Remove(TEXT("Alpha")); // 保持顺序,O(n)复杂度
Names.RemoveAtSwap(0); // 快速O(1)复杂度,破坏顺序
for (const FString& N : Names) { /* 范围for循环中请勿添加/移除元素 */ }
for (int32 i = Names.Num()-1; i >= 0; --i) { if (Names[i].IsEmpty()) Names.RemoveAt(i); }TMap — Hash Map
TMap — 哈希表
cpp
TMap<FName, int32> ItemCounts;
ItemCounts.Add(FName("Sword"), 3);
int32& Ref = ItemCounts.FindOrAdd(FName("Sword")); // Insert default if absent
int32* Ptr = ItemCounts.Find(FName("Axe")); // nullptr if absent
bool bHas = ItemCounts.Contains(FName("Shield"));
ItemCounts.Remove(FName("Shield"));
for (const TPair<FName, int32>& Pair : ItemCounts) { /* ... */ }cpp
TMap<FName, int32> ItemCounts;
ItemCounts.Add(FName("Sword"), 3);
int32& Ref = ItemCounts.FindOrAdd(FName("Sword")); // 不存在则插入默认值
int32* Ptr = ItemCounts.Find(FName("Axe")); // 不存在则返回nullptr
bool bHas = ItemCounts.Contains(FName("Shield"));
ItemCounts.Remove(FName("Shield"));
for (const TPair<FName, int32>& Pair : ItemCounts) { /* ... */ }TSet — Hash Set
TSet — 哈希集合
cpp
TSet<FName> Tags;
Tags.Add(FName("Flying"));
bool bFlying = Tags.Contains(FName("Flying"));
TSet<FName> Intersect = Tags.Intersect(OtherTags);
TSet<FName> Union = Tags.Union(OtherTags);cpp
TSet<FName> Tags;
Tags.Add(FName("Flying"));
bool bFlying = Tags.Contains(FName("Flying"));
TSet<FName> Intersect = Tags.Intersect(OtherTags);
TSet<FName> Union = Tags.Union(OtherTags);TOptional
TOptional
cpp
TOptional<float> MaybeHP;
if (MaybeHP.IsSet()) { float H = MaybeHP.GetValue(); }
float Safe = MaybeHP.Get(0.f); // Default if not set
MaybeHP = 75.f;
MaybeHP.Reset();cpp
TOptional<float> MaybeHP;
if (MaybeHP.IsSet()) { float H = MaybeHP.GetValue(); }
float Safe = MaybeHP.Get(0.f); // 未设置则返回默认值
MaybeHP = 75.f;
MaybeHP.Reset();TVariant
TVariant
cpp
// Type-safe tagged union — avoids unsafe casts
TVariant<int32, float, FString> Value;
Value.Set<FString>(TEXT("Hello"));
if (Value.IsType<FString>())
{
const FString& Str = Value.Get<FString>();
}
// Visit — use explicit overloads; LexToString(V) fails when V is FString.
Visit(TOverloaded{
[](int32 V) { UE_LOG(LogTemp, Log, TEXT("%d"), V); },
[](float V) { UE_LOG(LogTemp, Log, TEXT("%f"), V); },
[](const FString& V) { UE_LOG(LogTemp, Log, TEXT("%s"), *V); },
}, Value);cpp
// 类型安全的标记联合 — 避免不安全的类型转换
TVariant<int32, float, FString> Value;
Value.Set<FString>(TEXT("Hello"));
if (Value.IsType<FString>())
{
const FString& Str = Value.Get<FString>();
}
// Visit — 使用显式重载;当V是FString时LexToString(V)会失败。
Visit(TOverloaded{
[](int32 V) { UE_LOG(LogTemp, Log, TEXT("%d"), V); },
[](float V) { UE_LOG(LogTemp, Log, TEXT("%f"), V); },
[](const FString& V) { UE_LOG(LogTemp, Log, TEXT("%s"), *V); },
}, Value);Delegates
委托
See references/delegate-patterns.md for all declaration macros and binding methods.
所有声明宏和绑定方法请查看references/delegate-patterns.md。
Choosing the Right Type
选择合适的委托类型
| Type | Bindings | Blueprint | When to Use |
|---|---|---|---|
| 1 | No | Internal single-owner callback |
| N | No | Internal multi-listener events |
| 1 | Yes | Blueprint-assignable single callback |
| N | Yes | Blueprint-bindable events (most common) |
| 类型 | 绑定数量 | 支持Blueprint | 使用场景 |
|---|---|---|---|
| 1 | 否 | 内部单一所有者回调 |
| N | 否 | 内部多监听者事件 |
| 1 | 是 | 可由Blueprint赋值的单一回调 |
| N | 是 | 可由Blueprint绑定的事件(最常用) |
Declaration, Binding, Invocation
声明、绑定与调用
cpp
// File scope (before UCLASS)
DECLARE_DELEGATE_OneParam(FOnItemPickedUp, AActor*);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, float);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangedDynamic, float, CurrentHealth, float, MaxHealth);
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable, Category="Events")
FOnHealthChangedDynamic OnHealthChanged; // Dynamic multicast in UPROPERTY
};
// Static single delegate
FOnItemPickedUp D;
D.BindUObject(this, &AMyCharacter::HandlePickup);
D.BindLambda([this](AActor* Item){ UE_LOG(LogTemp, Log, TEXT("%s"), *Item->GetName()); });
D.ExecuteIfBound(SomeActor);
// Multicast: add/broadcast/remove
FDelegateHandle H = HealthDelegate.AddUObject(this, &AMyHUD::OnHealthChanged);
HealthDelegate.Broadcast(75.f, 100.f);
HealthDelegate.Remove(H);
// Dynamic multicast
OnHealthChanged.AddDynamic(this, &AMyCharacter::HandleHealthChange);
OnHealthChanged.RemoveDynamic(this, &AMyCharacter::HandleHealthChange);
OnHealthChanged.Broadcast(75.f, 100.f);cpp
// 文件作用域(UCLASS之前)
DECLARE_DELEGATE_OneParam(FOnItemPickedUp, AActor*);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, float);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangedDynamic, float, CurrentHealth, float, MaxHealth);
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable, Category="Events")
FOnHealthChangedDynamic OnHealthChanged; // UPROPERTY中的动态多播委托
};
// 静态单一委托
FOnItemPickedUp D;
D.BindUObject(this, &AMyCharacter::HandlePickup);
D.BindLambda([this](AActor* Item){ UE_LOG(LogTemp, Log, TEXT("%s"), *Item->GetName()); });
D.ExecuteIfBound(SomeActor);
// 多播委托:添加/广播/移除
FDelegateHandle H = HealthDelegate.AddUObject(this, &AMyHUD::OnHealthChanged);
HealthDelegate.Broadcast(75.f, 100.f);
HealthDelegate.Remove(H);
// 动态多播委托
OnHealthChanged.AddDynamic(this, &AMyCharacter::HandleHealthChange);
OnHealthChanged.RemoveDynamic(this, &AMyCharacter::HandleHealthChange);
OnHealthChanged.Broadcast(75.f, 100.f);String Types
字符串类型
| Type | Use For | Comparison | Mutable |
|---|---|---|---|
| Identifiers, asset names, tags | O(1) integer | No |
| General-purpose strings, file paths | O(n) | Yes |
| Player-visible display strings | — | No |
cpp
// FName — global name table, case-insensitive O(1) compare
FName Tag("WeaponTag_Rifle");
FString S = Tag.ToString();
FName N = FName(*S);
// FString — heap string, Printf for formatting
FString Msg = FString::Printf(TEXT("HP: %.1f"), Health);
UE_LOG(LogTemp, Log, TEXT("%s"), *Msg); // * dereferences to TCHAR*
int32 Num = FCString::Atoi(*FString("42"));
// FText — localized display text
// LOCTEXT requires a namespace defined in the same translation unit:
#define LOCTEXT_NAMESPACE "MyGame"
FText Label = LOCTEXT("Key", "Assault Rifle");
FText Fmt = FText::Format(LOCTEXT("HP", "HP: {0}/{1}"),
FText::AsNumber(Cur), FText::AsNumber(Max));
#undef LOCTEXT_NAMESPACE // Or: NSLOCTEXT("MyGame", "Key", "...") without a defineConversion: → FString, ← FString, , .
Name.ToString()FName(*Str)FText::FromString(Str)Text.ToString()| 类型 | 使用场景 | 比较复杂度 | 是否可变 |
|---|---|---|---|
| 标识符、资源名称、标签 | O(1)整数比较 | 否 |
| 通用字符串、文件路径 | O(n) | 是 |
| 玩家可见的显示字符串 | — | 否 |
cpp
// FName — 全局名称表,大小写不敏感,O(1)比较
FName Tag("WeaponTag_Rifle");
FString S = Tag.ToString();
FName N = FName(*S);
// FString — 堆字符串,使用Printf格式化
FString Msg = FString::Printf(TEXT("HP: %.1f"), Health);
UE_LOG(LogTemp, Log, TEXT("%s"), *Msg); // *解引用为TCHAR*
int32 Num = FCString::Atoi(*FString("42"));
// FText — 本地化显示文本
// LOCTEXT需要在同一翻译单元中定义命名空间:
#define LOCTEXT_NAMESPACE "MyGame"
FText Label = LOCTEXT("Key", "Assault Rifle");
FText Fmt = FText::Format(LOCTEXT("HP", "HP: {0}/{1}"),
FText::AsNumber(Cur), FText::AsNumber(Max));
#undef LOCTEXT_NAMESPACE // 或者:使用NSLOCTEXT("MyGame", "Key", "...")无需定义命名空间转换: → FString, ← FString,,。
Name.ToString()FName(*Str)FText::FromString(Str)Text.ToString()Memory & Garbage Collection
内存与垃圾回收
UE's GC tracks every reachable from a root. Unreachable objects are destroyed.
UObject*cpp
// UE5: TObjectPtr<> for UPROPERTY member UObject pointers
UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComp; // GC-tracked, lazy-load aware
// Without UPROPERTY — invisible to GC, pointer may dangle
UMyObject* UnsafePtr; // BAD
// TWeakObjectPtr — non-owning, safe (becomes null after GC)
TWeakObjectPtr<AMyActor> WeakRef;
if (WeakRef.IsValid()) { WeakRef->DoSomething(); }
// TSharedPtr/TWeakPtr — for non-UObject plain C++ types ONLY
TSharedPtr<FMyData> Data = MakeShared<FMyData>();
TWeakPtr<FMyData> Weak = Data;
if (TSharedPtr<FMyData> Pinned = Weak.Pin()) { Pinned->Process(); }
// NEVER use TSharedPtr for UObject-derived types
// GC root: AddToRoot (use sparingly)
UMyObject* Obj = NewObject<UMyObject>();
Obj->AddToRoot();
// ...
Obj->RemoveFromRoot();
// FGCObject: preferred for non-UObject C++ classes holding UObject refs
// UE 5.3+: use TObjectPtr<> — raw UObject* crashes with incremental GC.
class FMyManager : public FGCObject
{
public:
virtual void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(ManagedObject);
}
virtual FString GetReferencerName() const override { return TEXT("FMyManager"); }
private:
TObjectPtr<UMyObject> ManagedObject = nullptr;
};UE的垃圾回收器会追踪所有可从根对象访问到的。不可达对象会被销毁。
UObject*cpp
// UE5:UPROPERTY成员UObject指针使用TObjectPtr<>
UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComp; // 被GC追踪,支持懒加载感知
// 没有UPROPERTY — 对GC不可见,指针可能悬空
UMyObject* UnsafePtr; // 错误用法
// TWeakObjectPtr — 非拥有式引用,安全(GC后变为null)
TWeakObjectPtr<AMyActor> WeakRef;
if (WeakRef.IsValid()) { WeakRef->DoSomething(); }
// TSharedPtr/TWeakPtr — 仅用于非UObject的普通C++类型
TSharedPtr<FMyData> Data = MakeShared<FMyData>();
TWeakPtr<FMyData> Weak = Data;
if (TSharedPtr<FMyData> Pinned = Weak.Pin()) { Pinned->Process(); }
// 绝不要对UObject派生类型使用TSharedPtr
// GC根对象:AddToRoot(谨慎使用)
UMyObject* Obj = NewObject<UMyObject>();
Obj->AddToRoot();
// ...
Obj->RemoveFromRoot();
// FGCObject:持有UObject引用的非UObject C++类的首选方案
// UE 5.3+:使用TObjectPtr<> — 原生UObject*在增量GC下会崩溃。
class FMyManager : public FGCObject
{
public:
virtual void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(ManagedObject);
}
virtual FString GetReferencerName() const override { return TEXT("FMyManager"); }
private:
TObjectPtr<UMyObject> ManagedObject = nullptr;
};Logging
日志
cpp
// MyGameLog.h / .cpp
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);
DEFINE_LOG_CATEGORY(LogMyGame);
// Single-file: DEFINE_LOG_CATEGORY_STATIC(LogLocal, Log, All);
UE_LOG(LogMyGame, Log, TEXT("Loaded: %s"), *LevelName);
UE_LOG(LogMyGame, Warning, TEXT("HP low: %.1f"), Health);
UE_LOG(LogMyGame, Error, TEXT("Spawn failed: %s"), *ClassName);
UE_CLOG(Health < 0.f, LogMyGame, Error, TEXT("Negative HP: %.1f"), Health);| Verbosity | Visible | When |
|---|---|---|
| Always | Crash-level |
| Always | Operation failed |
| Always | Unexpected but recoverable |
| Non-shipping | Standard trace |
| | Fine-grained trace |
cpp
// MyGameLog.h / .cpp
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);
DEFINE_LOG_CATEGORY(LogMyGame);
// 单文件中使用:DEFINE_LOG_CATEGORY_STATIC(LogLocal, Log, All);
UE_LOG(LogMyGame, Log, TEXT("Loaded: %s"), *LevelName);
UE_LOG(LogMyGame, Warning, TEXT("HP low: %.1f"), Health);
UE_LOG(LogMyGame, Error, TEXT("Spawn failed: %s"), *ClassName);
UE_CLOG(Health < 0.f, LogMyGame, Error, TEXT("Negative HP: %.1f"), Health);| 日志级别 | 是否可见 | 使用场景 |
|---|---|---|
| 始终可见 | 崩溃级错误 |
| 始终可见 | 操作失败 |
| 始终可见 | 意外但可恢复 |
| 非发布版本可见 | 标准追踪 |
| 需要通过 | 细粒度追踪 |
Subsystems
子系统
Auto-registered singletons — no manual needed.
AddToRoot| Subsystem | Owner | Persists Level Load | Per Player |
|---|---|---|---|
| | Yes | No |
| | No | No |
| | Yes | Yes |
| | Yes (whole session) | No |
cpp
UCLASS()
class MYGAME_API UInventorySubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
UFUNCTION(BlueprintCallable) void AddItem(FName ItemID, int32 Count);
private:
TMap<FName, int32> Inventory;
};
// Access — each subsystem type has a different accessor
UInventorySubsystem* Inv = GetGameInstance()->GetSubsystem<UInventorySubsystem>();
USpawnSubsystem* Sp = GetWorld()->GetSubsystem<USpawnSubsystem>();
UUIStateSubsystem* UI = GetLocalPlayer()->GetSubsystem<UUIStateSubsystem>();
UMyEngineSubsystem* ES = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();Subsystems have and -- override for setup/teardown. persists across map changes; reinitializes per world. Call via the owning context (, , ).
Initialize()Deinitialize()UGameInstanceSubsystemUWorldSubsystemGetSubsystem<T>()GetGameInstance()GetWorld()GetLocalPlayer()自动注册的单例 — 无需手动调用。
AddToRoot| 子系统 | 所有者 | 跨关卡加载是否持久化 | 按玩家实例化 |
|---|---|---|---|
| | 是 | 否 |
| | 否 | 否 |
| | 是 | 是 |
| | 是(整个会话周期) | 否 |
cpp
UCLASS()
class MYGAME_API UInventorySubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
UFUNCTION(BlueprintCallable) void AddItem(FName ItemID, int32 Count);
private:
TMap<FName, int32> Inventory;
};
// 访问方式 — 不同类型的子系统有不同的访问函数
UInventorySubsystem* Inv = GetGameInstance()->GetSubsystem<UInventorySubsystem>();
USpawnSubsystem* Sp = GetWorld()->GetSubsystem<USpawnSubsystem>();
UUIStateSubsystem* UI = GetLocalPlayer()->GetSubsystem<UUIStateSubsystem>();
UMyEngineSubsystem* ES = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();子系统拥有和方法 — 可重写用于初始化/清理。跨地图加载持久化;每个世界重新初始化。通过所属上下文调用(、、)。
Initialize()Deinitialize()UGameInstanceSubsystemUWorldSubsystemGetSubsystem<T>()GetGameInstance()GetWorld()GetLocalPlayer()Replicated Properties
复制属性
Both specifier AND are required:
UPROPERTYGetLifetimeReplicatedPropscpp
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;
UFUNCTION()
void OnRep_Health(); // Called on clients when server updates Health
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutProps) const
{
Super::GetLifetimeReplicatedProps(OutProps);
DOREPLIFETIME_CONDITION(AMyActor, Health, COND_OwnerOnly);
// COND_None, COND_OwnerOnly, COND_SkipOwner, COND_SimulatedOnly, COND_InitialOnly
}必须同时具备说明符和实现:
UPROPERTYGetLifetimeReplicatedPropscpp
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;
UFUNCTION()
void OnRep_Health(); // 服务器更新Health时在客户端调用
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutProps) const
{
Super::GetLifetimeReplicatedProps(OutProps);
DOREPLIFETIME_CONDITION(AMyActor, Health, COND_OwnerOnly);
// COND_None, COND_OwnerOnly, COND_SkipOwner, COND_SimulatedOnly, COND_InitialOnly
}Conditional Compilation Guards
条件编译守卫
cpp
#if WITH_EDITOR
// Editor-only properties — stripped from shipping builds
UPROPERTY(EditAnywhere, Category="Debug")
bool bShowDebugSpheres = false;
virtual void PostEditChangeProperty(FPropertyChangedEvent& E) override;
#endif
#if !UE_BUILD_SHIPPING
// Available in Development + Debug, stripped from Shipping
void DrawDebugInfo();
#endifcpp
#if WITH_EDITOR
// 仅编辑器可用的属性 — 发布版本中会被移除
UPROPERTY(EditAnywhere, Category="Debug")
bool bShowDebugSpheres = false;
virtual void PostEditChangeProperty(FPropertyChangedEvent& E) override;
#endif
#if !UE_BUILD_SHIPPING
// 在开发版和调试版中可用,发布版中被移除
void DrawDebugInfo();
#endifCommon Mistakes
常见错误
Raw UObject member without UPROPERTY — dangling pointer:*
cpp
UMyObject* Obj; // BAD — GC invisible
UPROPERTY() TObjectPtr<UMyObject> Obj; // GOODModify TArray during ranged-for — undefined behavior:
cpp
for (const AActor* A : Actors) { Actors.Remove(A); } // CRASH
for (int32 i = Actors.Num()-1; i >= 0; --i) { if (ShouldRemove(Actors[i])) Actors.RemoveAt(i); }TSharedPtr on a UObject — GC + refcount conflict:
cpp
TSharedPtr<UMyObject> P = MakeShared<UMyObject>(); // BAD — leaks or double-free
UPROPERTY() TObjectPtr<UMyObject> P; // GOODMissing GetLifetimeReplicatedProps:
cpp
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyActor, ReplicatedHealth);
DOREPLIFETIME_CONDITION(AMyActor, TeamScore, COND_OwnerOnly);
}GENERATED_USTRUCT_BODY() in UE5 structs — use GENERATED_BODY() instead.
AddDynamic with a non-UFUNCTION — compile error or crash at runtime.
未添加UPROPERTY的原生UObject*成员 — 指针悬空:
cpp
UMyObject* Obj; // 错误用法 — GC不可见
UPROPERTY() TObjectPtr<UMyObject> Obj; // 正确用法范围for循环中修改TArray — 未定义行为:
cpp
for (const AActor* A : Actors) { Actors.Remove(A); } // 崩溃
for (int32 i = Actors.Num()-1; i >= 0; --i) { if (ShouldRemove(Actors[i])) Actors.RemoveAt(i); }对UObject使用TSharedPtr — GC与引用计数冲突:
cpp
TSharedPtr<UMyObject> P = MakeShared<UMyObject>(); // 错误用法 — 内存泄漏或双重释放
UPROPERTY() TObjectPtr<UMyObject> P; // 正确用法缺少GetLifetimeReplicatedProps实现:
cpp
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyActor, ReplicatedHealth);
DOREPLIFETIME_CONDITION(AMyActor, TeamScore, COND_OwnerOnly);
}UE5结构体中使用GENERATED_USTRUCT_BODY() — 请改用GENERATED_BODY()。
对非UFUNCTION使用AddDynamic — 编译错误或运行时崩溃。
Related Skills
相关技能
- ue-module-build-system — Build.cs, module dependencies, include paths, PCH configuration
- ue-actor-component-architecture — AActor/UActorComponent lifecycle, spawning, tick groups, component setup
- ue-module-build-system — Build.cs、模块依赖、包含路径、PCH配置
- ue-actor-component-architecture — AActor/UActorComponent生命周期、生成、Tick组、组件设置