UE 面试题库 · 整理版

UE 面试题库整理版

本站为 UE 面试题资料的统一整理版:对 38 份原始资料 + 面试题整理.txt 去重、统一格式、按主题重新分章,并标注每题的难度。

整理说明

  • 去重:同一道题在多个源文件(中文章节 + 英文专题 + 难度分级文件 + 面试题整理.txt)中出现时,只保留一份完整答案,互补要点已合并。
  • 统一格式:每题固定包含 **难度**(★☆☆☆☆ 简单 / ★★☆☆☆ 中等 / ★★★☆☆ 困难)、**考点**、**参考答案**(精简要点 + 关键代码)。
  • 分级速查:按"初级/中级/高级"组织的原资料已转为速查索引(只列题目 + 难度 + 归属章节),完整答案请到对应章节查阅。
  • 内容完整:原始资料已全部纳入本整理版,按章节重新组织,便于检索与复习。
  • 格式规范:见 _格式规范.md。

章节总览

章节 文件 题数 覆盖主题
第1章 章节/01_基础概念与引擎架构.md 77 引擎架构、UObject/Actor/Component、反射、GC、模块
第2章 章节/02_C++与UE编程核心.md 89 多态、智能指针、模板、STL、内存、UCLASS/UHT、委托、多线程、友元、共用体
第3章 章节/03_蓝图系统与交互.md 30 蓝图变量、事件图表、接口、委托、蓝图通信
第4章 章节/04_渲染管线与材质系统.md 57 渲染流程、光照、PBR、材质、贴图、后处理
第5章 章节/05_动画系统.md 32 动画蓝图、状态机、混合空间、蒙太奇、IK、重定向、优化
第6章 章节/06_物理与碰撞系统.md 22 碰撞、物理材质、Chaos、约束、射线检测
第7章 章节/07_网络与多人游戏.md 22 复制、RPC、权威性、预测、延迟补偿、网络优化
第8章 章节/08_性能优化.md 51 Profiler、CPU/GPU、内存、加载、DrawCall、资源
第9章 章节/09_AI系统.md 34 行为树、黑板、寻路、EQS、感知、AI优化
第10章 章节/10_Gameplay框架与高级主题.md 47 GameMode、GAS、输入、数据资产、玩法设计
第11章 章节/11_高频真题补充.md 10 高频真实面试题、系统设计
第12章 章节/12_算法面试题.md 27 排序、查找、链表、树、图、DP、回溯、编程题
第13章 章节/13_数据结构面试题.md 37 数组、链表、哈希、树、图、UE容器、复杂度
第14章 章节/14_3D数学面试题.md 32 向量、矩阵、四元数、欧拉角、插值、坐标变换
第15章 章节/15_设计模式与架构.md 31 单例、工厂、观察者、状态、对象池、架构分层、适配器、迭代器、模板方法、外观、建造者、原型、抽象工厂
第16章 章节/16_数字孪生行业.md 19 数字孪生概念、行业应用、BIM、数据接入、UE落地
第17章 章节/17_Lua与热更新.md 4 Lua、Puerts/slua、热更新流程
第18章 章节/18_工具链与工程实践.md 12 版本控制、World Partition、资产管线、调试工具、像素流送
第19章 章节/19_场景与业务实战.md 43 业务场景、刁钻排查、产品设计、架构取舍
合计 670

速查索引

难度 文件 来源
初级 速查索引/初级速查索引.md 01-初级基础 + 初级岗位核心
中级 速查索引/中级速查索引.md 02-中级实战 + 中级岗位核心
高级 速查索引/高级速查索引.md 03-高级架构 + 高级岗位核心

使用建议

  1. 系统性复习:按章节 1 → 19 顺序学习,覆盖全部主题。
  2. 面试冲刺:先看 速查索引 定位薄弱难度,再回到对应章节精读答案。
  3. 查漏补缺:数字孪生(第16章)、场景业务(第19章)等是行业岗位特色内容,投相关岗位时重点看。

第1章 基础概念与引擎架构

本章共 77 道题,覆盖:UObject 与反射系统、垃圾回收与内存管理、继承体系与组件设计、Actor 生命周期、Gameplay 框架、World/Level 与 PIE、Tick 与游戏循环、引擎启动流程、模块与插件、委托与事件、序列化与网络、UE5 渲染新技术、World Partition 与 Data Layer、ECS/Mass、项目结构、编辑器工具等基础概念与引擎架构核心知识。

一、UObject 系统与反射机制

1.1 什么是 UObject?它在虚幻引擎中的作用是什么?

★ 简单

考点: UObject 基类、引擎对象模型、反射与 GC

参考答案:

  1. UObject 是虚幻引擎中所有引擎对象的基类,是整个引擎对象系统的核心;
  2. 核心能力:
    • 反射:提供运行时类型信息(UClass/UFunction/FProperty),支持动态类型查询与属性操作;
    • 垃圾回收:自动管理对象生命周期,防止内存泄漏;
    • 序列化:支持保存/加载,用于存档与关卡保存;
    • 属性系统:通过 UPROPERTY 暴露属性到编辑器和蓝图;
    • 网络复制:支持属性同步;
    • 编辑器集成:对象属性可在详情面板可视化编辑。
  3. 通过 UCLASS + GENERATED_BODY() 声明;常见派生类有 AActor、UActorComponent、UDataAsset、UUserWidget 等;
  4. 只能通过 NewObject<>() / CreateDefaultSubobject<>() 创建,不能 new。

1.2 UObject 的类型系统是如何组织的?

★★ 中等

考点: UClass、UStruct、类型查询

参考答案:

  1. 类型系统基于反射元数据结构组织:
    • UField:所有反射字段的基类;
    • UStruct:结构体基类,派生出 UClass(UObject 类)、UFunction(函数)、UScriptStruct(如 FVector);
    • UEnum:枚举反射信息;
    • 接口通过 UINTERFACE 宏声明为 UInterface。
  2. 类型查询示例:
UClass* Class = AActor::StaticClass();           // 类的 UClass
UClass* DynClass = MyObj->GetClass();            // 实例的 UClass
bool bIsActor = MyObj->IsA(AActor::StaticClass());
bool bIsChar = Cast<ACharacter>(MyObj) != nullptr;
for (UClass* SC = Class; SC; SC = SC->GetSuperClass()) { /* 遍历继承链 */ }
UClass* Found = FindObject<UClass>(nullptr, TEXT("/Script/Engine.Character"));

1.3 什么是反射机制?虚幻引擎如何实现反射?

★★★ 困难

考点: 反射原理、UHT、UClass、FProperty

参考答案:

  1. 反射(Reflection):程序在运行时能查询和操作自身结构(类、属性、函数)的能力;
  2. 实现方式:
    • UHT 编译时解析 UCLASS/UPROPERTY/UFUNCTION 宏,生成反射注册代码;
    • 每个反射头文件生成对应的 .generated.h;
    • UClass 存类元数据(类名、父类、属性/函数列表);UField 是所有反射字段基类,派生出 UStruct(UClass/UFunction/UScriptStruct)与 FProperty(属性元数据,UE5 改为值类型,替代旧 UProperty);
  3. 应用场景:蓝图调用 C++、编辑器详情面板、序列化、网络复制、GAS(Gameplay Ability System);
  4. 反射遍历示例:
UClass* Class = AMyActor::StaticClass();
for (TFieldIterator<FProperty> It(Class); It; ++It) {
    FProperty* Prop = *It;
    UE_LOG(LogTemp, Log, TEXT("Property: %s"), *Prop->GetName());
}
UFunction* Func = Class->FindFunctionByName(TEXT("TakeDamage"));

1.4 UHT(Unreal Header Tool)的工作流程是什么?

★★★ 困难

考点: UHT、代码生成、.generated.h

参考答案:

  1. UHT 是 UE 的自定义代码生成工具,负责解析反射宏并生成 C++ 代码;
  2. 工作流程:扫描含反射宏的头文件 → 语法解析(UCLASS/UPROPERTY/UFUNCTION 及说明符)→ 收集类型信息(类层次、属性类型、函数签名)→ 生成 .generated.h → 生成注册代码并在模块启动时注册;
  3. 生成的代码包括:StaticClass()/StaticStruct()、属性注册、函数调用桩、序列化逻辑;
  4. 注意事项:
    • 反射宏只能在头文件使用;
    • 类体必须包含 GENERATED_BODY(),引用时需包含对应 .generated.h;
    • UHT 不支持 C++ 模板类反射;
    • 修改反射宏后需重新编译,UHT 会自动重跑。

1.5 UCLASS、UPROPERTY、UFUNCTION 宏的作用是什么?

★ 简单

考点: 反射宏、说明符、蓝图暴露

参考答案:

  1. UCLASS:声明一个 UObject 派生类,使其参与反射、GC 与序列化;
  2. UPROPERTY:声明属性,暴露给编辑器、蓝图与序列化系统;
  3. UFUNCTION:声明函数,供蓝图调用或作为事件回调;
  4. 常用说明符:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") float MaxHealth;
