中EN
综合页

引擎运行时架构总览

Engine Runtime Architecture Overview

本页把 wiki 中分散在 GEA 第 1、6、8、17 章及 GPP、UE5 相关页面里的内容串成一条线,回答"一个游戏引擎在运行时是怎样组织起来的":静态结构上是自下而上的分层,上层依赖下层;启动关闭按依赖顺序显式进行;每一帧由主循环驱动各子系统批量更新,游戏对象的逻辑被安排在子系统阶段之间,并按依赖分桶或设先后关系;多核上一帧被拆成成百上千个作业;玩法基础系统(对象模型、事件、脚本、世界加载流送、高层流程)位于引擎与游戏的分界线附近。各结论的细节与出处见所链接的概念页。

1. 静态结构:分层与分界线

  • 引擎自下而上是:目标硬件、驱动、操作系统、第三方 SDK、平台无关层、核心系统(断言、内存分配、数学、容器、字符串 id、反射与序列化、句柄等)、资源管理器、各大子系统(渲染、碰撞物理、动画、音频、HID、网络、剖析调试),再到游戏性基础层和游戏专属子系统。规则是上层依赖下层、反之不行,否则形成循环依赖。见 运行时引擎分层架构。
  • 游戏与引擎之间如果能划线,就在游戏专属子系统与游戏性基础层之间,但实际上是一片渐变的模糊地带;GEA 第 17 章称这一层为玩法基础系统,并指出各引擎差异在这里最大。见 运行时对象模型架构。
  • UE 的对应:渲染线程、场景代理与 RHI 对应"底层渲染器 + 场景与剔除层"(UE5 渲染架构),Actor/组件、GameMode 等属于游戏性基础层的一种实现(Gameplay 框架)。

2. 生命周期:启动与关闭

  • C++ 全局/静态对象的构造析构顺序不可控,GEA 推荐单例管理器的构造析构什么也不做,在 main() 里按依赖顺序显式调用 startUp(),关闭时大致反序;"蛮力法总是胜出",因为简单、显式、易调试。真实引擎(Naughty Dog)的启动还包括异常处理、专用对齐堆、配置文件、文件系统等。见 子系统启动与关闭。
  • 启动时还会载入常驻 (LSR) 资源并记录栈标记,此后关卡资源在其上加载卸载。见 游戏世界的加载与流送 与 资源管理器。

3. 一帧之内:主循环与更新调度

下图把几页中的内容合成一帧的典型顺序(以 GEA 的分阶段、分桶循环为骨架,示意图):

flowchart TB
  A["读取输入 HID"] --> B["推进时钟,得到 Δt"]
  B --> C["对每个桶:<br/>对象 PreAnimUpdate → 动画中间姿势 → 对象 PostAnimUpdate →<br/>布娃娃/物理/碰撞 → 最终姿势与矩阵调色板 → 对象 FinalUpdate"]
  C --> D["分发排队事件"]
  D --> E["音频等其他子系统批量更新"]
  E --> F["渲染:提交场景并交换缓冲"]
  • 主循环:各子系统需要的频率不同(动画 30/60 Hz,物理可能更高,AI 每秒一两次),最简单是一个主循环更新一切;架构风格有消息泵、回调框架和基于事件的更新。时间步长上 GPP 推荐"固定更新步长、可变渲染",GEA 认为多数引擎用测得的 Δt 并配合帧率调控,两者并列见 游戏循环与更新方法 和 游戏时间线与时钟。
  • 批量更新:子系统由主循环直接驱动、批量处理,对象只修改子系统中属于自己的状态(网格实例的可见性、要播放的动画)。好处是缓存一致性、少重复计算、资源复用、便于分散/汇集。见 游戏对象的实时更新。
  • 顺序约束:子系统之间(动画 ↔ 布娃娃物理)和对象之间(人抱着猫)都有依赖。GEA 的手段是分阶段钩子(最好是回调注册)和按依赖森林分桶;UE 对应的是 Tick 组和 Tick 依赖(委托与 Tick 机制)。物理在循环中的位置见 物理引擎与游戏的集成。
  • 一致性规则:"所有对象的状态在更新循环之前和之后一致,循环之中可能不一致。"违反它会出现一帧延迟;对策是状态缓存、时间戳和桶号断言。GPP 对"依次更新"的看法更乐观,两种观点并列记录在 游戏循环与更新方法。
  • 事件分发也要嵌在循环里合适的位置(例如先更新动画时钟、再分发 EndOfAnimation 事件、再开始混合),否则会多出一帧延迟。见 事件与消息传递。

