虚幻引擎_核心框架

一、UE 核心框架整体分层

UE 的核心框架采用分层设计,底层屏蔽平台差异,上层提供游戏开发的通用能力,整体可分为 4 层:

层级 核心职责 关键组件 / 模块
平台抽象层 屏蔽 Windows/PS/Xbox/ 移动端等平台的底层差异,提供统一接口 Core/Public/Platform(平台 API 封装)、RHIs(渲染硬件接口,如 DX12/Vulkan/Metal)
核心系统层 引擎的基础支撑,提供内存管理、线程、反射、GC、序列化等核心能力 UObject 体系、TaskGraph(线程任务)、Memory Manager、Serialization System
引擎层 游戏开发的核心能力集,提供渲染、物理、音频、输入、资源管理等通用功能 Renderer、PhysX、Audio Engine、Input System、Asset Manager
游戏框架层 基于引擎层封装的游戏专属逻辑框架,提供开箱即用的游戏对象和规则 World、Actor/Component、GameMode/GameState、PlayerController、UI 系统(UMG)

核心设计原则

  • 高内聚低耦合:每层仅依赖下一层,模块间通过接口通信,便于扩展和替换(如替换渲染后端、物理引擎);
  • 可定制化:底层核心系统稳定,上层游戏框架可通过 C++/ 蓝图自定义;
  • 多线程并行:核心逻辑拆分到不同线程,最大化利用多核 CPU。

二、核心线程模型(UE 框架的 "动力系统")

UE 核心线程配合逻辑 + 具体游戏实例-CSDN博客

1. 核心线程模型 (The "Big Three" + Others)

UE 的架构是典型的 "主从式 + 任务图" 混合结构。

|---------------------------|------------------------------------------------------------------------|-----------------------------------------------------------------|
| 线程名称 | 核心职责 | 关键特性 (必须要背的) |
| 游戏线程 (Game Thread) | **大管家 / 权威:**处理 Gameplay 逻辑、Blueprint 虚拟机、动画更新(部分)、AI 决策、UI 布局、网络逻辑同步。 | 只有它能修改 Actor/UObject 的状态。 所有其他线程要修改游戏数据,必须"请求"游戏线程来做。 |
| 渲染线程 (Render Thread) | 翻译官:接收游戏线程的命令,生成渲染指令。处理可视性裁剪 (Culling)、LOD 计算。 | 游戏线程运行比渲染线程快 1-2 帧 。 不能直接访问游戏线程的 UObject,只能访问其镜像数据 (Proxy)。 |
| RHI 线程 (RHI Thread) | **驱动层:**Render Hardware Interface。负责将渲染线程的指令提交给显卡驱动 (DX12/Vulkan)。 | 用于并行渲染,减轻渲染线程压力,防止驱动层阻塞。 |
| 音频线程 (Audio Thread) | **调音师:**处理声音播放请求、混音、DSP 音效处理、3D 空间化计算。 | 独立于游戏帧率,保证声音不卡顿。 |
| 物理任务 (Async Physics) | 物理模拟: 基于 Chaos 引擎。计算刚体碰撞、布料模拟、破坏效果。 | UE5 支持"异步物理 Tick",允许物理模拟频率与游戏帧率解耦。 |
| 工作线程池(Worker Threads) | 打工人 通过 TaskGraph 系统动态调度的线程池。处理光照构建、资源流式加载、复杂的数学计算。 | 用完即走,高度并行。 |

2. 线程通信机制

UE 严禁跨线程直接访问数据(会崩溃或死锁),必须使用以下机制:

|-----------------|----------------------------------|---------------------------------------------------------------------------------|
| 通信方向 | 核心机制 | 代码/宏示例 |
| 游戏线程 → 渲染线程 | 渲染命令队列(Render Command Queue) | ENQUEUE_RENDER_COMMAND(...) 游戏线程把数据打包成包,扔进队列就不管了(非阻塞)。 |
| 任意线程 → 游戏线程 | 任务图系统 (TaskGraph System) | FFunctionGraphTask::CreateAndDispatchWhenReady(...) 让游戏线程在下一帧空闲时执行某个回调函数。 |
| 游戏线程 ↔ 物理线程 | 双缓冲数据 (Double Buffering) | 物理引擎计算时使用的是数据的副本。计算完成后,在同步点 (Sync Point) 将位置/旋转写回 Actor。 |
| 线程同步与互斥 | 锁与原子操作 | FScopeLock (作用域锁), FThreadSafeCounter (原子计数器)。 警告:在 Gameplay 逻辑中应尽量避免用锁,改用任务通知。 |


三、模块系统(Module System)------ UE 框架的 "组织单元"

UE 的所有功能都以模块(Module) 为单位组织,模块是代码编译、链接、加载的最小单元,也是框架扩展性的核心:

1. 模块的核心特性

  • 模块化封装 :每个模块对应一个功能域(如CoreEngineRenderCoreUMG),内部代码高内聚;
  • 依赖管理 :模块间通过Build.cs声明依赖(公有 / 私有),引擎自动处理加载顺序;
  • 动态加载:支持运行时动态加载 / 卸载模块(如插件模块),降低内存占用;
  • 跨平台编译 :通过Target.cs配置不同平台的编译选项、宏定义、链接库。

2. 核心模块组成(UE 必选模块)

