ue-cpp-foundations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE C++ Foundations

UE C++ 基础

You are an expert in Unreal Engine's C++ extensions and property system.
您是Unreal Engine C++扩展与属性系统方面的专家。

Context

上下文

Read
.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.
请阅读
.agents/ue-project-context.md
了解引擎版本、编码规范和项目特定规则。引擎版本十分重要:UE5使用
TObjectPtr<>
,而UE4使用原生
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
GENERATED_BODY()
inside the class/struct and the corresponding
.generated.h
include.
所有UE反射宏都要求在类/结构体内部添加
GENERATED_BODY()
,并包含对应的
.generated.h
头文件。

UCLASS()

UCLASS()

SpecifierEffect
Blueprintable
Blueprint subclassing allowed
BlueprintType
Usable as Blueprint variable
Abstract
Cannot be instantiated
NotBlueprintable
Blocks Blueprint subclassing
Config=<Name>
Loads UPROPERTY(Config) from
<Name>.ini
Transient
Not saved/serialized
Within=<OuterClass>
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.
说明符作用
Blueprintable
允许创建Blueprint子类
BlueprintType
可作为Blueprint变量使用
Abstract
无法实例化
NotBlueprintable
禁止创建Blueprint子类
Config=<Name>
<Name>.ini
加载UPROPERTY(Config)
Transient
不保存/序列化
Within=<OuterClass>
外部对象必须为指定类型
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, CheatManager
cpp
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、CheatManager

USTRUCT() 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

选择合适的委托类型

TypeBindingsBlueprintWhen to Use
DECLARE_DELEGATE
1NoInternal single-owner callback
DECLARE_MULTICAST_DELEGATE
NNoInternal multi-listener events
DECLARE_DYNAMIC_DELEGATE
1YesBlueprint-assignable single callback
DECLARE_DYNAMIC_MULTICAST_DELEGATE
NYesBlueprint-bindable events (most common)
类型绑定数量支持Blueprint使用场景
DECLARE_DELEGATE
1内部单一所有者回调
DECLARE_MULTICAST_DELEGATE
N内部多监听者事件
DECLARE_DYNAMIC_DELEGATE
1可由Blueprint赋值的单一回调
DECLARE_DYNAMIC_MULTICAST_DELEGATE
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

字符串类型

TypeUse ForComparisonMutable
FName
Identifiers, asset names, tagsO(1) integerNo
FString
General-purpose strings, file pathsO(n)Yes
FText
Player-visible display stringsNo
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 define
Conversion:
Name.ToString()
→ FString,
FName(*Str)
← FString,
FText::FromString(Str)
,
Text.ToString()
.

类型使用场景比较复杂度是否可变
FName
标识符、资源名称、标签O(1)整数比较
FString
通用字符串、文件路径O(n)
FText
玩家可见的显示字符串
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", "...")无需定义命名空间
转换:
Name.ToString()
→ FString,
FName(*Str)
← FString,
FText::FromString(Str)
Text.ToString()

Memory & Garbage Collection

内存与垃圾回收

UE's GC tracks every
UObject*
reachable from a root. Unreachable objects are destroyed.
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);
VerbosityVisibleWhen
Fatal
AlwaysCrash-level
Error
AlwaysOperation failed
Warning
AlwaysUnexpected but recoverable
Log
Non-shippingStandard trace
Verbose
-LogCmds
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);
日志级别是否可见使用场景
Fatal
始终可见崩溃级错误
Error
始终可见操作失败
Warning
始终可见意外但可恢复
Log
非发布版本可见标准追踪
Verbose
需要通过
-LogCmds
开启
细粒度追踪

Subsystems

子系统

Auto-registered singletons — no manual
AddToRoot
needed.
SubsystemOwnerPersists Level LoadPer Player
UGameInstanceSubsystem
UGameInstance
YesNo
UWorldSubsystem
UWorld
NoNo
ULocalPlayerSubsystem
ULocalPlayer
YesYes
UEngineSubsystem
UEngine
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
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()
).

自动注册的单例 — 无需手动调用
AddToRoot
子系统所有者跨关卡加载是否持久化按玩家实例化
UGameInstanceSubsystem
UGameInstance
UWorldSubsystem
UWorld
ULocalPlayerSubsystem
ULocalPlayer
UEngineSubsystem
UEngine
是(整个会话周期)
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()
方法 — 可重写用于初始化/清理。
UGameInstanceSubsystem
跨地图加载持久化;
UWorldSubsystem
每个世界重新初始化。通过所属上下文调用
GetSubsystem<T>()
GetGameInstance()
GetWorld()
GetLocalPlayer()
)。

Replicated Properties

复制属性

Both
UPROPERTY
specifier AND
GetLifetimeReplicatedProps
are required:
cpp
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
}

必须同时具备
UPROPERTY
说明符和
GetLifetimeReplicatedProps
实现:
cpp
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();
#endif

cpp
#if WITH_EDITOR
// 仅编辑器可用的属性 — 发布版本中会被移除
UPROPERTY(EditAnywhere, Category="Debug")
bool bShowDebugSpheres = false;

virtual void PostEditChangeProperty(FPropertyChangedEvent& E) override;
#endif

#if !UE_BUILD_SHIPPING
// 在开发版和调试版中可用,发布版中被移除
void DrawDebugInfo();
#endif

Common Mistakes

常见错误

Raw UObject member without UPROPERTY — dangling pointer:*
cpp
UMyObject* Obj;  // BAD — GC invisible
UPROPERTY() TObjectPtr<UMyObject> Obj;  // GOOD
Modify 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;                 // GOOD
Missing 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组、组件设置