4. 多核:把一帧拆成作业

  • 子系统各自启动多个作业(如每个需要混合的对象一个动画作业),接口要线程安全;对象代码要异步思考:尽早发起请求、尽晚取结果。见 作业系统 与 游戏对象并发更新。
  • 并行度受依赖限制,每个依赖都是同步点;推迟同步点能减少空等("优化并发设计的秘诀是延迟")。
  • 对象模型本身也能并行:Naughty Dog 让每个桶内的对象更新成为作业,跨桶访问免锁,同桶读快照、写用锁或请求队列。纤程作业让"阻塞等待射线结果"这类写法重新可行。
  • 这与性能预算的关系见 帧时间预算与渲染性能。

5. 玩法基础系统:对象、事件、脚本、世界

  • 对象模型:以对象为中心(单体层次 → 组合与组件)或以属性为中心(属性表、SoA);UE 的 Actor/组件与 MassEntity 分别靠近两端。见 运行时对象模型架构。
  • 对象从哪里来:世界块中的生成器(类型 id + 键值对)在运行时按类型实例化对象,类型模式和默认值把编辑器与运行时解耦。见 世界块数据格式与生成器。
  • 对象之间如何找到和通信:句柄/智能指针/裸指针各有取舍,查询靠专用加速结构;通信靠事件对象(可排队、可转发、可多播)。见 游戏对象引用与查询、事件与消息传递。
  • 谁来写玩法逻辑:脚本(文本或图形,如 UE 蓝图)把行为交给策划,代价是 VM 性能和调试成本。见 游戏脚本系统。
  • 世界如何进出内存:从一关一载到气闸,再到细粒度流送和加载区域;UE5 的世界分区以网格和流送源自动完成。见 游戏世界的加载与流送。
  • 游戏如何推进:高层流程常是状态机,Naughty Dog 的任务图还支持并行任务。见 游戏世界与游戏对象。

6. 贯穿全局的取舍

  • 解耦 vs 性能:分层、事件、脚本、组件都在换取灵活性,代价是间接层和缓存不友好;数据导向与批量更新则反过来。GPP 视角的整体讨论见 游戏编程模式选型:解耦与性能。
  • 内存是一等约束:分配器、流送和对象生成策略决定了引擎能否长时间稳定运行,见 游戏中的内存管理。
  1. Game_Engine_Architecture_4Ed_-_Jason_Gregory.pdf
  2. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 I 第 1、6、8 章(经由下列 wiki 页面综合)
  3. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  4. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章(经由下列 wiki 页面综合)
  5. GPP 与 UE5 官方文档(经由下列 wiki 页面综合)

相关页面