核心模块 职责
Core 最底层核心模块,提供基础类型(FString/FVector)、内存管理、线程、容器(TArray/TMap)
CoreUObject UObject 体系的核心模块,提供反射、GC、序列化、元数据系统
Engine 引擎核心模块,封装 Actor、World、输入、物理、音频等游戏基础功能
RenderCore 渲染核心模块,提供渲染硬件接口(RHI)、渲染资源管理
Renderer 渲染实现模块,提供光栅化、光照、粒子、后期处理等渲染逻辑
InputCore 输入核心模块,封装跨平台输入设备(键鼠 / 手柄 / 触屏)的统一接口

3. 模块配置文件

  • Build.cs:定义模块的名称、依赖、包含路径、链接库、编译选项(如:PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" }););
  • Target.cs:定义编译目标(客户端 / 服务器 / 编辑器)、平台配置、编译宏(如bBuildEditor = true;表示编译编辑器版本)。

四、UObject 体系

UObject 体系是 UE 框架最核心的设计,是所有游戏对象、资源的基础,支撑了反射、蓝图、GC、序列化等关键能力,也是 UE 区别于其他引擎的核心特征:

1. UObject 体系的结构

bash 复制代码
UObject (万物之源:垃圾回收 / 反射 / 序列化)
│
│  # 1. [实体与逻辑] World Actors
├─────── AActor (场景实体基类)
│           ├── APawn
│           │     └── ACharacter (胶囊体 + CharacterMovement)
│           ├── AController (控制者)
│           │     ├── APlayerController (玩家输入 / HUD / 摄像机管理)
│           │     └── AAIController (AI 寻路 / 行为树驱动)
│           ├── AInfo (纯逻辑)
│           │     ├── AGameModeBase (服务器端规则)
│           │     ├── AGameStateBase (全网同步的游戏状态)
│           │     └── APlayerState (全网同步的玩家个人数据:计分、名字)
│           ├── AHUD (传统的 UI 绘制类,用于调试或管理 Widget)
│           └── ACameraActor (摄像机演员)
│
│  # 2. [组件系统] Components (能力的积木)
├─────── UActorComponent (逻辑组件)
│           ├── USceneComponent (带 Transform 坐标)
│           │     ├── UPrimitiveComponent (渲染与物理)
│           │     │     ├── UStaticMeshComponent
│           │     │     ├── USkeletalMeshComponent (带动画)
│           │     │     ├── UShapeComponent (碰撞盒: Box/Sphere/Capsule)
│           │     │     └── UParticleSystemComponent (粒子特效)
│           │     ├── UAudioComponent (音效)
│           │     ├── UCameraComponent (摄像机视角)
│           │     └── UChildActorComponent (在 Actor 里嵌套另一个 Actor)
│           ├── UMovementComponent (移动逻辑)
│           │     ├── UProjectileMovementComponent (投射物/子弹)
│           │     └── URotatingMovementComponent (自转)
│           └── UInputComponent (接收按键输入)
│
│  # 3. [核心框架] Framework & System
├─────┬── UWorld (地图容器,管理 Level / Physics / Rendering)
│     ├── ULevel (关卡数据)
│     ├── UGameInstance (全局单例,跨关卡持久化)
│     ├── ULocalPlayer (本地玩家代表,管理分屏和 Slate 视口)
│     ├── USaveGame (存档对象,序列化成 .sav 文件)
│     └── USubsystem (生命周期自动管理的子系统)
│           ├── UGameInstanceSubsystem (如:成就系统)
│           ├── UWorldSubsystem (如:怪物生成器)
│           └── UEditorSubsystem (仅编辑器工具)
│
│  # 4. [动画系统] Animation (角色表现的核心)
├─────┬── UAnimInstance (动画蓝图的基类,处理动画状态机)
│     ├── UAnimSequence (动画序列资源)
│     └── UAnimMontage (动画蒙太奇,用于播放攻击动作等)
│
│    # 5. [UI 系统] User Interface
├──── UVisual
│        └── UWidget (Slate 包装器)
│                 ├── UUserWidget (UMG 蓝图基类)
│                 └── UPanelWidget (布局容器:CanvasPanel, VerticalBox)
│
│  # 6. [静态资产] Assets (Content Browser 中的文件)
├─────┬── UDataAsset (通用数据表)
│     ├── UStreamableRenderAsset
│     │     ├── UTexture (纹理)
│     │     └── UStaticMesh / USkeletalMesh (模型)
│     ├── USoundBase
│     │     ├── USoundCue (音频逻辑图)
│     │     └── USoundWave (音频源文件)
│     ├── UMaterialInterface (材质)
│     ├── UInputMappingContext / UInputAction (UE5 增强输入资源)
│     └── UBehaviorTree / UBlackboardData (AI 行为树资源)
│
│ # 7. [反射与元数据] Reflection
├────── UField
│           ├── UStruct
│           │     ├── UClass (类的类型信息)
│           │     ├── UFunction (函数)
│           │     └── UScriptStruct (结构体)
│           └── UEnum (枚举)
│
│ # 8. [接口] Interface
└────── UInterface (C++ 接口基类)

2. 各子类详细解析:

👑 根基:UObject

作用:所有 UE 类的祖先。提供垃圾回收 (GC)、反射 (Reflection)、序列化 (Serialization) 和网络复制的基础。

常用函数

  • CreateDefaultSubobject<T>("Name"):仅构造函数用。创建组件或子对象。

  • GetWorld():获取当前对象所属的世界(用于生成 Actor、设置定时器等)。

  • GetName():获取对象的名称字符串。

  • IsA(UClass*):判断当前对象是不是某个类(或子类)。

1.1. 🌍 实体与逻辑 (World Actors)

所有能被 Spawn 到世界里的东西。

AActor

作用:世界中存在的实体的基类。

常用函数

  • BeginPlay():游戏开始时调用(初始化逻辑写这)。

  • Tick(float DeltaTime):每帧调用(高频逻辑)。

  • GetActorLocation() / SetActorLocation():获取/设置位置。

  • Destroy():销毁自己。

  • SetActorHiddenInGame(bool):隐藏/显示。

  • GetDistanceTo(OtherActor):计算距离。

AActor -> APawn (继承自 Actor)

作用:可以被 Controller "附身 (Possess)" 的代理体。

常用函数

  • AddMovementInput():输入移动向量(需配合 MovementComponent)。

  • GetController():获取当前控制它的控制器。

  • PossessedBy(Controller):当被附身时触发的回调。

AActor -> APawn -> ACharacter (继承自 Pawn)

作用:人形角色,自带胶囊体碰撞和强大的移动组件。

常用函数

  • Jump() / StopJumping():跳跃逻辑。

  • Crouch() / UnCrouch():蹲伏逻辑。

  • GetCharacterMovement():获取移动组件指针(修改速度、重力等)。

AActor -> AController

作用:控制 Pawn 的大脑。

常用函数

  • Possess(APawn*):控制某个 Pawn。

  • UnPossess():释放当前 Pawn。

AActor -> AController -> APlayerController

  • GetHUD():获取 HUD 实例。

  • GetMousePosition(...):获取鼠标在屏幕的位置。

  • SetInputMode(...):切换 UI 模式或游戏模式。

AActor -> AController -> AAIController

  • MoveToActor(...):让 AI 走到目标面前。

  • RunBehaviorTree(...):运行行为树。

AActor -> AInfo (及其子类)

AActor -> AInfo -> AGameModeBase

作用

bash 复制代码
核心管理:控制玩家进入流程(PreLogin/Login/PostLogin)。
角色生成:决定默认生成哪个 Pawn、哪个 Controller 和哪个 HUD。
位置选择:决定玩家在哪个 PlayerStart 处出生。
权限模型:纯服务器逻辑,不允许客户端修改,保证游戏公平。

函数:SpawnDefaultPawnAtTransform(...)(生成玩家)、PostLogin(...)(玩家连入时触发)。

bash 复制代码
AGameModeBase (基类:定义游戏基础规则、玩家进入、Pawn生成)
│
├── AGameMode (引擎核心子类:增加了"匹配状态机" Match State)
│     │
│     ├── AShooterGameMode (UE官方示例:如《暗影枪神》中的对战模式实现)
│     │   # 功能:处理杀敌得分规则、连杀奖励逻辑、团队平衡校验。
│     ├── ACommonGameMode (Modular Gameplay 插件中用于模块化开发的变体)
│     └── [YourProject]GameMode (你的项目自定义:如 AMyGameMode)
│         # 功能:处理特定的胜负条件(如:塔楼全灭则胜利)、关卡特有的初始化逻辑。
├── ALyraGameMode (Lyra 示例项目:现代模块化架构,支持通过 Experience 定义规则)
│   # 功能特性:
│   # Experience 系统:不再硬编码规则,而是根据"游戏体验"配置动态加载不同的规则集。
│   # 集成模块化插件:能够动态启用或禁用特定的游戏功能插件。
│   # 高度可扩展:适合支持多种不同玩法模式(如:团战、占点、夺旗)的同一个游戏。
│
├── ATemplateGameModeBase (各种项目模板生成的默认类)
│     ├── AThirdPersonGameMode (第三人称模板)
│     │   # 功能:自动关联 ThirdPersonCharacter 和控制逻辑。
│     ├── AFirstPersonGameMode (第一人称模板)
│     ├── AVRGameMode (VR模板)
│     └── ATopDownGameMode (顶视角模板)
│
└── AOnlineGameMode (某些旧版插件中针对网络对战优化的基类)

AActor -> AInfo -> AGameModeBase -> AGameMode

作用

bash 复制代码
Match State 管理:支持 WaitingToStart(等待)、InProgress(进行中)、LeavingMap(离开地图)等状态。
比赛流程控制:提供 BeginMatch()、EndMatch()、RestartGame() 等控制函数。
超时逻辑:处理玩家长时间不操作或比赛时限到的逻辑。
状态同步:与 AGameState 配合,将比赛进度广播给所有客户端。

AActor -> AInfo -> AGameStateBase

作用

bash 复制代码
时间同步:提供 GetServerWorldTimeSeconds(),确保所有客户端的时钟与服务器对齐。
玩家列表:维护 PlayerArray(存储所有 APlayerState),用于获取当前房间内的所有玩家。
基础同步:负责最基本的游戏数据同步(如:关卡是否加载完成)。
对应关系:通常与 AGameModeBase 配对使用。

函数:GetServerWorldTimeSeconds()(获取服务器时间)。

bash 复制代码
AGameStateBase (基类:负责同步世界时间、基础游戏状态,存在于服务器和所有客户端)
│
├── AGameState (引擎核心子类:增加了"匹配状态"的同步,与 AGameMode 配合使用)
│     │  # 匹配状态同步:同步MatchState变量, 如:WaitingToStart, InProgress, WaitingPostMatch)。
│     │  # 流程广播:当比赛状态改变时,通知所有客户端更新 UI(如:显示"游戏开始"或"结算画面")。
│     │  # 比赛时长:通常在此处处理比赛剩余时间的倒计时同步。
│     ├── AShooterGameState (UE官方示例:同步复杂的对战数据,如全队总击杀数)
│     │   #功能:专门针对射击游戏优化,同步团队得分,当前占领点进度,每局剩余时间等复杂竞技数据。
│     └── [YourProject]GameState (你的项目自定义:如 ABattleBlasterGameState)
│
├── ALyraGameState (Lyra 示例项目:现代架构,负责管理活跃的游戏插件和模块化状态)
│    # 功能: 支持"Game Experience"系统,能根据不同的插件动态改变同步的数据;|
│    # 集成高级能力系统(GAS)的全局状态。
├── ATemplateGameStateBase (各种项目模板生成的默认类)
│     ├── AThirdPersonGameState (第三人称模板)
│     ├── AFirstPersonGameState (第一人称模板)
│     └── ...其他模板
│
└── ANetworkTestGameState (内部用于网络压力测试或调试的变体)

AActor -> AInfo -> APlayerState

作用:同步玩家个人数据(即使 Pawn 死了/被销毁,这个还在)。

函数:GetPlayerName()、SetScore()。

bash 复制代码
APlayerState (基类:负责同步玩家非物理状态数据,如分数、延迟、名称)
│
├── ALyraPlayerState (Lyra 示例项目:现代模块化架构的典范)
│       # 功能:支持团队分配、高级角色属性同步、经验值与等级系统、与 GAS 组件集成
│
├── AModularPlayerState (Modular Gameplay 插件子类)
│       # 功能:允许通过"游戏特性"插件动态地向玩家状态添加组件或行为
│
├── AShooterPlayerState (官方 ShooterGame 示例)
│       # 功能:专门处理射击游戏逻辑,如:击杀数 (Kills)、死亡数 (Deaths)、助攻数 (Assists)
│
└── [YourProject]PlayerState (开发者自定义子类,如 ABattleBlasterPlayerState)
        # 功能:存储坦克生命值、弹药量、当前关卡得分、持有的道具等

1.2. 🧩 组件系统 (Components)

功能的积木,必须挂载在 Actor 上。

UActorComponent (纯逻辑组件)

作用:没有物理位置的功能模块。

函数

  • GetOwner():获取拥有这个组件的 Actor。

  • Activate() / Deactivate():开启/关闭组件 Tick。

UActorComponent -> USceneComponent

作用:有位置、旋转、缩放。

函数

  • SetRelativeLocation():设置相对父组件的位置。

  • AttachToComponent(...):挂载到另一个组件上。

  • GetComponentLocation():获取世界坐标。

UActorComponent -> USceneComponent -> UPrimitiveComponent (渲染与物理)

作用:有几何形状,能被看到或发生碰撞。

常用子类与函数

  • UStaticMeshComponent:SetStaticMesh(...)(换模型)。

  • USkeletalMeshComponent:PlayAnimation(...)(播动画)、GetAnimInstance()。

  • 通用函数:SetSimulatePhysics(bool)(开启物理模拟)、AddImpulse(Vector)(施加力/冲量)、SetCollisionEnabled(...)(设置碰撞类型)。

UActorComponent -> UMovementComponent

作用:负责移动计算。

函数:SafeMoveUpdatedComponent(...)(移动并处理碰撞滑移)。

1.3. ⚙️ 核心框架 (Framework)

UWorld

作用:代表当前地图。

函数

  • SpawnActor<T>(...):在世界中生成一个 Actor。

  • LineTraceSingleByChannel(...):发射射线检测。

  • GetTimeSeconds():获取游戏运行时间。

UWorld -> UGameInstance

作用:游戏进程单例,切换关卡不销毁。

函数

  • Init():游戏启动时调用一次。

  • Shutdown():游戏关闭时调用。

  • (通常用来存放跨关卡的变量,如 int TotalCoins)。

UWorld -> USaveGame

作用:存档数据容器。

用法:不直接调用函数,而是作为数据对象。配合 UGameplayStatics::SaveGameToSlot(...) 和 LoadGameFromSlot(...) 使用。

UWorld -> USubsystem

作用:自动管理的单例系统。

函数:Initialize()、Deinitialize()。

UWorld -> ULocalPlayer

1.4. 💃 动画系统 (Animation)

UAnimInstance

作用:动画蓝图的 C++ 父类,管理动画状态机。

函数

  • NativeUpdateAnimation(DeltaTime):相当于动画蓝图的 Update,用于每帧计算变量(如速度、方向)。

  • TryGetPawnOwner():获取正在播放该动画的 Pawn。

  • Montage_Play(...):播放蒙太奇动作(攻击、受击)。

UAnimSequence
UAnimMontage

1.5. 🖼️ UI 系统

UVisual -> UWidget -> UUserWidget
  • 作用:UMG 界面基类。

  • 函数

    • AddToViewport():把 UI 显示到屏幕上。

    • RemoveFromParent():关闭 UI。

    • NativeConstruct():UI 初始化时调用。

1.6. 📦 静态资产 (Assets)

这些类通常作为指针变量存在(例如 UTexture* MyIcon),用于读取数据。

UDataAsset:自定义数据表。通常没有复杂函数,主要是 UPROPERTY 变量供策划配置。

UTexture / UMaterialInterface:作为参数传递给 MeshComponent->SetMaterial()。

USoundBase:作为参数传递给 UGameplayStatics::PlaySoundAtLocation(...)。

1.7. 🪞 反射与元数据 (Reflection)

底层系统,日常开发主要用来做"类型查询"。

UClass:描述一个类的类型。

  • GetDefaultObject() (CDO):获取该类的默认对象实例。

UFunction:描述一个函数。

  • 用于 ProcessEvent 手动调用蓝图函数(高级用法)。

2. UObject 体系的核心能力

核心能力 作用 应用场景
反射系统(Reflection) 运行时获取类 / 结构体 / 函数的元数据(如类名、属性、函数参数) 蓝图交互、序列化、网络复制、编辑器可视化
垃圾回收(GC) 自动管理 UObject 对象的内存,无需手动delete 避免内存泄漏,简化游戏对象的内存管理
序列化(Serialization) 将 UObject 对象的状态转换为字节流(或反向) 游戏存档、资源加载 / 保存、网络同步
元数据系统 通过UCLASS()/UPROPERTY()等宏注入元数据,控制对象行为 标记蓝图可见性、网络复制规则、存档规则
生命周期管理 统一的对象创建(NewObject/SpawnActor)、销毁(Destroy)流程 保证对象创建 / 销毁的一致性,避免野指针

3. 关键设计点

  • UClass:每个 UObject 子类都对应一个 UClass 对象("类对象"),存储该类的元数据(如属性、函数、蓝图配置);
  • GC 机制 :采用 "标记 - 清除" 算法,通过UPROPERTY追踪对象引用,自动回收无引用的 UObject;
  • 非 UObject 对象 :通过TSharedPtr/TWeakPtr管理(如 Slate UI 对象),不参与 GC,需手动控制生命周期。

五、游戏框架

游戏框架层是基于 UObject 体系和引擎层封装的游戏专属逻辑框架,提供了开箱即用的游戏对象和规则,是开发者日常开发接触最多的部分:

1. 核心游戏对象层级

在 Unreal Engine 中构建一个结构合理、逻辑清晰且易于扩展的游戏框架,关键在于严格遵守 UE 的设计哲学(Gameplay Framework)。UE 是一套"有主见"的引擎,它已经为你规定好了**"什么数据应该放在哪里"**。

如果你的代码结构混乱,通常是因为把本该属于 B 的逻辑写到了 A 里面(例如:把"游戏胜利判定"写在了"玩家角色"里,或者把"UI 刷新"写在了"游戏模式"里)。

以下是构建**"最合理游戏结构"的标准范式,我将其分为四层架构**来解析。


第一层:全局持久层 (Persistent Layer)

作用 :贯穿整个游戏生命周期,切关卡不销毁

|---------------------------------------------|------------|----------------------------------------------------------------------------------------------------|
| 类 | 职责归属 | 最佳实践 |
| UGameInstance | 全局大管家 | 存放跨关卡数据(如:音量设置、玩家选的皮肤ID、存档的元数据)。<br>不要在这里写复杂的战斗逻辑。 |
| USubsystem<br>(GameInstanceSubsystem) | 模块化管理器 | 比如 UQuestManager (任务系统)、UInventoryManager (全局背包)。<br>尽量把逻辑从 GameInstance 拆分到 Subsystem 中,保持代码解耦。 |

  • 架构关系:GameInstance 包含并管理各个 Subsystem。它们是所有 Level 的"上级"。

第二层:战局规则层 (Match Rules Layer)

作用 :定义当前关卡的玩法。切换关卡时会被销毁并重建

|--------------------|----------------|----------------------------------------------------------------------|
| 类 | 职责归属 | 最佳实践 |
| AGameModeBase | 裁判 (仅服务器) | 判定游戏输赢、重生规则、生成玩家。<br>例如:"杀够30人结束"、"倒计时归零结束"。<br>注意:客户端无法访问它。 |
| AGameStateBase | 记分员 (全网同步) | 存放全场公开的数据。<br>例如:当前剩余时间、红蓝队总比分、当前是第几波怪。<br>所有客户端都能读到它。 |

  • 架构关系

    • GameMode 拥有最高权威,负责修改 GameState。

    • Actor 询问 GameState 获取比赛信息。


第三层:玩家逻辑层 (Player Layer) ------ 最核心的"铁三角"

作用:处理单个玩家的输入、表现和数据。这是新手最容易搞混的地方。

请务必把"玩家"拆分为以下三个部分,不要把所有逻辑都堆在 Character 里!

1. 意志 (Mind) ------ APlayerController
  • 职责:处理输入(按键)、管理 UI(创建 HUD)、控制摄像机管理器。

  • 生命周期比 Pawn 长。玩家死后,Controller 还在(此时看着尸体或进入观战模式)。

  • 合理安排

    • 按键绑定 (Input Mapping) 放在这里。

    • 打开/关闭背包界面的逻辑放在这里。

    • 不要在这里写"开枪扣血"的逻辑。

2. 身体 (Body) ------ APawn / ACharacter
  • 职责:在世界中的物理表现。移动、播放动画、受击判定。

  • 生命周期脆弱。很容易被 Destroy(被打死)。

  • 合理安排

    • 血量 (Health) 放在这里(因为身体换了,血量通常重置)。

    • 开枪特效、走路动作放在这里。

3. 档案 (Data) ------ APlayerState
  • 职责:玩家的个人数据(即使身体死了,数据也得留着)。

  • 生命周期与关卡同在。玩家死了重生,PlayerState 不会重置。

  • 合理安排

    • 个人击杀数 (K/D)。

    • 玩家昵称、Ping 值。

    • 当前的等级、经验值(如果在单局内有效)。


第四层:表现与物品层 (Presentation & Content)

|------------------------|----------|-----------------------------------------------------------------------------------------------------------------|
| 类 | 职责归属 | 最佳实践 |
| AHUD / UUserWidget | 显示器 | 纯被动显示。<br>UI 应该监听数据的变化,而不要去存数据。<br>错误做法:在 UI 里存"int CurrentHealth"。<br>正确做法:UI 绑定 Character->Health。 |
| AActor (武器/物品) | 交互道具 | 武器应该是一个独立的 Actor,被 Attach 到 Character 上。<br>不要把枪械逻辑直接写在 Character 代码里。 |


🚀 终极架构图:理想的数据流向

为了让结构最合理,应该遵循**"单向依赖""事件驱动"**的原则。

✨ 一个具体的实战案例:射击游戏 (FPS)

假设你正在做一个"团队死斗"游戏,最合理的逻辑分配如下:

  1. 玩家按鼠标左键

    • PlayerController 收到输入 -> 告诉 Character "开火"。
  2. 角色开火

    • Character 播放开枪动画 -> 调用手中的 WeaponActor 发射射线。
  3. 击中判定

    • WeaponActor 检测到击中了敌人 EnemyCharacter -> 调用 Enemy->ApplyDamage()。
  4. 敌人死亡

    • EnemyCharacter 血量归零 -> 告诉服务器的 GameMode "我死了,凶手是 PlayerA"。

    • EnemyCharacter 播放死亡动画,变成布娃娃,3秒后 Destroy。

  5. 得分与重生

    • GameMode 收到消息:

      1. 给 PlayerA 的 PlayerState 加 1 分(杀敌数)。

      2. 给 AGameState 的红队总分加 1 分。

      3. 执行重生成逻辑:在随机出生点生成一个新的 EnemyCharacter 并让敌人的 Controller 附身。

  6. UI 刷新

    • PlayerA 的 HUD 监听到 PlayerState 分数变动 -> 更新屏幕右上角的 K/D 显示。

    • 所有人的 HUD 监听到 GameState 分数变动 -> 更新顶部比分条。

💡 总结:怎么判断"合不合理"?

当你写代码时,问自己三个问题:

  1. 如果角色死了(Destroy),这个数据还需要吗?

    • 需要 -> 放 PlayerState。

    • 不需要 -> 放 Character。

  2. 换了关卡,这个数据还需要吗?

    • 需要 -> 放 GameInstance。

    • 不需要 -> 放 GameState。

  3. 这个逻辑是针对全场所有人的,还是针对我自己的?

    • 全场 -> GameMode / GameState。

    • 自己 -> PlayerController / Character。

遵循这套 UE 官方推荐架构,你的项目在后期扩展(比如加多人联机、加新模式)时,会比"把代码全写在 Actor 里"轻松一百倍。

2. 核心游戏对象的职责

核心对象 核心职责 作用范围
UWorld 游戏世界的根对象,管理所有关卡、Actor、游戏规则,是游戏的 "容器" 全局(单例)
UGameInstance 游戏生命周期的全局对象,跨关卡保存数据(如玩家信息、配置),不随关卡销毁 全局(单例)
AGameModeBase 服务器专属,定义游戏规则(出生点、胜利条件、玩家加入 / 退出逻辑) 服务器
AGameStateBase 存储全局游戏状态(比分、剩余时间),同步给所有客户端 服务器 + 客户端
APlayerController 关联玩家和 Pawn,处理输入、相机控制、网络同步(客户端 <-> 服务器) 每个玩家一个
APawn/ACharacter 玩家 / AI 的可控制实体,包含物理、动画、碰撞等组件 场景对象
UActorComponent 组件化设计,封装单一功能(如网格、音频、碰撞),附属于 Actor Actor 的子对象

3. 核心设计理念:组件化(Component-Based)

UE 游戏框架采用组件化设计,而非传统的继承式设计:

  • Actor 作为 "容器",本身仅管理变换(位置 / 旋转 / 缩放)和组件;
  • 具体功能(如渲染、物理、音频)由不同的 Component 实现;
  • 优势:功能解耦、复用性高(如多个 Actor 可复用同一个 "音频组件")、灵活扩展(动态添加 / 移除组件)。

| 子类 | 作用 |
| USceneComponent | 带 Transform 的组件(所有空间组件的基类) |
| UStaticMeshComponent | 渲染静态网格模型 |
| USkeletalMeshComponent | 渲染骨骼动画模型 |
| UCapsuleComponent | 胶囊体碰撞组件(Character 的核心碰撞) |
| UInputComponent | 处理玩家输入 |
| UAudioComponent | 播放音频 |

UCameraComponent 相机视角组件

六、核心子系统(Engine Subsystems)

子系统 (Subsystem) 是 Unreal Engine (从 4.22 版本引入,4.24 成熟) 中最棒的架构特性之一。

简而言之,它们是引擎自动管理的、模块化的"单例"

如果你曾经把所有的全局逻辑(背包、任务、网络、音频管理)都塞进 UGameInstance 或 APlayerController 里,导致那个类变成了几千行的"上帝类 (God Class)",那么子系统就是用来拯救你的

以下是关于 UE 子系统的核心知识点全解:

1. 核心概念:它是什么?

本质:继承自 UObject 的类。

特性

  1. 自动创建:你不需要 NewObject,引擎会在特定的生命周期节点自动帮你创建实例。

  2. 自动销毁:引擎会在生命周期结束时自动销毁它们。

  3. 易于访问:在任何地方都能通过全局上下文轻松获取。

  4. 模块化:把代码拆分到不同的子系统里,互不干扰。

2. 五大子系统类型 (按生命周期划分)

这是面试和实战中最关键的部分,决定了你的逻辑写在哪里。

|----------------------------|----------------------------------------|------------------------------------|-----------------------------------------------------------|
| 子系统基类 | 生命周期 | 适用场景 | 获取方式 (C++) |
| UEngineSubsystem | 最长。游戏启动 ~ 游戏进程结束。 | 硬件接口、全局统计、跟具体存档无关的工具。 | GEngine->GetEngineSubsystem<T>() |
| UEditorSubsystem | 编辑器运行期间。 (仅编辑器模式有效) | 制作编辑器工具、自动化脚本、资源检查插件。 | GEditor->GetEditorSubsystem<T>() |
| UGameInstanceSubsystem | 中等 。游戏启动 ~ 关闭。<br>(跨地图存在) | 绝大多数全局逻辑:背包、任务、成就、网络连接管理、存档管理。 | GameInstance->GetSubsystem<T>() |
| UWorldSubsystem | 较短 。加载地图 ~ 卸载地图。<br>(切图即销毁) | 关卡内的逻辑:怪物生成器、天气控制器、当前关卡得分统计。 | World->GetSubsystem<T>() |
| ULocalPlayerSubsystem | 特殊。与本地玩家存在时间一致。<br>(分屏游戏尤其重要) | UI 管理器、输入映射管理 (Enhanced Input)。 | PlayerController->GetLocalPlayer()->GetSubsystem<T>() |

3. 实战代码示例

A. 如何定义一个子系统 (C++)

比如我们要写一个分数的管理系统。

cpp 复制代码
// MyScoreSubsystem.h
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "MyScoreSubsystem.generated.h"

UCLASS()
class MYGAME_API UMyScoreSubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

public:
    // --- 1. 生命周期函数 ---
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;

    // --- 2. 你的逻辑 ---
    UFUNCTION(BlueprintCallable)
    void AddScore(int32 Delta);

    UFUNCTION(BlueprintPure)
    int32 GetCurrentScore() const { return Score; }

private:
    int32 Score = 0;
};
cpp 复制代码
// MyScoreSubsystem.cpp
void UMyScoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    Score = 0;
    UE_LOG(LogTemp, Log, TEXT("Score System Started!"));
}

