虚幻引擎_核心框架

一、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 事件。 |

相关推荐
小小数媒成员15 小时前
如何优化代码适应托管内存?
游戏·unity·游戏引擎
狂人开飞机1 天前
21、游戏架构设计模式
游戏·游戏引擎·godot
狂人开飞机3 天前
14、游戏手感与视觉打磨
游戏·游戏引擎·godot
狂人开飞机3 天前
15、音频系统
游戏引擎·godot
狂人开飞机3 天前
16、粒子线条与视觉特效
游戏引擎·godot
心前阳光3 天前
Unity发布PC软件图标不能改变
unity·游戏引擎
an86950014 天前
unity如何在visual studio中打断点debug
unity·游戏引擎·visual studio
xcLeigh4 天前
Unity基础:使用Transform控制物体移动——Translate与position详解
unity·游戏引擎
FairGuard手游加固4 天前
Unity小游戏加密:global-metadata.dat与AssetBundle保护
游戏·unity·游戏引擎
狂人开飞机4 天前
11、UI系统
游戏引擎·godot