游戏开发运行时引擎分层架构Runtime Engine ArchitectureGregory 把游戏视为“软实时、交互式、基于代理的计算机模拟”,把游戏引擎定义为数据驱动、可复用来做多款游戏的软件。运行时引擎按层组织:从目标硬件、驱动、操作系统、第三方 SDK、平台无关层、核心系统、资源管理器,到渲染、物理、动画、音频、网络等子系统,再到游戏性基础层和游戏专属子系统;上层依赖下层,避免循环依赖。游戏开发子系统启动与关闭Subsystem Start-Up & Shut-Down游戏引擎由许多相互依赖的子系统组成,启动时必须按依赖顺序初始化(B 依赖 A,则先启动 A),关闭时通常反序。C++ 全局/静态对象的构造与析构顺序不可预测,「首次调用时构造」的函数静态单例也无法控制析构顺序且时机不可控。GEA 推荐最朴素的方案:单例管理器的构造/析构函数什么也不做,提供显式的 startUp() / shutDown(),在 main() 里按顺序手动调用。游戏开发游戏循环与更新方法Game Loop & Update Method游戏循环 (Game Loop) 让游戏时间的推进与玩家输入和处理器速度脱钩:每一轮非阻塞地处理输入、更新状态、渲染,并跟踪真实时间来控制节奏;最常推荐的做法是“固定更新步长、可变渲染”。更新方法 (Update Method) 是它的搭档:世界维护一组对象,每个对象实现一个模拟“一帧”行为的 update(),每帧依次调用。两者和组件模式一起常被视为游戏引擎的核心。游戏开发游戏时间线与时钟Game Timelines & Clocks游戏引擎要同时处理多种「时间」:真实时间、游戏时间、动画片段的局部时间、CPU 周期等。GEA 用抽象时间线 (abstract timeline) 统一看待它们,并讨论帧时间 \Delta t 的测量方法、高精度计时器、时间单位与时钟变量类型的取舍,以及断点恢复后的异常 \Delta t 处理。(卷 I 第 8 章 引言、8.4–8.5 节)游戏开发作业系统Job System多核硬件上,游戏循环的工作要分解成可并行执行的子任务。GEA 依次比较「每子系统一个线程」「分散/汇集 (scatter/gather)」和通用的作业系统 (job system),并给出典型作业 API、基于线程池的简单实现及其局限、用协程或纤程让作业可以睡眠、作业计数器、作业同步原语与可视化剖析工具,最后介绍 Naughty Dog 基于纤程的作业系统。(卷 I 8.6 节)游戏开发游戏对象的实时更新Updating Game Objects in Real Time游戏对象更新就是根据上一时刻状态 Si(t − Δt) 求出当前状态 Si(t)。"每帧对每个对象调一次虚函数 Update(),并在里面顺带更新它的动画、物理、渲染组件"的简单做法在商业引擎里行不通:底层子系统需要批量更新才能高效,而对象之间、子系统之间又有依赖顺序。GEA 介绍的对策依次是:子系统由游戏循环直接批量更新、对象只操纵子系统中的"代理状态";在帧内多个阶段挂回调;按依赖森林把对象分桶,逐桶运行完整的分阶段更新(Naughty Dog 的做法);并用状态缓存和时间戳对付更新过程中的状态不一致和一帧延迟(GEA4 卷 II 17.6)。游戏开发游戏对象并发更新Concurrent Game Object Updates游戏对象模型要和几乎必然是多线程的底层子系统打交道,所以对象代码必须异步思考:耗时操作用非阻塞请求尽早发起、尽晚取结果,必要时容忍一帧延迟。作业之间的依赖决定了并行度 (DOP),每个依赖都引入同步点,"优化并发设计的秘诀是延迟"。对象模型本身也能并行:Naughty Dog 在把引擎从 PS3 移植到 PS4(《最后生还者 重制版》)时,让每个桶内的对象更新成为作业;桶间访问无需加锁,桶内读取用对象快照,桶内修改尽量避免,或加锁、或放进请求队列、或交给下一桶中的同步作业(GEA4 卷 II 17.7)。游戏开发运行时对象模型架构Runtime Object Model Architectures玩法基础系统 (gameplay foundation system) 是位于"引擎与游戏分界线"附近、供构建游戏规则和动态元素的一组运行时组件,其中最复杂的是运行时对象模型:它在运行时实现世界编辑器展示给策划的抽象对象模型。GEA 把架构分为两大类:以对象为中心(每个工具侧对象对应一个或一小组类实例)和以属性为中心(对象只是一个 id,属性分散在按属性类型划分、以 id 为键的表里)。前者从单体类层次走向组合与组件,后者更省内存、更缓存友好、更易数据驱动,但难以表达属性间关系、也更难调试(GEA4 卷 II 17.1–17.2)。游戏开发游戏世界与游戏对象Game World & Game Objects游戏由玩法 (gameplay) 而不是技术定义;游戏机制 (game mechanics) 是支配游戏中各实体交互的规则,以及目标、成败标准、玩家能力、非玩家实体和整体流程(GEA 引用的论文称实现玩法的软件系统为游戏的 G 因子)。大多数游戏世界由静态元素和动态元素组成,划分为若干世界块,并由高层游戏流程组织玩家目标;动态元素通常实现为游戏对象 (GO),引擎提供的建模设施称为游戏对象模型,且分为工具侧和运行时两套(GEA4 卷 II 16、16.1–16.2)。游戏开发事件与消息传递Events & Message Passing游戏天然是事件驱动的:爆炸、玩家被敌人发现、血包被捡起都是事件,系统要通知感兴趣的对象并让它们以各自的方式处理。用虚函数 OnExplosion() 通知属于静态类型的后期绑定,迫使基类为所有可能的事件声明虚函数;GEA 主张把事件封装成对象(类型 + 参数),实现动态类型的后期绑定,从而获得单一处理函数、持久性和盲转发。GEA 依次讨论事件类型(枚举 vs 字符串/字符串 id)、参数(派生类、Variant 数组、键值对)、处理器、职责链、兴趣注册、排队与否的利弊,最后是数据驱动的事件系统和以数据通路连接端口的图形化系统(如 UE 蓝图)(GEA4 卷 II 17.8)。游戏开发游戏脚本系统Game Scripting脚本语言是主要用来让用户控制和定制软件行为的编程语言;在游戏引擎中,它是高层、相对易用、能方便访问引擎大多数常用功能的语言,程序员和非程序员都能用它开发新游戏或给现有游戏做 mod。GEA 先区分数据定义语言和运行时脚本语言,梳理语言分类与游戏脚本的典型特征(解释执行、轻量、快速迭代、方便),介绍 QuakeC、UnrealScript、Lua、Python、Pawn,列出从"脚本回调"到"脚本驱动整个游戏"的六种架构,再以 Naughty Dog 的 DCScript 为例讲虚拟机与原生函数接口,最后是对象引用、事件收发、面向对象脚本、脚本状态机和协作式多线程脚本(GEA4 卷 II 17.游戏开发游戏世界的加载与流送Loading & Streaming Game Worlds世界加载系统负责两件事:把世界块和所需资源从磁盘读进内存的文件 I/O,以及这些资源的内存分配与释放;另外还要管理游戏对象的生成与销毁。GEA 按演进顺序介绍:一次只载一个关卡的栈式加载(LSR 资源压栈底),用小块气闸 (air lock) 区域实现基本无缝的过渡,再到把所有资产切成等大块、用块池分配器按关卡加载区域流送的细粒度方案(Naughty Dog);对象生成的内存问题有"不许运行时分配"、按尺寸分池、小内存分配器和内存重定位四种对策;存档则分检查点和随处存档(GEA4 卷 II 17.4)。游戏开发资源管理器Resource Manager游戏由网格、材质、纹理、着色器、动画、音频、关卡布局、碰撞与物理参数等大量资源(也叫资产、媒体)构成。内存有限,所以引擎要保证每个资源在内存中只有一份(五个网格共用一张纹理时只载入一份)。每个引擎都有某种资源管理器:有的是统一集中的子系统,有的散布在多年间由不同人写的各个子系统中,但职责和要解决的问题是一样的。本页讲运行时部分:职责、文件组织与归档、文件格式、资源 GUID、资源注册表、生命周期(引用计数)和资源的内存管理;交叉引用与加载后初始化见 资源交叉引用与加载后初始化,离线工具链见 资产管线与工具链。综合游戏中的内存管理Memory Management in Games本页把 wiki 中关于内存的内容串成一条线:内存从两方面影响游戏,一是访问模式(缓存未命中远比计算昂贵),二是分配本身(通用堆分配慢且产生碎片,而主机上没有自动换页的虚拟内存兜底)。对策按生命周期组织:常驻与关卡数据用栈、每帧临时数据用单帧/双缓冲分配器、同尺寸小对象用池或小内存分配器、大小不一的动态对象用堆加碎片整理与句柄;资源按引用计数和分块加载管理,流送系统把世界切成等大的块;UE 则在 UObject 层用垃圾回收管理对象生命周期。最后,没有准确的追踪工具,以上一切都无从验证。各结论的细节与出处见所链接的概念页。游戏开发物理引擎与游戏的集成Integrating a Physics Engine into a Game碰撞/物理世界里的刚体只是抽象的数学描述,要通过游戏对象这个中枢,间接连到屏幕上的视觉表现。刚体按谁驱动分三类:物理驱动、游戏驱动(Havok 称 key framed)、固定;Havok 用可在运行时切换的"运动类型"统一表示。每帧更新有固定顺序:先更新游戏驱动刚体、幻影、力、冲量和约束,再步进模拟,然后回读物理驱动对象、查询幻影、执行投射查询。物理宜用恒定的 1/30 或 1/60 秒时间步。GEA 还逐一讨论了子弹、手雷、爆炸、可破坏物、角色移动、相机碰撞和布娃娃的实际做法(GEA4 卷 II 14.5–14.6)。游戏开发 · UE5Gameplay 框架Gameplay FrameworkGameplay 框架 (Gameplay Framework) 是 UE 提供的一组相互配合的类,用来搭建游戏的基本结构:Actor 与组件 (Component) 构成场景中的一切;GameMode 在服务器上定规则并生成玩家;玩家由“控制器 (Controller) + 棋子 (Pawn)”组成;GameState 与 PlayerState 负责把全局与每个玩家的状态复制给所有客户端;GameInstance 跨关卡存活。游戏开发 · UE5委托与 Tick 机制Delegates & TickUE 有两种驱动游戏逻辑的方式:事件驱动——委托 (Delegate) 在某事发生时回调绑定的函数;轮询驱动——Tick 每帧(或按最小间隔)调用 Actor/组件的更新函数,并按 Tick 组 (Tick Group) 与物理模拟排序。官方建议能用定时器、委托就不用 Tick,必须 Tick 时用 Tick 组和 Tick 依赖保证顺序,避免“差一帧”问题。游戏开发 · UE5UE5 渲染架构UE5 Rendering ArchitectureUE5 的渲染器是独立的 Renderer 模块,在渲染线程 (Rendering Thread) 上以「场景代理」镜像游戏线程的组件状态,每帧按固定顺序执行深度预通道、G-buffer 基础通道、光照、雾、半透明和后处理。现代渲染代码用渲染依赖图 (Render Dependency Graph, RDG) 声明通道与资源,由 RDG 自动排屏障、复用瞬态内存、并行录制;最终命令经 RHI 线程翻译成 D3D12/Vulkan 等图形 API 调用。默认走延迟着色 (Deferred Shading),也可切换前向着色 (Forward Shading)。综合游戏编程模式选型:解耦与性能Choosing Game Programming Patterns本页把《Game Programming Patterns》里的 19 个模式放到同一张取舍图上:大多数模式要么为了解耦(降低改代码时需要装进脑子里的知识量),要么为了性能(利用对数据和硬件的具体假设),而这两个目标经常互相拉扯。下面先讲全书的定位和 Nystrom 的架构观,再给出按问题选模式的路线和与 UE 的对照。综合帧时间预算与渲染性能Frame Budget & Rendering Performance本页把 wiki 中分散在 RTR4 第 18~23 章的性能知识串成一条线:一帧的时间预算由最慢的管线阶段决定,先用毫秒帧时间度量并定位瓶颈,再按瓶颈所在位置——CPU/驱动、几何、像素、带宽/内存——选用对应手段;最后从 GPU 硬件机制(占用率、HiZ/early-z、压缩、分块架构)理解这些手段为何有效。各结论的细节与出处见所链接的概念页。游戏开发世界块数据格式与生成器World Chunk Data Formats & Spawners世界块中的动态部分要为每个游戏对象记录两样东西:属性初始值和对象类型。GEA 比较了三种存储方式:二进制对象映像(生成最简单,但要处理指针、虚表和字节序,且对变化很脆弱)、序列化描述(XML/JSON/私有二进制格式,配合反射与类工厂),以及生成器 (spawner) + 类型模式 (type schema):生成器是只含类型 id 和键值对的轻量数据块,彻底切断世界编辑器与运行时代码的耦合;模式里的默认值配合"与默认值相同就省略键"可以让默认值的修改自动传播到旧实例(GEA4 卷 II 17.3)。游戏开发游戏对象引用与查询Game Object References & Queries游戏对象需要唯一 id 来区分、查找和充当通信目标;找到对象后还要能引用它,引用的寿命可以从一次函数调用到许多分钟。GEA 比较三种实现:裸指针最快最简单,但有孤儿对象、悬垂指针、非法指针三类问题,且不能配合内存重定位;智能指针可以检测对象已删除、做引用计数,但很难写对,GEA 个人建议远离它,非用不可就用 C++11 标准库或 Boost 的成熟实现;句柄是全局句柄表的整数索引,加上唯一 id 防止槽位复用后指向错误对象,简单而安全。对象查询则不追求通用数据库,而是为最常用的查询建专门的数据结构(GEA4 卷 II 17.5)。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原