UPROPERTY(VisibleAnywhere) float CurrentHealth;          // 只读
UPROPERTY(Replicated) int32 Score;                       // 网络复制
UPROPERTY(ReplicatedUsing = OnRep_Health) float Health;  // 复制回调
UPROPERTY(Transient) UObject* TempRef;                   // 不参与序列化
UFUNCTION(BlueprintCallable) void ApplyDamage(float Amount);
UFUNCTION(Server, Reliable) void Server_Fire(FVector Origin, FVector Dir);
  • EditAnywhere:编辑器任意位置可编辑;BlueprintReadWrite:蓝图中可读写;BlueprintCallable:蓝图中可调用;Replicated:网络复制;Transient:不序列化。

1.6 GENERATED_BODY() 宏的作用是什么?

★ 简单

考点: 反射宏、代码生成

参考答案:

  1. 每个 UCLASS 类内必需的反射宏,必须放在类定义体的第一行;
  2. 作用:
    • 声明类型别名与生成代码接口,包含对应的 .generated.h;
    • 声明 typedef AActor Super; 供 Super:: 调用父类;
    • 声明 StaticClass() 及 AddReferencedObjects 等 GC 相关函数;
  3. 替代旧版 GENERATED_UCLASS_BODY()/GENERATED_BODY_SIMPLE();缺失会编译错误。
UCLASS()
class AMyActor : public AActor {
    GENERATED_BODY()
    // GENERATED_BODY() 展开大致包含:
    // typedef AActor Super; static UClass* StaticClass(); ...
};

1.7 如何创建 UObject?NewObject 与 CreateDefaultSubobject 的区别?

★★ 中等

考点: NewObject、CreateDefaultSubobject、CDO、Outer

参考答案:

  1. NewObject<T>():运行时动态创建任意 UObject,可指定 Outer 与名称;
  2. CreateDefaultSubobject<T>():构造函数中创建子对象(组件),参与 CDO 与蓝图配置;
  3. 区别对比:
特性 NewObject CreateDefaultSubobject
使用场景 运行时动态创建 构造函数中创建子对象
编辑器可视化 不支持组件可视化 支持编辑器中可视化编辑
CDO 集成 不参与 CDO 参与类默认对象(CDO)
蓝图覆盖 不可被蓝图覆盖 可在蓝图中替换与配置
UMyObject* Obj = NewObject<UMyObject>(Outer, TEXT("MyObj"));
AMyActor::AMyActor() {
    MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    RootComponent = MeshComp;
}

1.8 什么是 CDO(Class Default Object)?它的作用是什么?

★★ 中等

考点: CDO、默认属性、蓝图继承

参考答案:

  1. CDO 是每个 UClass 唯一的默认实例,存储该类属性的默认值;
  2. 特点:类注册时自动创建;构造函数赋值会反映到 CDO;新对象从 CDO 复制属性值;编辑器的"类默认值"面板即修改 CDO;
  3. 获取:GetDefault<T>() / StaticClass()->GetDefaultObject<T>();GetMutableDefault<T>() 可修改(一般不建议运行时改);
  4. 作用:属性默认值、编辑器类默认值配置、蓝图继承覆盖、运行时查询默认值。
AMyActor* Default = GetDefault<AMyActor>();

1.9 UObject 的子对象(Subobject)是什么?如何管理?

★★ 中等

考点: Outer、对象树、组件创建

参考答案:

  1. 子对象是 Outer 指向另一个 UObject 的 UObject,形成对象层次结构;
  2. 特点:父对象序列化时子对象一并序列化;父被 GC 回收时子同步回收;Actor 的 Component 是典型子对象;
  3. 管理:
    • 构造函数用 CreateDefaultSubobject 创建;
    • 运行时用 NewObject<T>(this),this 作为 Outer;
    • 运行时创建的组件需 RegisterComponent();
    • 查找用 GetComponentByClass<T>();
  4. 不要手动 delete UObject,交给 GC。
AMyActor::AMyActor() {
    MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    RootComponent = MeshComp;
}
UMyData* Data = NewObject<UMyData>(this);       // this 为 Outer

1.10 如何在编辑器中自定义 UObject 的属性显示?

★★ 中等

考点: UPROPERTY 显示控制、IDetailCustomization

参考答案:

  1. 通过 UPROPERTY 说明符控制显示方式:
UPROPERTY(EditInstanceOnly) FString PlayerName;        // 仅实例可编辑
UPROPERTY(EditDefaultsOnly) float SpawnRate;           // 仅 CDO/蓝图可编辑
UPROPERTY(meta=(ClampMin="0", ClampMax="100")) uint8 Percent;
UPROPERTY(meta=(MakeEditWidget)) FVector TargetPoint;  // 视口中可编辑 Widget
UPROPERTY(Transient) UObject* TempRef;                 // 不序列化
  1. 自定义详情面板:IDetailCustomization 自定义类布局、IPropertyTypeCustomization 自定义属性类型显示;
  2. 在模块 StartupModule 注册:
FPropertyEditorModule& Mod = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
Mod.RegisterCustomClassLayout(AMyActor::StaticClass()->GetFName(),
    FOnGetDetailCustomizationInstance::CreateStatic(&FMyActorDetails::MakeInstance));

1.11 UObject 的生命周期是什么?

★★ 中等

考点: UObject 生命周期、GC、销毁回调

参考答案:

  1. 生命周期由垃圾回收系统管理:创建 → 初始化 → 注册到引擎对象表 → 使用 → 标记回收 → 销毁 → 释放;
  2. 创建:NewObject<>() / CreateDefaultSubobject<>();
  3. 初始化:构造函数 → PostInitProperties() 完成属性初始化;
  4. 注册:对象被注册到引擎对象表,可被查找与引用;
  5. 销毁:没有强引用时 GC 回收,依次调用 BeginDestroy() → IsReadyForFinishDestroy() → FinishDestroy();入口是 ConditionalBeginDestroy()。
virtual void PostInitProperties() override { Super::PostInitProperties(); }
virtual void BeginDestroy() override { Super::BeginDestroy(); }
virtual bool IsReadyForFinishDestroy() override { return true; }
virtual void FinishDestroy() override { Super::FinishDestroy(); }

二、垃圾回收与内存管理

2.1 虚幻引擎的垃圾回收机制是什么?

★★★ 困难

考点: Mark-and-Sweep、根集合、强引用/弱引用

参考答案:

  1. 采用标记-清除算法,专门管理 UObject 及含 UObject 指针的对象;
  2. 根集合(Root Set):AddToRoot() 对象、UWorld 及其子对象、引擎全局对象(GEngine/GEditor)、蓝图 CDO;
  3. 过程:从根递归遍历(通过 UPROPERTY 指针、AddReferencedObjects())标记可达集,未标记对象销毁;
  4. 关键概念:强引用(UPROPERTY 指针)参与追踪;弱引用 TWeakObjectPtr 不阻止回收;循环引用不泄漏(不同于引用计数);GC 在游戏线程暂停时执行,可能造成掉帧;
  5. 自定义追踪:
void UMyObject::AddReferencedObjects(UObject** InObj, FReferenceCollector& Collector) {
    Super::AddReferencedObjects(InObj, Collector);
    Collector.AddReferencedObject(MyReferencedObject);
}

2.2 标记-清除算法的具体流程是什么?

★★★ 困难

考点: GC 算法、Stop The World、ConditionalBeginDestroy

参考答案:

  1. 准备:暂停游戏线程(Stop The World),收集根集合;
  2. 标记:遍历根集合对象,调用 AddReferencedObjects() 递归标记所有可达对象存活(写 GC 标记位);
  3. 清除:遍历全部 UObject,未标记的调 ConditionalBeginDestroy(),从对象表移除、释放内存;
  4. 恢复:清除标记位、恢复游戏线程。

2.3 如何优化 GC 性能?

★★★ 困难

考点: GC 优化、UStruct、弱引用、TObjectPtr

参考答案:

  1. 减少 UObject 数量:纯数据结构用 USTRUCT;
  2. 不需要所有权的引用用 TWeakObjectPtr;
  3. 避免 AddToRoot(增大根集合拖慢 GC);
  4. 合理使用 Outer 让子对象随父回收;
  5. UE5 用 TObjectPtr 替代裸指针;
  6. 调试:obj list、obj list class=X、stat gc、GC.DebugObjectNames=。
UPROPERTY() TObjectPtr<AActor> Target; // UE5 推荐
USTRUCT() struct FBulletData { float Damage; float Speed; };

2.4 AddToRoot 的作用是什么?什么时候应该使用?

★★ 中等

考点: 根集合、对象生命周期保护

参考答案:

  1. AddToRoot() 将 UObject 加入 GC 根集合,阻止回收;
  2. 适用:对象没有 Outer 且需长期存活、单例跨关卡、异步加载过程保护、编辑器工具对象;
  3. 不再需要时必须 RemoveFromRoot();
  4. 尽量少用(增大根集合);优先用 UPROPERTY 引用或子对象关系;UE5 推荐 UPrimaryDataAsset + 资产管理器。
UMySingleton* S = NewObject<UMySingleton>();
S->AddToRoot();          // 防回收
// ... 用完后
S->RemoveFromRoot();
S = nullptr;             // 之后可被 GC 回收

2.5 TWeakObjectPtr 和 TWeakPtr 有什么区别?

★★ 中等