void UMyScoreSubsystem::Deinitialize()
{
    // 保存数据或清理
    UE_LOG(LogTemp, Log, TEXT("Score System Shutdown!"));
    Super::Deinitialize();
}
B. 如何在蓝图中使用

子系统的一个巨大优势是蓝图极其友好

  • 只要你的函数加了 UFUNCTION(BlueprintCallable)。

  • 在蓝图里右键,直接搜 "MyScoreSubsystem",你会发现引擎自动生成了节点的上下文,你甚至不需要手动获取 "Get GameInstance"。

4. 高级技巧与知识点

技巧一:控制是否创建 (ShouldCreateSubsystem)

默认情况下,子系统会无条件创建。但如果你只想在服务器 上创建,或者只想在客户端创建,怎么办?

复写 ShouldCreateSubsystem 函数:

cpp 复制代码
bool UMyScoreSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
    // 例子:只在客户端创建这个子系统(比如 UI 管理器)
    if (UWorld* World = Outer->GetWorld())
    {
        return World->GetNetMode() == NM_Client;
    }
    return true;
}
技巧二:让子系统支持 Tick (FTickableGameObject)

默认情况下,子系统是不 Tick 的(为了性能)。如果你需要它每帧更新逻辑:

  1. 继承 FTickableGameObject。

  2. 实现 Tick(), GetStatId(), IsTickable()。