考点: 弱引用、GC 与引用计数

参考答案:

维度 TWeakObjectPtr TWeakPtr
对象 UObject 非 UObject 的 C++ 对象
机制 与 GC 集成 与 TSharedPtr 引用计数配合
失效 对象被 GC 回收后自动失效 最后一个 TSharedPtr 销毁时失效
访问 IsValid() + Get() Pin() 转 TSharedPtr
UPROPERTY() TWeakObjectPtr<AActor> WeakActor;
if (WeakActor.IsValid()) { AActor* A = WeakActor.Get(); }

TSharedPtr<FMyData> Shared = MakeShared<FMyData>();
TWeakPtr<FMyData> Weak = Shared;
if (TSharedPtr<FMyData> Pinned = Weak.Pin()) { /* 使用 Pinned */ }

2.6 GC 暂停(Stop The World)对性能的影响及缓解?

★★★ 困难

考点: GC 卡顿、增量 GC、异步 GC

参考答案:

  1. 影响:GC 暂停期间游戏逻辑完全停止;对象越多暂停越长,可能出现卡顿/掉帧;多人游戏中会放大延迟;
  2. 缓解:增量 GC(分帧执行)、减少对象总数、控制 GC 频率、在加载界面/过场时触发异步 GC;
  3. 命令与代码:
// 控制台:obj gc 手动触发
GetWorld()->ForceGarbageCollection(true); // 立即清理 PendingKill
gc.TimeBetweenPurgingPendingKillObjects=60

2.7 增量 GC 的原理是什么?

★★★ 困难

考点: 时间切片、增量标记、增量销毁

参考答案:

  1. 将完整 GC 拆成多帧执行:初始短暂停 → 每帧增量标记一部分 → 最终短暂停 → 每帧增量销毁;
  2. 每帧分配固定时间预算(如 gc.MaxIncrementalTimePerFrame=5.0),帧间保存 GC 状态;
  3. 特点:总耗时可能增加,但单次暂停大幅缩短、对帧率更友好;对 VR 等敏感场景重要;UE5 默认启用(gc.IncrementalBeginDestroyEnabled=True)。

2.8 如何避免 UObject 的内存泄漏?

★★ 中等

考点: UPROPERTY、生命周期管理、防悬空指针

参考答案:

  1. 所有 UObject 指针用 UPROPERTY() 让 GC 追踪;
  2. 少用/慎用 AddToRoot;
  3. 不必拥有的引用用 TWeakObjectPtr;
  4. 正确建立子对象关系(CreateDefaultSubobject);
  5. BeginDestroy 中清理外部引用;非 UObject 用 TSharedPtr/TWeakPtr;避免裸指针存 UObject*。
AActor* Cached;                        // 危险:可能悬空
UPROPERTY() AActor* Safe;              // GC 追踪
UPROPERTY() TWeakObjectPtr<AActor> Weak; // 更安全,不阻止回收

三、继承体系、Actor 与组件设计

3.1 UObject、AActor、UActorComponent 继承体系详解

★★★ 困难

考点: 对象模型、反射与 GC、职责边界

参考答案:

  1. 完整层次(概要):
UObject
├── AActor                       可放置到世界
│   ├── APawn → ACharacter
│   ├── AController (APlayerController / AAIController)
│   ├── AGameModeBase / AGameStateBase / APlayerState
│   └── AInfo / ATriggerVolume ...
├── UActorComponent
│   ├── USceneComponent → UPrimitiveComponent → 网格/形状/粒子
│   └── 纯逻辑组件
├── USubsystem (UEngine / UGameInstance / UWorld / ULocalPlayer / UEditor)
├── UUserWidget / UDataAsset / UAnimInstance ...
  1. 各层职责:
    • UObject:反射、GC、序列化、CDO、网络、编辑器集成;没有 Transform;
    • AActor:增加 Transform、组件容器、生命周期方法(PreInitialize→PostInitialize→BeginPlay→Tick→EndPlay→Destroyed)、关卡关联、网络角色、被 Possess 可处理输入;
    • UActorComponent:可复用功能单元,增加 Owner、Activate/Deactivate、组件依赖、网络复制开关;
  2. 职责边界表:
层级 职责 不包含
UObject 反射/GC/序列化/CDO Transform、默认无 Tick
AActor 世界存在、组件容器、生命周期 具体功能逻辑
UActorComponent 可复用功能单元 Transform(Scene 才有)
  1. 关键设计原则:Actor 是"薄壳",功能交给 Component 组装(组合优于继承)。

3.2 Actor、Pawn、Character 的区别与关系

★★ 中等

考点: 对象设计、继承层次、角色体系

参考答案:

  1. 继承:UObject → AActor → APawn → ACharacter;
  2. AActor:可放进世界对象的基类,无自带视觉/移动能力;
  3. APawn:可被 Controller/AIController Possess 的"棋子",有被控制能力,移动逻辑通常自实现;
  4. ACharacter:内置 UCharacterMovementComponent(行走/跳跃)、UCapsuleComponent(碰撞)、USkeletalMeshComponent(骨骼模型);
  5. 对比表:
特性 Actor Pawn Character
被 Controller 控制 ✘ ✔ ✔
内置移动组件 ✘ ✘ ✔
内置碰撞/骨骼 ✘ ✘ ✔
典型用途 静态物/特效/触发器 载具/自定义 玩家角色/NPC
  1. 选型:可破坏箱子→Actor;自定义飞行器(特殊移动)→Pawn;人形角色→Character。

3.3 Component 组件化架构设计思想

★★ 中等

考点: 组合优于继承、UE 组件体系、组件生命周期

参考答案:

  1. UE 遵循组合优于继承(Composition over Inheritance),用组件自由组装功能,避免"菱形继承/类爆炸";
  2. 组件继承体系:UObject → UActorComponent(纯逻辑,无 Transform)→ USceneComponent(有 Transform 可附加)→ UPrimitiveComponent(渲染/碰撞)→ 具体组件;
  3. UActorComponent vs USceneComponent:前者无位置,适合数据/逻辑/管理器组件;后者有位置,适合挂载点、相机;
  4. 创建与生命周期:构造函数 CreateDefaultSubobject + SetupAttachment;运行时 NewObject + RegisterComponent()(必须注册);生命周期依次 InitializeComponent → BeginPlay → TickComponent → EndPlay → DestroyComponent;
  5. 自定义组件要点:UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))、属性 EditAnywhere/BlueprintReadWrite、方法 BlueprintCallable、可用动态多播委托声明事件。

3.4 UActorComponent 与 USceneComponent 的生命周期差异

★★ 中等

考点: 组件类型、注册/注销时序

参考答案:

  1. 继承:UObject → UActorComponent(无 Transform)→ USceneComponent(有 Transform,可附加子组件)→ UPrimitiveComponent(可渲染/碰撞);
  2. 生命周期对比:
阶段 UActorComponent USceneComponent
创建 CreateDefaultSubobject 同左
注册 RegisterComponent(+InitializeComponent) RegisterComponent + 设置 Transform
附加 — SetupAttachment / 层级关系
Tick 可选 可选
注销/销毁 Unregister / DestroyComponent 额外清理子组件
  1. SceneComponent 额外回调:OnRegister(可设相对位置)、OnUnregister、AttachmentChanged(附加关系变化)。

3.5 组件的 Activate/Deactivate 机制是什么?如何正确使用?

★★ 中等

考点: 组件激活状态、性能优化

参考答案:

  1. Activate/Deactivate 控制组件是否参与游戏逻辑(启用/停止 Tick 与事件响应);
  2. 用法:Comp->Activate(true) / Comp->Deactivate();
  3. 重写示例:
void UMyComp::Activate(bool bReset) {
    Super::Activate(bReset);
    GetWorld()->GetTimerManager().SetTimer(TimerH, this, &UMyComp::OnTick, 0.1f, true);
}
void UMyComp::Deactivate() {
    GetWorld()->GetTimerManager().ClearTimer(TimerH);
    Super::Deactivate();
}
  1. 场景:切换武器激活/停用组件、技能冷却停用、休眠 AI 停用感知。

3.6 运行时动态创建和销毁组件的最佳实践

★★ 中等

考点: 动态组件、注册、安全销毁

参考答案:

  1. 动态创建:NewObject<T> + 必须 RegisterComponent()(否则不参与世界/Tick/渲染/碰撞),需要网络同步时 SetIsReplicated(true):
UMyComponent* Comp = NewObject<UMyComponent>(this, UMyComponent::StaticClass(), TEXT("Comp"));
Comp->RegisterComponent();
Comp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
  1. 动态销毁:DestroyComponent() 自动完成注销、与父组件分离、销毁子组件、标记 PendingKill;
  2. 常见陷阱:
    • 忘记 RegisterComponent → 组件"凭空消失";
    • 在组件自己的 Tick 中 DestroyComponent() 自毁有风险,应标记待销毁,由 Actor/下帧统一清理。
void UMyComp::TickComponent(float DeltaTime, ELevelTick Type, FActorComponentTickFunction* F) {
    if (bShouldDie) bPendingDestroy = true; // 下帧统一销毁
}

四、Actor 生命周期与销毁

4.1 Actor 从创建到销毁的完整生命周期

★★★ 困难

考点: 生命周期全流程、初始化顺序

参考答案:

  1. 标准顺序:
    • 构造函数:设置默认值、创建默认组件(仅用于 CDO 与编辑器显示);
    • PostInitProperties(UObject 属性初始化完成);
    • OnConstruction/Construction Script(蓝图构造脚本,编辑器摆放时也调用);
    • 组件注册:PreRegisterAllComponents → RegisterAllComponents → PostRegisterAllComponents;
    • PostSpawnInitialize(Spawn 时)→ PostActorCreated;
    • PostInitializeComponents:所有组件初始化完成,可安全访问组件;
    • PostLoad(从磁盘反序列化后调用);
    • BeginPlay:游戏逻辑开始,唯一一次;
    • Tick:每帧,按 TickGroup 顺序;
    • EndPlay:游戏结束/关卡卸载/主动销毁;
    • Destroyed → FinishDestroy:内存释放前清理。
  2. 注意:构造函数在 CDO 创建与 SpawnActor 时都会调用;BeginPlay 只在运行时调用一次;OnConstruction 在编辑器中也调用;
  3. EndPlay 触发场景:Destroyed(主动 Destroy)、LevelTransition(切卡)、EndPlayInEditor(PIE 结束)、RemovedFromWorld(World 销毁);
  4. 常见追问:构造函数内不能访问 GetWorld()(可能未创建)。
void AMyActor::PostInitializeComponents() { Super::PostInitializeComponents(); /* 组件间初始化 */ }
void AMyActor::BeginPlay() { Super::BeginPlay(); /* 找 Actor/注册事件/Timer/网络同步 */ }
void AMyActor::EndPlay(const EEndPlayReason::Type Reason) { /* 清理资源 */ Super::EndPlay(Reason); }

4.2 SpawnActor 的完整执行流程?构造函数中能做什么、不能做什么?

★★★ 困难

考点: Spawn 流程、CDO、构造函数限制

参考答案:

  1. Spawn 流程:SpawnActor<T>(Class, Transform, SpawnParams) → 构造函数 → PostSpawnInitialize → PostActorCreated → RegisterAllComponents → PostInitializeComponents → BeginPlay;
  2. 构造函数中可以:设置变量默认值、CreateDefaultSubobject + SetupAttachment、设置 Tick 标志、加载默认资产;
  3. 构造函数中不可以:
    • 调用 GetWorld()(可能为 null,SpawnActor 会崩溃);
    • 查询其他 Actor、播放声音特效、修改游戏状态、进行网络操作;
  4. 原因:构造函数在 CDO 创建(编辑器启动)、SpawnActor、蓝图实例化时都会被调用;CDO 阶段世界不存在,所以构造函数只做"数据初始化"、不做"逻辑初始化"。

4.3 BeginPlay 和 PostInitializeComponents 的区别是什么?

★★ 中等

考点: 初始化时序、使用场景

参考答案:

  1. 顺序:PostInitializeComponents 先于 BeginPlay;
  2. 区别:
特性 PostInitializeComponents BeginPlay
时机 本 Actor 组件注册初始化后 所有 Actor 初始化完成后
其他 Actor 可能未初始化 已全部初始化
适合 组件间配置/依赖设置 游戏逻辑、找 Actor、注册事件、启动定时器
网络 复制属性可能未同步 复制属性已同步
  1. 示例:PostInit 里给 Mesh 绑定材质、按组件配置调整行为;BeginPlay 里 SetTimer、绑定事件、网络初始化。

4.4 Actor 销毁流程详解:Destroy() vs GC 的区别?

★★ 中等

考点: 销毁流程、内存释放

参考答案:

  1. Destroy()(逻辑销毁):SetLifeSpan(0) → DetachFromActor → EndPlay(Destroyed) → UnregisterAllComponents → 标记 PendingKill → 从 World ActorList 移除;对象仍在内存,IsValid() 返回 false,不可再用于逻辑;
  2. GC(物理销毁):GC 触发 → 对 PendingKill/不可达对象调 ConditionalBeginDestroy → BeginDestroy → FinishDestroy → 真正释放内存;
  3. 一句话理解:Destroy = 逻辑上移除,GC = 物理上释放;IsValid() 在 Destroy 后即 false;
  4. 立即释放内存:ForceGarbageCollection(true)。

五、Gameplay 框架

5.1 GameMode、GameState、PlayerState 的角色与职责

★★ 中等

考点: 多人架构、服务端/客户端职责、状态管理

参考答案:

  1. 分工:规则/流程(GameMode,仅服务端)与状态(GameState 全局、PlayerState 玩家)分离;
  2. GameMode(AGameModeBase/AGameMode):仅在服务端,管登录/登出、默认 Pawn 类、重生(RestartPlayer/ChoosePlayerStart)、MatchState 与游戏规则(胜利条件);客户端调用 GetAuthGameMode() 得到 nullptr;
  3. GameState:所有端存在并复制,存全局状态(比分、回合、比赛阶段);
  4. PlayerState:每玩家一个、复制到所有端,存玩家名、击杀/死亡、队伍、准备状态;
  5. 对比:
特性 GameMode GameState PlayerState
存在 仅服务端 所有端 所有端(每玩家一个)
复制 ✘ ✔ ✔
核心 规则与流程 全局共享状态 单个玩家状态
  1. 数据流:服务端 GameMode 校验击杀 → 更新 KillerPS/DeathPS(自动复制)→ 更新 GameState 分数 → 判定胜利并结束比赛;客户端用 GetWorld()->GetGameState<T>() 读比分。

5.2 PlayerController vs AIController

★★ 中等

考点: 控制器模式、Possess、输入与 AI

参考答案:

  1. Controller 是 Pawn 与逻辑的中间层:Controller 持逻辑、Pawn 持物理表现;同一 Controller 可在不同 Pawn 间切换(Possess/UnPossess);
  2. PlayerController:输入第一接收者(SetupInputComponent + BindAction/BindAxis)→ 转发给 Possess 的 Pawn;管 HUD、相机、UI(用 IsLocalController() 判断本地);网络存在于客户端与服务端,可发 RPC;
  3. AIController:行为树驱动(OnPossess 中 UseBlackboard + RunBehaviorTree)、寻路 MoveTo、感知 UAIPerceptionComponent、黑板读写;
  4. 对比表:
特性 PlayerController AIController
输入来源 硬件输入 行为树/状态机
数量 每本地玩家一个 每 AI Pawn 一个
HUD/相机 ✔ ✘
行为树/感知 ✘ ✔
  1. Possess 流程:服务端 Possess → Pawn->PossessedBy(服务端回调)→ 复制到客户端后 AcknowledgePossession。

六、World、Level、关卡与 PIE

6.1 Level、World、SubLevel 的关系

★★ 中等

考点: 关卡管理体系、流式加载、大世界架构

参考答案:

  1. 层次:UEngine → UWorld(一个游戏世界,可包含多个 Level)→ ULevel(.umap)/ ULevelStreaming(流式子关卡)/ World Partition;
  2. UWorld:游戏世界的容器,含 Level 管理、物理(FPhysScene)、导航、全局 Timer、Actor 生成;同一时刻可存在多个 World(Game、Editor、PIE、Preview 等);
  3. ULevel:一个 .umap;PersistentLevel 始终加载,Streaming Levels 按需加载;
  4. 流式加载:
    • 编辑器设 Level Streaming Volume;
    • 代码 ULevelStreaming::LoadLevelInstance + SetShouldBeLoaded / SetShouldBeVisible;
    • 蓝图 LoadStreamLevel;
    • UE5 推荐 World Partition(按距离自动);
  5. 传统流式 vs World Partition:手动划分 + 手动 Volume vs 自动 Grid + Streaming Source 距离加载;整 Level 文件 vs OFPA(每 Actor 一个文件);适合线性关卡 vs 适合开放世界。
ULevelStreaming* S = ULevelStreaming::LoadLevelInstance(World, TEXT("/Game/Maps/Sub"), Vec, Rot, bOk);
if (S) { S->SetShouldBeVisible(true); S->SetShouldBeLoaded(true); }

6.2 World 是如何创建的?

★★★ 困难

考点: World 创建、EWorldType、World 组成

参考答案:

  1. 流程:UEngine 创建 World(GEngine->CreateWorld)→ 加载地图(.umap)→ 反序列化并初始化 Actor → 初始化 GameMode → 生成 PlayerController/Pawn → GameMode::StartPlay;
  2. World 类型(枚举):None / Game / Editor / PIE / GamePreview / GameRPC / Inactive;
  3. 常用属性:GetWorld()->GetFirstPlayerController()、GetAuthGameMode()、GetCurrentLevel()、PersistentLevel、GetStreamingLevels()、GetTimeSeconds();
  4. 组成:Persistent Level + Streaming Levels + GameInstance(跨关卡)+ AudioDevice + PhysicsScene。

6.3 关卡加载的流程是什么?

★★★ 困难

考点: 反序列化、关卡初始化、异步加载