cpp 复制代码
class UMySystem : public UGameInstanceSubsystem, public FTickableGameObject
{
    // ...
    virtual void Tick(float DeltaTime) override;
    virtual TStatId GetStatId() const override { return TStatId(); }
};
技巧三:子系统之间的依赖

如果 Subsystem A 初始化时需要用到 Subsystem B 怎么办?

在 Initialize 函数中,使用 Collection 参数:

cpp 复制代码
void UMySystemA::Initialize(FSubsystemCollectionBase& Collection)
{
    // 强制依赖:确保 SystemB 在 SystemA 之前初始化完成
    Collection.InitializeDependency(UMySystemB::StaticClass());
    
    // 现在可以安全访问 B 了
    UMySystemB* SysB = Collection.GetSubsystem<UMySystemB>(UMySystemB::StaticClass());
}

5. 为什么要用子系统?(对比传统做法)

|------------|---------------------------------------------|---------------------------------------|--------------------------------------|
| 场景 | 传统做法 (旧时代的眼泪) | 子系统做法 (现代架构) | 优势 |
| 全局任务系统 | 在 UMyGameInstance.h 里写 StartQuest() 等几百行代码。 | 创建 UQuestSubsystem。 | GameInstance 不再臃肿,任务逻辑独立封装。 |
| 关卡刷怪器 | 在 AGameMode 里写生成逻辑。 | 创建 UMonsterWorldSubsystem。 | 即使换了 GameMode,刷怪逻辑依然可以复用;且逻辑从裁判类中剥离。 |
| UI 管理 | 在 APlayerController 里存一堆 Widget 指针。 | 创建 UUIManager (LocalPlayerSubsystem)。 | 支持本地分屏,每个玩家有独立的 UI 管理器。 |

总结

Engine Subsystem 是 UE C++ 开发者的神兵利器。

  • 什么时候用? 当你需要一个管理类 (Manager),且希望它能自动管理生命周期时。

  • 选哪个?

    • 跨关卡数据 -> GameInstanceSubsystem (最常用)。

    • 单关卡逻辑 -> WorldSubsystem

    • UI/输入 -> LocalPlayerSubsystem






七、核心运行机制:Tick 与事件驱动

"能用事件驱动(Delegate/Event/Timer)解决的问题,绝对不要写在 Tick 里!" 这是 UE 开发优化的第一准则。

1. Tick 机制:不知疲倦的心跳 (主动轮询)

本质是什么?

Tick 是游戏循环(Game Loop)的直接体现。引擎的每一帧(Frame)都在进行一次巨大的 while 循环。在这一帧里,引擎会问场景里的每一个 Actor:"你现在有什么要做的吗?"

如果有 1000 个 Actor 开启了 Tick,引擎这一帧就要把这 1000 个对象的 Tick() 函数全部跑一遍。

核心参数:DeltaTime (Δt)

因为电脑性能不同,有的电脑一秒跑 60 帧(Tick 60次),有的跑 30 帧(Tick 30次)。

为了保证游戏逻辑一致(比如子弹飞行速度),Tick 函数会携带一个 DeltaTime 参数,代表**"上一帧到这一帧过去了多少秒"**。

  • 公式: 位移 = 速度 × DeltaTime。

  • 这样无论帧率多少,物体在 1 秒内的移动距离都是一样的。