参考答案:

  1. 请求:OpenLevel / LoadStreamLevel / LoadLevelInstance;
  2. 异步加载:后台线程反序列化关卡资产、创建 Actor 模板;
  3. 初始化:ULevel::InitializeActorsForWorld 注册 Actor 到世界并调 BeginPlay;
  4. 完成后关卡变为活动状态;
  5. 优化:异步加载不卡主线程、用 Level Streaming 按需加载、Soft Reference 延迟加载。
UGameplayStatics::OpenLevel(GetWorld(), FName("NewLevel"));
UGameplayStatics::LoadStreamLevel(GetWorld(), TEXT("Battle"), false, true, LatentInfo);

6.4 PIE 中的多 World 场景与常见陷阱

★★★ 困难

考点: PIE、FWorldContext、多 World

参考答案:

  1. 点击 Play 时编辑器复制生成 PIE World 并运行,编辑器 World 冻结;多人测试时并存 Server + 多个 Client World;每个 World 有一个 FWorldContext;
  2. GetWorld() 拿到的是 PIE World 而非 Editor World;可通过 World->WorldType(EWorldType:Game/PIE/Editor…)判断;
  3. 常见陷阱:
    • 静态/全局变量在多个 PIE 实例间共享 → 用 World-local 或 Subsystem;
    • PIE 停止时 Actor 销毁、单例/全局状态残留 → 在 EndPlay 里清理;
    • 多人 PIE 网络端口冲突;
  4. NetMode 判断:GetWorld()->GetNetMode() 返回 NM_Standalone / NM_ListenServer / NM_DedicatedServer / NM_Client,用于确定当前角色。

6.5 PIE 启动流程与 Standalone 有什么区别?

★★ 中等

考点: PIE 机制、编辑器内测试

参考答案:

  1. PIE 流程:编辑器保存快照 → 创建新 UWorld → 复制关卡到 PIE World → 设置 NetMode → 创建 PlayerController → BeginPlay → 开始 Tick;
  2. Standalone 流程:直接加载地图 → 创建 GameInstance → 创建 World → GameMode → PlayerController → BeginPlay → 主循环;
  3. 对比表:
特性 PIE Standalone
World PIE 专用(复制) 独立 World
速度/资源 受编辑器影响、共享资源 原生、独立资源
热重载 Live Coding 不支持
调试 编辑器内 外部调试器
多人 多人 PIE 多进程

七、Tick 机制与游戏循环

7.1 Actor Tick 机制与性能优化

★★ 中等

考点: 游戏循环理解、Tick 性能影响、优化策略

参考答案:

  1. 启用:PrimaryActorTick.bCanEverTick、bStartWithTickEnabled、TickInterval;组件用 PrimaryComponentTick:
AMyActor::AMyActor() {
    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.TickInterval = 0.0f; // 0 每帧,>0 间隔
}
  1. 每帧执行链:FEngineLoop → UGameEngine → UWorld::Tick → TickGroup → Actor/Component Tick;
  2. 性能杀手:大量 Actor 同时 Tick、Tick 内做射线/GetAllActors/更新 UI;
  3. 优化策略:
    • 不 Tick 的 bCanEverTick = false;
    • 间隔化执行 TickInterval;
    • 战斗等场景动态 SetActorTickEnabled;
    • 用 Timer 替代高频 Tick(GetWorldTimerManager().SetTimer);
    • USignificanceManager 按距离/重要性调频;
    • UTickableWorldSubsystem 批量更新,替代大量 Actor Tick;
    • 事件驱动(状态变更时广播,不每帧轮询);
  4. 分析:stat game / stat memory、Unreal Insights、SCOPE_CYCLE_COUNTER。

7.2 Tick Group 是什么?不同 Tick Group 的执行顺序和应用场景?

★★ 中等

考点: Tick 执行顺序、物理同步

参考答案:

  1. UE 用 TickGroup 规定一帧内执行的顺序:
1. TG_PrePhysics:输入、移动命令
2. 物理模拟(PhysX/Chaos)
3. TG_PostPhysics:基于物理结果更新逻辑
4. TG_PostUpdateWork:相机更新、UI(后处理)
  1. 配置:PrimaryActorTick.TickGroup = TG_PrePhysics;(或 BeginPlay 中动态设置);
  2. 场景:角色输入/移动速度在 PrePhysics 提交给移动组件;相机跟随放 PostUpdateWork 避免抖动。

7.3 引擎的 Tick 循环是如何工作的?

★★★ 困难

考点: 引擎主循环、每帧步骤

参考答案:

  1. 每帧步骤:输入 → Tick(World→Actor 按 TickGroup、Component、Timer、Delegate)→ 物理模拟 → 动画 → 网络(复制/RPC)→ 渲染(场景更新/光照/后处理/呈现)→ GC 检查 → 帧率限制;
  2. TickGroup 枚举:TG_PrePhysics / TG_StartPhysics / TG_DuringPhysics / TG_EndPhysics / TG_PostPhysics / TG_PostUpdateWork / TG_LastDemotable / TG_NewlySpawned;
  3. 优化:尽可能 bCanEverTick=false、Timer 替代轮询、事件驱动、把逻辑放进合适的 TickGroup 并行执行。

八、引擎启动、GEngine 与退出

8.1 引擎从 main() 到游戏启动的完整流程是什么?

★★★ 困难

考点: 引擎初始化、模块加载、GEngine

参考答案:

  1. 入口 GuardedMain:设置异常处理;FEngineLoop::PreInit 初始化基础(内存、线程、命令行解析、配置、日志);
  2. 模块加载:FModuleManager::LoadModules 按依赖顺序加载并调用各模块 StartupModule;
  3. FEngineLoop::Init:初始化 RHI、物理、音频、输入;创建 GEngine(UEngine);初始化渲染核心、Slate/UMG UI;
  4. GEngine->Browse(URL) 加载初始地图:创建 UWorld、加载关卡、创建 GameInstance(在引擎 init 后、加载首地图前创建,跨关卡唯一)→ 创建 GameMode → PlayerController → 开始游戏循环;
  5. 主循环 while (!IsExitRequested()) GEngineLoop.Tick():处理输入 → 逻辑 Tick → 渲染 → 提交帧;
  6. 模块加载顺序:Core → CoreUObject → Engine → Renderer → Game(项目模块)→ Editor(编辑器模式);
  7. 简化示意:
int32 GuardedMain(const TCHAR* CmdLine) {
    FEngineLoop Loop;
    Loop.PreInit(CmdLine); Loop.Init();
    while (!IsEngineExitRequested()) Loop.Tick();
    Loop.Exit();
    return 0;
}

8.2 GEngine 是什么?它的作用是什么?

★★ 中等

考点: 全局引擎对象、世界管理

参考答案:

  1. GEngine 是全局 UEngine*,引擎核心管理器,生命周期与引擎相同;
  2. 作用:管理所有 UWorld、创建/管理 UGameInstance、提供引擎级子系统(GetEngineSubsystem)、当前世界(GetWorld)、遍历世界(GetWorldContexts())、创建世界(CreateWorld);
UWorld* W = GEngine->GetWorld();
UGameInstance* GI = GEngine->GetGameInstance();
for (const FWorldContext& C : GEngine->GetWorldContexts()) { UWorld* W = C.World(); }

8.3 引擎退出流程是怎样的?

★★ 中等

考点: 引擎关闭、模块关闭、资源清理

参考答案:

  1. 顺序:RequestExit → World 清理(所有 Actor 调 EndPlay/Destroyed、销毁 UWorld)→ UGameInstance::Shutdown() → 模块按与启动相反顺序 ShutdownModule → 引擎级清理(关闭 RHI/物理/音频、保存配置)→ 基础系统清理(日志/线程/内存);
  2. 代码:
FGenericPlatformMisc::RequestExit(false);   // 请求退出
void UMyGameInstance::Shutdown() { SaveGameState(); CleanupNetwork(); Super::Shutdown(); }

九、模块、插件与系统

9.1 UE 模块系统与依赖管理

★★★ 困难

考点: Build.cs、Public/Private 依赖、模块接口

参考答案:

  1. 模块是独立编译单元(Windows 下为 DLL),有自己的头文件、源文件、依赖与加载阶段;结构:Module.Build.cs + Public/(对外)+ Private/(实现);
  2. 核心模块:Core(基础库)、CoreUObject(UObject/反射/GC)、Engine(Actor/World/Level)、Renderer/RHI、Slate/UMG、PhysicsEngine、Navigation、AI 等;
  3. 依赖类型:
    • PublicDependencyModuleNames:公共依赖,传递给依赖本模块的其他模块;
    • PrivateDependencyModuleNames:仅本模块内部使用;
    • DynamicallyLoadedModuleNames:运行时动态加载;
    • 被依赖模块先加载;循环依赖编译错误,可抽公共接口模块解决;对外导出符号用 MYMODULE_API;
  4. 模块接口:
class FMyModule : public IModuleInterface {
public:
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
    virtual bool IsGameModule() const override { return true; }
};
IMPLEMENT_MODULE(FMyModule, MyModule)
  1. 常见问题:Include 路径未配置、依赖缺失链接错误、Editor 代码用 #if WITH_EDITOR 隔离。

9.2 模块的生命周期与加载阶段(LoadingPhase)

★★ 中等

考点: 模块生命周期、加载阶段、顺序