Tick Group (Tick 组/时序)

所有的 Tick 不是乱序执行的,UE 把它们分成了"组"。比如:

  • TG_PrePhysics:在物理模拟计算执行(重置力、计算意图)。

  • TG_DuringPhysics:与物理模拟并行(通常用于物理无关的逻辑)。

  • TG_PostUpdateWork:在所有东西都算完执行(通常用于修正摄像机位置,防止画面抖动)。

代价

Tick 是性能杀手。哪怕你的 Tick 函数里是空的,引擎调用它也有开销(虚函数开销 + 内存寻址)。

  • 原则:默认情况下,应该把 Actor 的 PrimaryActorTick.bCanEverTick 设置为 false。

2. 事件驱动:静待花开的反射 (被动响应)

本质是什么?

这是"好莱坞原则":Don't call us, we'll call you.(不要打给我们,有事我们会打给你)。

对象平时处于休眠/空闲状态,完全不占用 CPU 资源。只有当特定的"条件"满足时,才会瞬间被唤醒执行代码。

核心工具:Delegate (委托)

UE 的事件驱动建立在 Delegate 机制之上。它像一个**"订阅列表"**。

  • 场景:Boss 死了。

  • Tick 的做法:UI 每帧检测 if (Boss.IsDead()) UpdateUI();(极其浪费)。

  • 事件的做法

    1. Boss 类里有一个 OnDeath 委托(广播站)。

    2. UI 在初始化时说:"我要订阅 OnDeath"。

    3. Boss 死了,发出广播。UI 收到信号,执行刷新。