参考答案:

  1. 模块通过 IModuleInterface 的 StartupModule()(启动)与 ShutdownModule()(卸载)控制生命周期;
  2. LoadingPhase(从早到晚):EarliestPossible → PostConfigInit → PreLoadingScreen → PreDefault → Default(最常用)→ PostDefault → PostEngineInit → PostSplashScreen → None(手动);
  3. 加载顺序由依赖关系决定,被依赖的模块先加载;
  4. 选择建议:核心游戏逻辑 Default;需最早 PreDefault;编辑器扩展/可选功能 PostEngineInit。

9.3 什么是插件(Plugin)?与模块有什么区别?

★★ 中等

考点: uplugin、模块与插件差异

参考答案:

  1. 插件是可启用/禁用的功能扩展包,可包含多个模块 + Content 内容资产,以 .uplugin 描述;
特性 模块 插件
定义文件 .Build.cs .uplugin
启停 始终加载 可启用/禁用
内容 代码模块 多个模块 + 内容资源
分发 项目内部 支持市场
  1. 插件模块类型:Runtime(打包保留)、Developer、Editor、UncookedOnly;
  2. .uplugin 示例:
{
  "FileVersion": 3, "FriendlyName": "My Plugin", "Category": "Gameplay",
  "Modules": [ { "Name": "MyRuntime", "Type": "Runtime", "LoadingPhase": "Default" },
               { "Name": "MyEditor",   "Type": "Editor", "LoadingPhase": "PostEngineInit" } ]
}

9.4 什么是 Subsystem?有哪些类型?

★★ 中等

考点: 服务化架构、生命周期、全局访问

参考答案:

  1. Subsystem 是服务化架构(全局/特定上下文),自动管理生命周期、全局唯一访问;
  2. 类型:
    • UEngineSubsystem(引擎级,生命周期同引擎);
    • UGameInstanceSubsystem(游戏实例级);
    • UWorldSubsystem(世界级);
    • ULocalPlayerSubsystem(玩家级,每玩家一个);
    • UDynamicSubsystem(动态创建/销毁,UE5.1+);
  3. 获取:GetGameInstance()->GetSubsystem<T>() / GEngine->GetEngineSubsystem<T>();
  4. 优势:替代传统全局单例模式、生命周期自动管理、WorldSubsystem 支持网络复制。

9.5 如何实现自定义的引擎子系统?

★★ 中等

考点: 子系统实现、Initialize/Deinitialize

参考答案:

  1. 继承对应基类(如 UEngineSubsystem),覆盖 Initialize/Deinitialize 初始/清理资源;资源用 UPROPERTY 持有;不自行创建单例,用 GEngine->GetEngineSubsystem<T>() 获取:
UCLASS()
class UAudioMgr : public UEngineSubsystem {
    GENERATED_BODY()
public:
    virtual void Deinitialize() override {
        for (UAudioComponent* C : Active) { if (C) C->Stop(); }
        Active.Empty();
        Super::Deinitialize();
    }
    UPROPERTY() TArray<UAudioComponent*> Active;
};
// GEngine->GetEngineSubsystem<UAudioMgr>();

9.6 什么是 GameFeature 系统?

★★★ 困难

考点: 模块化功能、DLC、按需加载

参考答案:

  1. UE5 的模块化游戏功能系统,将功能打包为可独立启用/禁用的单元(插件形式 GameFeaturePlugin);
  2. 核心概念:GameFeatureData(数据资产)、GameFeatureAction(激活时执行的动作:加 Actor、注册输入、加 UI、加 Ability 等)、UGameFeaturesSubsystem(统一激活/停用管理);
  3. 场景:DLC/赛季内容模块化、A/B 测试切换、按需加载减少内存、Lyra 中广泛使用。

9.7 UBT(Unreal Build Tool)的工作原理是什么?

★★★ 困难

考点: UBT、构建系统、与 UHT 区别

参考答案:

  1. UBT 是 UE 核心构建系统:解析模块依赖图、生成编译器命令、增量编译(PCH/Unity Build)、多平台支持、并行/分布式编译;
  2. 流程:读 .uproject + 各 Build.cs 建立依赖图 → 确定平台/配置 → 生成编译命令调用平台编译器(MSVC/Clang)→ 链接 → 后处理;
  3. 命令行:UnrealBuildTool MyProject Win64 Development、-clean、-projectfiles;
  4. 与 UHT 区别:
    • UBT = C++ 编译/链接、模块依赖、构建配置;
    • UHT = 解析反射宏、生成 .generated.h(反射/序列化/网络注册代码);
    • 顺序:UHT 先运行,UBT 再编译其生成的代码。

十、委托与事件

10.1 虚幻引擎的委托系统有哪些类型?

★★ 中等

考点: 委托类型、动态/原生、蓝图

参考答案:

  1. 单播委托 DECLARE_DELEGATE:只能绑定一个函数,直接调用、性能最高;
  2. 多播委托 DECLARE_MULTICAST_DELEGATE:可绑定多个函数,Broadcast 依次触发;
  3. 动态单播/多播委托 DECLARE_DYNAMIC_*:支持蓝图绑定与序列化,基于反射性能略低;
  4. 选择:C++ 内部高频用原生;需要蓝图绑定用动态;事件通知用多播;对外暴露用事件。
DECLARE_DELEGATE(FOnSimpleAction);
DECLARE_DELEGATE_OneParam(FOnHealthChanged, float);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDamageTaken, float);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDeath, AActor*, Actor);

10.2 如何声明和使用委托?

★ 简单

考点: 声明宏、绑定、触发

参考答案:

  1. 声明宏 → 绑定 → 触发;单播用 ExecuteIfBound,多播用 Broadcast:
OnHealthChanged.BindUObject(this, &AMyChar::HandleHealth);
OnHealthChanged.ExecuteIfBound(NewHealth);
OnDamageTaken.AddUObject(this, &AMyChar::HandleDamage);
OnDamageTaken.Broadcast(Damage);

10.3 委托支持哪些绑定方式?

★★ 中等

考点: 绑定方式、安全性

参考答案:

方式 安全性 场景
BindUObject 高,自动检查有效 UObject 成员函数
BindStatic 高 全局/静态
BindRaw 低,不检查 非 UObject 原始指针
BindLambda 依捕获 简短回调
BindSP 高(弱引用) TSharedPtr 管理对象
BindDynamic 高 蓝图函数

10.4 委托和事件(Event)有什么区别?

★★ 中等

考点: 封装性、观察者模式

参考答案:

  1. 委托:外部可绑定/解除/触发(任何人可 Broadcast 多播),灵活但封装性差;
  2. 事件:DECLARE_EVENT 封装,只能在声明者内部 Broadcast,对外只暴露绑定接口,符合观察者模式、封装更好;
  3. 最佳实践:对外暴露事件用 DECLARE_EVENT,内部回调用 DECLARE_DELEGATE。
DECLARE_EVENT_OneParam(UHealthComp, FOnHealthChanged, float);
class UHealthComp : public UActorComponent {
public:
    FOnHealthChanged& HealthChangedEvent() { return Ev; } // 外部只能绑定
private:
    FOnHealthChanged Ev; // 仅本类 Broadcast
};

10.5 使用 Lambda 绑定委托有什么注意事项?

★ 简单

考点: Lambda 捕获、生命周期

参考答案:

OnHealthChanged.BindLambda([Threshold](float H) { if (H < Threshold) LowHealth(); });
OnDamageTaken.AddLambda([this](float D) { Health -= D; });
  1. 捕获 this 的 Lambda 若对象先销毁会悬空指针 → 需在销毁前解除;
  2. Lambda 无法被单独解绑(多播可用 Handle);
  3. 捕获变量的 Lambda 比无捕获略慢;
  4. 动态委托不支持 Lambda。

10.6 委托的性能特点与优化?

★★★ 困难

考点: 委托开销、性能排序

参考答案:

  1. 性能排序:直接函数调用 < 原生单播 < 原生多播 < 动态单播 < 动态多播;
  2. 优化:高频回调用原生委托;避免在 Tick 中触发委托;及时 Remove(Handle);清空用 Clear()。

10.7 委托如何与蓝图系统交互?

★★ 中等

考点: 动态委托、BlueprintAssignable、蓝图绑定机制

参考答案:

  1. 只有 DECLARE_DYNAMIC_* 声明的动态委托才能暴露给蓝图,因为动态委托基于反射,蓝图通过函数名字符串查找来绑定;
  2. 关键 UPROPERTY 说明符:
    • BlueprintAssignable:暴露属性到蓝图事件图表,蓝图可绑定该委托(通常用于多播事件);
    • BlueprintCallable:蓝图可直接调用该委托(如单播委托的 Execute);
    • 二者不可同时用于同一委托属性;
  3. 声明动态委托时,参数名必须是合法的 C++ 标识符,蓝图中显示的参数名来自声明时的名称:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnScoreChanged, int32, OldScore, int32, NewScore);
DECLARE_DYNAMIC_DELEGATE_RetVal(float, FCalculateDamage);
  1. 蓝图端绑定步骤:选中拥有委托属性的 Actor → 事件图表 → 右键"Bind Event to OnScoreChanged" → 拖出连线选择目标事件;或用 Bind Event 节点绑定到自定义事件;
  2. C++ 触发蓝图委托的完整示例:
// 头文件:声明动态多播委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);

UCLASS()
class AMyCharacter : public ACharacter {
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintAssignable, Category = "Events")
    FOnHealthChanged OnHealthChanged;

    void TakeDamage(float Amount) {
        Health -= Amount;
        OnHealthChanged.Broadcast(Health);  // 触发蓝图中所有绑定的事件
    }
private:
    UPROPERTY()
    float Health = 100.f;
};
  1. 单播 vs 多播在蓝图中的区别:
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE:蓝图可绑定多个事件,Broadcast 依次触发;适合事件通知模式;
    • DECLARE_DYNAMIC_DELEGATE:蓝图只能绑定一个回调,通过 Bind 节点设置;适合回调模式(如异步操作完成);
    • 蓝图中多播委托的"事件"节点会自动创建可绑定的事件入口;
  2. 易错点:
    • 动态委托的绑定/触发基于函数名字符串查找,性能比原生委托差约 5-10 倍,高频调用场景避免使用;
    • 动态委托必须配合 UFUNCTION() 标记的函数才能在蓝图中绑定(BindDynamic 内部用 UFunction 查找);
    • 动态委托不支持 Lambda 绑定(无 BindLambda);
    • 参数类型必须是蓝图支持的类型(UObject 指针、基础类型、USTRUCT);
    • 蓝图中绑定的事件函数命名不能与 C++ 原生函数重名冲突;
  3. 面试点评:此题考察对蓝图反射机制与委托绑定原理的理解,重点是动态委托的反射本质(函数名查找)及性能影响,面试中应能说清 BlueprintAssignable/BlueprintCallable 的区别、动态与原生委托的性能差异,以及为什么动态委托不支持 Lambda。

10.8 如何安全地解除委托绑定?

★★ 中等

考点: 解除绑定、防崩溃

参考答案:

  1. 单播 Unbind();多播 Remove(Handle) / RemoveAll(this) / Clear();
  2. BindUObject 绑定在对象销毁后自动失效;BindRaw 不安全需手动解除;捕获 this 的 Lambda 需主动断开;
  3. Broadcast 过程中解除绑定安全;动态委托对象销毁时自动解除;
  4. 推荐在 BeginDestroy 中 RemoveAll(this)。

十一、序列化、存档与网络

11.1 UObject 的序列化机制是什么?

★★★ 困难

考点: Serialize、FArchive、序列化类型

参考答案:

  1. 通过 virtual void Serialize(FArchive&) 实现:基类数据 → 遍历 UPROPERTY(排除 Transient)→ 按 Ar.IsLoading()/Ar.IsSaving() 读写;
  2. 自定义 Serialize 时注意版本兼容;
  3. 类型:二进制(存档/网络)、文本(配置/调试)、JSON(跨平台/Web);
  4. 内存往返:FBufferArchive(写)、FMemoryReader(读)。
virtual void Serialize(FArchive& Ar) override {
    Super::Serialize(Ar);
    Ar << CustomValue;
}

11.2 如何实现游戏存档系统?

★★ 中等

考点: USaveGame、存档 API

参考答案:

  1. 定义 USaveGame 子类,其 UPROPERTY 字段自动序列化;
  2. 保存:UGameplayStatics::CreateSaveGameObject → 填数据 → SaveGameToSlot;
  3. 加载/检测/删除:LoadGameFromSlot、DoesSaveGameExist、DeleteGameInSlot;
  4. 存档文件存平台用户目录;大存档用异步避免卡顿;引入版本号管理兼容。

11.3 网络序列化是如何工作的?

★★★ 困难

考点: 属性复制、RPC、DOREPLIFETIME

参考答案:

  1. 属性复制:UPROPERTY(ReplicatedUsing = OnRep_Health) + GetLifetimeReplicatedProps + DOREPLIFETIME;基于脏标记只同步变化的属性;条件复制 COND_OwnerOnly、COND_SkipOwner 等;
  2. RPC:
UFUNCTION(Server, Reliable) void Server_Fire(FVector Origin, FVector Dir);   // 客户端→服务端
UFUNCTION(Client, Reliable) void Client_PlayHitEffect(float Damage);         // 服务端→指定客户端
UFUNCTION(NetMulticast, Unreliable) void Multicast_Explosion(FVector Loc);   // 服务端→所有
  1. Reliable 保证顺序与送达,Unreliable 允许丢失(高频预测用)。

11.4 如何实现自定义序列化?

★★★ 困难

考点: Serialize 重写、FArchive 操作符

参考答案:

  1. 重写 Serialize + FArchive 操作符:
virtual void Serialize(FArchive& Ar) override {
    Super::Serialize(Ar);
    Ar << MyData;
    // Ar.IsLoading() 时按版本分支读取
}
  1. 复杂 UStruct 定义 friend FArchive& operator<<(FArchive& Ar, FMyStruct& Data);
  2. 大数据块用 BulkData;始终考虑版本兼容。

11.5 序列化中的版本控制如何实现?

★★★ 困难

考点: CustomVersion、兼容策略

参考答案:

  1. 用 Ar.UsingCustomVersion(GUID) + 自定义枚举版本:
int32 MyVer = Ar.CustomVer(FMyCustomVersion::GUID);
if (MyVer >= FMyCustomVersion::AddedNewField) { Ar << NewField; }
else { /* 旧版本数据转换 */ }
  1. 模块启动时注册:FCustomVersionContainer::Get().RegisterCustomVersion(GUID, LatestVer);
  2. 策略:只新增字段、不删改旧字段;提供旧→新转换逻辑;保持序列化字段顺序一致;记录每次结构变更。

11.6 BulkData 序列化是什么?适用什么场景?

★★ 中等

考点: BulkData、大数据块

参考答案:

  1. FByteBulkData 等将大数据块独立于主序列化流,支持异步加载、内存映射、压缩;
  2. 避免把大块数据混进主序列化流,减少序列化开销与内存占用;
  3. 场景:纹理与图像、音频波形、大型网格数据等不频繁修改的大数据块。

十二、UE5 渲染新技术

12.1 UE5 是什么?与 UE4 相比有哪些重大更新?

★★ 中等

考点: UE5 认知、核心新特性、技术选型

参考答案:

  1. UE5(2022 正式发布)面向"电影级实时";
  2. 三大核心:
    • Nanite:虚拟化几何体,GPU-driven 自动像素级 LOD(Static Mesh 启用);
    • Lumen:实时全局光照/反射(Software RT 默认,可选 Hardware RTX);
    • World Partition:取代 World Composition,自动分区 + One File Per Actor;
  3. 其他:Virtual Shadow Maps(替代 CSM)、TSR(替代 TAAU)、MetaSounds、Mass Entity、PCG、Enhanced Input、Common UI;
  4. UE4→UE5 关键对比:手动 LOD→Nanite;烘焙 Lightmass→Lumen;World Composition→World Partition;CSM→VSM;TAAU→TSR;传统 Input→Enhanced Input;
  5. 迁移注意:Nanite 骨骼网格仍受限(UE5.4 实验)、半透明材质不支持 Nanite。

12.2 Nanite 技术深入解析

★★★ 困难

考点: Cluster 架构、GPU-driven 渲染、与 LOD 区别

参考答案:

  1. 本质:虚拟化微多边形几何体,Cluster-based 层次结构(每 Cluster 约 128 个三角),运行时按屏幕像素覆盖选择细节("像素级 LOD");
  2. GPU-driven 管线:CPU 一次性提交全部 Cluster,GPU 执行剔除、LOD 选择、软件光栅化、Deferred Shading;CPU 绘制调用开销极低;
  3. vs 传统 LOD:传统离散 LOD 有跳变、手动制作、面数分配不均;Nanite 连续像素级、无跳变、自动构建;
  4. 限制:骨骼网格(UE5.4 实验性)、半透明材质、自定义深度/模板、World Position Offset(顶点动画)、自定义 Vertex Shader、部分顶点颜色受限;
  5. 性能调节:r.Nanite.MaxPixelsPerEdge、r.Nanite.MaxVisibleClusters;可视化 r.Nanite.Visualize Overview/Triangles/Clusters。

12.3 Lumen 全局光照系统

★★★ 困难

考点: Lumen 原理、光线追踪模式、性能优化

参考答案:

  1. Lumen 是实时全局光照 + 反射,无需烘焙;
  2. 技术栈:Surface Cache(材质/几何缓存)+ Screen Space Tracing(近距) + Global Tracing(远距离用 Lumen Scene 简化表示)+ Temporal Accumulation(时间累积降噪);
  3. 两种模式:
    • Software RT(默认):SDF + Surface Cache,无需 RTX、性能友好;
    • Hardware RT:RTX 加速、精度高噪点少、开销大;
  4. vs Lightmass 烘焙:烘焙质量高、运行时开销低但静态;Lumen 支持动态光照/昼夜、无需烘焙、但运行时开销大、有噪点;
  5. 性能优化:用 Software 模式、降低追踪距离(r.Lumen.DiffuseIndirect.MaxTraceDistance)、降低反射复杂度、简化场景。

12.4 什么是 Virtual Shadow Maps(VSM)?

★★★ 困难

考点: VSM 原理、虚拟纹理、与 Nanite 集成

参考答案:

  1. VSM 用虚拟纹理管理阴影:16K 虚拟空间 + 物理页面按需分配,只渲染相机可见区域,支持缓存;
  2. vs CSM:CSM 固定分辨率、近好远差、边缘锯齿、与 Nanite 不兼容;VSM 均匀高质量、无锯齿、与 Nanite 深度共享几何、内存按需;
  3. 与 Nanite 协同:共享 Cluster 数据、GPU 管线,获得极高阴影精度;
  4. 关键配置:r.Shadow.Virtual.Enable、MaxPhysicalPages、ResolutionLodBiasLocal、Cache,可视化命令调试。

十三、World Partition 与 Data Layer

13.1 UE5 的 World Partition 与 Data Layer

★★★ 困难

考点: WP、OFPA、HLOD、Data Layer、场景切换

参考答案:

  1. World Partition:UE5 大世界架构,按 Runtime Grid 自动分区(主/细节/HLOD 多级),Actor 按位置分配到 Cell;Streaming Source(通常是玩家)决定加载范围;可用 AWorldPartitionStreamingSourceComponent 自定义源;
  2. OFPA(One File Per Actor):每个 Actor 独立外部文件,多人可同时编辑、版本控制冲突最小化、支持增量保存;
  3. HLOD:将远距离多个 Actor 合并为简化代理网格(Actor 级 LOD),降低远景开销;
  4. Data Layer:Actor 逻辑分组,运行时可独立控制;一个 Actor 可属多个 Layer;
    • 类型:Editor Data Layer(仅编辑器)/ Runtime Data Layer(运行可控);
    • 状态:Unloaded(不可见、不 Tick)→ Loaded(加载未激活)→ Activated(完全功能);
  5. 运行时控制(白天/夜晚等场景切换):
UDataLayerManager* M = UDataLayerManager::GetDataLayerManager(GetWorld());
UDataLayerInstance* L = M->GetDataLayerInstance(TEXT("Night"));
M->SetDataLayerRuntimeState(L, EDataLayerRuntimeState::Activated);

或经 UWorldPartitionSubsystem 取 Layer 并控制可见性/碰撞/Tick; 6. 协同关系:WP 管空间分区与加载,Data Layer 管逻辑分组与可见性,二者独立工作; 7. 综合示例:MainGrid + 任务 Data Layer + 日夜 Layer,用任务生命周期驱动 SetDataLayerRuntimeState。

十四、ECS 与 Mass Entity

14.1 什么是 ECS 架构?与传统 OOP 的区别?

★★ 中等

考点: ECS 概念、数据/逻辑分离

参考答案:

  1. 概念:
    • Entity:唯一 ID,无数据逻辑;
    • Component:纯数据容器;
    • System:逻辑,查询并处理特定 Component 组合的实体;
  2. 与传统 OOP(UE Actor)对比:数据组织(类封装 vs 数据/逻辑分离)、内存布局(堆分散 vs Chunk 连续存储)、缓存友好度、扩展性(继承 vs 组合)、性能(适合少量复杂对象 vs 大量简单对象);
  3. 优势:数十万~百万实体时连续存储 + SIMD + 并行,缓存命中率与吞吐量大幅提升。

14.2 Mass Entity 框架是什么?

★★★ 困难

考点: Mass ECS、Fragment、Processor

参考答案:

  1. UE5 高性能 ECS 框架:数据连续存储/SIMD、可管理数十万~数百万实体、System 可并行;
  2. 核心组成:UEntityConfig(配置)、FMassEntity(实体)、FMassFragment(数据单元)、FMassTrait(特征构建器)、FMassProcessor(System)、FMassChunk(同构数据块);
  3. 主要应用:人群模拟、交通、大规模粒子、RTS 单位。

14.3 Mass Entity 中的 Chunk 是什么?

★★★ 困难

考点: Archetype、连续内存、高效遍历

参考答案:

  1. Chunk 是 Mass 存储实体的基本单元:相同 Fragment 组合(Archetype)的实体连续存放在同一 Chunk,缓存友好;
  2. Processor 按 Chunk 高效遍历(ForEachEntityChunk + GetFragmentView);
  3. 实体的 Fragment 组合变化时会在不同 Chunk 间迁移;
  4. 优化:减少 Archetype 数量(避免过多小 Chunk)、Processor 只取所需 Fragment、按 Chunk 并行处理。

14.4 Mass 中的 Trait 与 Fragment 的关系?

★★ 中等

考点: 配置与数据、组合

参考答案:

  1. Trait 是"配置":定义实体创建时自动添加哪些 Fragment 及初始值;Fragment 是"数据";
  2. 一个 Trait 可添加多个 Fragment;多个 Trait 可添加同一 Fragment(后添加覆盖先添加);
  3. 用 UEntityConfig 组合多个 Trait 得到实体。

14.5 Mass Entity 适用哪些游戏场景?

★★ 中等

考点: 适用/不适用场景、性能对比

参考答案:

  1. 适用:人群模拟、交通系统、RTS 大量单位、生态模拟、大规模粒子;
  2. 不适用:少数复杂对象(用 Actor/Component 更合适)、需要编辑器可视化编辑、需要深度蓝图集成、需要网络复制(Mass 目前不直接支持);
  3. 性能参考:大量 Actor(千级)每帧需要数 ms,Mass 在数十万实体级仍保持高效(数据连续 + 并行)。

十五、项目结构与构建配置

15.1 UE 项目的目录结构规范

★★ 中等

考点: 项目结构、目录规范、可维护性

参考答案:

  1. 标准结构概要:
MyProject/
├── MyProject.uproject      # 项目描述、模块/插件引用
├── Config/                 # DefaultEngine/Game/Input/Editor/Scalability.ini
├── Content/                # Blueprints/Materials/Textures/Meshes/Maps 等资产
├── Source/MyProject/       # Build.cs + Public/ + Private/ 模块源码
├── Plugins/                # 项目插件
└── Saved/                  # 生成数据:Config/Logs/Autosaves
  1. 组织规范:Content 按功能模块组织,Source 按模块职责;资产命名前缀 BP_(蓝图)、M_(材质)、T_(纹理)、SM_/SK_(网格)、Anim_、BPI_(蓝图接口)、E_(枚举)、F_(结构);
  2. 大型项目:按功能模块拆插件(GameplayCore/Combat/Inventory/UI),提升可维护性与编译隔离。

15.2 .uproject 文件的作用与配置?

★★ 中等

考点: 项目配置、模块管理、插件配置

参考答案:

  1. .uproject 是项目核心配置:FileVersion/EngineAssociation 元数据、Modules(Name/Type/LoadingPhase)、Plugins(启停)、TargetPlatforms;
  2. 模块依赖在 Build.cs 中管理(Public/Private 依赖、平台条件依赖、第三方库);
  3. 插件的启用/禁用可在编辑器 Edit → Plugins 中管理,等价于修改 .uproject 的 Plugins 段;
  4. 与 Target.cs/Build.cs 配合,由 UBT 解析执行。

十六、编辑器与工作流

16.1 UE 编辑器常用面板与工作流

★ 简单

考点: 编辑器基本操作、开发效率

参考答案:

  1. 核心面板:Viewport(视口:导航/聚焦/模式切换)、Outliner(大纲:层级/搜索/隐藏/锁定)、Details(细节:属性/搜索/重置/添加组件)、Content Browser(内容浏览器:资产管理/引用/大小)、World Settings(世界设置:GameMode/重力/KillZ)、Output Log(输出日志);
  2. 标准工作流:导入资产 → 放置 Actor → Details 配置 → 编写逻辑(C++/蓝图)→ PIE 测试 → 调试(Output Log/Visual Logger/断点)→ 打包;
  3. 提效:Ctrl+Space 资产菜单、Reference Viewer/Size Map 审计、Editor Utility Widget/Blutility 自定义工具、启动参数(-game/-log)。

16.2 UE 编辑器常用快捷键与工作流技巧

★ 简单

考点: 编辑器操作、快捷键、工作效率

参考答案:

  1. 视口:右键拖动旋转、右键+WASD 飞行、F 聚焦、Alt+左键环绕、W/E/R 移动/旋转/缩放、End 落地面、Alt+拖动复制、Ctrl+1~4 视图切换、Alt+1/2/3/4 显示模式;
  2. 资产与内容:Ctrl+S 保存、Ctrl+Shift+S 全部保存、Ctrl+P 快速打开、F2 重命名、Ctrl+D/W 复制、Ctrl+Shift+F 全局查找;
  3. 蓝图与调试:F7 编译、F9 断点、F10/F11 单步/步入、Ctrl+G 跳转定义;~ 打开控制台输入 stat fps/stat unit;
  4. 工作流技巧:Ctrl+P 模糊搜索、批量选中编辑、Reference Viewer 查依赖、视口书签(Ctrl+数字保存/恢复);
  5. 可在 Editor Preferences 自定义快捷键。

说明

  • 本章为两份源文件《第一章_基础概念与引擎架构》《engine-architecture》全部题目的合并去重结果;重复题保留内容最完整的版本并将补充关键点合并进同一题;小节与题号连续编号,共 77 道题。