常见的事件类型

  • 物理事件:OnComponentHit(撞击)、OnBeginOverlap(进入区域)。

  • 输入事件:SetupPlayerInputComponent 绑定的按键按下。

  • 生命周期:BeginPlay(出生)、EndPlay(销毁)。

  • 定时器 (Timer):这是 Tick 的替代方案。比如每隔 1 秒执行一次,而不是每帧(每 0.016 秒)执行一次。


3. 总结与最佳实践 (这也是面试必考题)

如何选择代码的执行方式

|--------------|-------------|---------------------------|----------------------------------------------------------|
| 场景 | 应该用 Tick 吗? | 为什么? | 正确做法 |
| 每秒回血 | ❌ 不要用 | 用 Tick 太浪费性能,且需要复杂的计时器逻辑。 | 使用 Timer (定时器),每1秒触发一次事件。 |
| UI 刷新血条 | ❌ 不要用 | 不要每帧去检测血量变了没。 | 绑定 OnHealthChanged 委托,只有掉血时才刷新 UI。 |
| 导弹追踪目标 | ✅ 必须用 | 每一帧位置都在变,必须实时计算。 | 在 Tick 里更新 Location。 |
| 检测是否踩到陷阱 | ❌ 不要用 | 不要每帧去算距离。 | 使用 Collision Component 的 OnComponentBeginOverlap 事件。 |

相关推荐
GLDbalala6 小时前
Unity 实现一个简单的构建机
unity·游戏引擎
JIes__1 天前
Unity(二)——Resources资源动态加载
unity·游戏引擎
地狱为王1 天前
Unity使用NovaSR将沉闷的16kHz音频升频成清晰的48kHz音频
unity·游戏引擎·音视频·novasr
孟无岐2 天前
【Laya】Socket 使用指南
websocket·typescript·游戏引擎·游戏程序·laya
tealcwu2 天前
【Unity资源】Unity MCP 介绍
unity·游戏引擎
晚霞的不甘2 天前
Flutter for OpenHarmony 引力弹球游戏开发全解析:从零构建一个交互式物理小游戏
前端·flutter·云原生·前端框架·游戏引擎·harmonyos·骨骼绑定
Thomas_YXQ2 天前
Unity3D中提升AssetBundle加载速度的详细指南
java·spring boot·spring·unity·性能优化·游戏引擎·游戏开发
晚霞的不甘2 天前
Flutter 方块迷阵游戏开发全解析:构建可扩展的关卡式益智游戏
前端·flutter·游戏·游戏引擎·游戏程序·harmonyos
玉梅小洋3 天前
Unity Muse 完整使用文档:Sprite+Texture专项
unity·ai·游戏引擎