有朋友私信问我刚接手一个自研引擎项目几千个源文件摆在眼前该从哪里看起。我给的答案一直是同一个先看引擎的基础架构也就是引擎的“骨架”。这有点像你拿到一栋复杂的建筑先得搞清楚承重墙在哪、管道怎么走、电路怎么分再去琢磨房间里怎么装修。如果一上来就盯着某个特效算法或者某个渲染特性看大概率会在几千个文件里迷路最后什么也学不到。“游戏引擎架构深度解析一引擎基础架构”这篇我打算从自己多年摸爬滚打的经验出发把引擎底层最重要的那几个部分——分层设计、核心基础设施、帧循环、资源管理、数据驱动——逐个拆开讲清楚。包括每个模块要解决的问题、典型实现思路、背后为什么这么设计以及我在实际项目中踩过的坑和总结的排查技巧。适合刚接触引擎源码的开发者、准备做引擎定制的中级工程师以及那些在引擎复杂机制面前无从下手的同学们参考。1. 引擎整体架构分层设计1.1 为什么绝大多数引擎都采用分层架构打开任何一个成熟引擎的源码你会发现它的目录结构惊人地相似底层是平台抽象层往上依次是核心系统、功能系统、资源系统、编辑器工具层。这种一致性并非偶然而是长期工程实践逼出来的结果。想象一下如果引擎所有代码都堆在一起没有清晰的分层边界会是什么场景平台相关代码比如Windows的窗口创建、Android的Activity管理和游戏逻辑代码混合在一起每次要适配新平台就得把整个引擎翻个底朝天。更重要的是分层架构带来的最大收益是可替换性——你可以在不触碰上层逻辑的前提下把渲染后端从DirectX换到Vulkan或者把物理库从PhysX换成Bullet只要接口保持不变。我接手过一个引擎它的网络模块和游戏逻辑模块居然互相直接调用内部函数后来想给游戏逻辑加个服务器验证层几乎重写了整个网络模块。这充分说明脱离分层谈模块复用就是空谈。所以理解引擎架构的第一步就是先看它的分层边界在哪里。1.2 各层职责划分与依赖方向引擎的依赖方向是单向的从下到上上层依赖下层下层对上层一无所知。这是架构设计中最重要的纪律。我把引擎大致划分为五个层级每一层都有明确的目的和边界平台抽象层处理操作系统差异封装窗口、输入、文件系统、线程、计时器等能力。这一层是唯一允许调用系统API的地方。核心系统层提供数学库、内存分配、容器、字符串、日志、断言、配置等基础能力。这一层是纯逻辑不关心平台细节。功能系统层渲染、音频、物理、动画、粒子、网络等子系统。它们只依赖核心系统层并且通过抽象接口互相解耦。资源管理层负责加载、解析、缓存、释放各类游戏资源为功能系统提供统一的数据入口。游戏逻辑层真正的游戏玩法、实体组件系统、场景管理。它是唯一允许调用所有下层模块的层。这样设计的核心逻辑是当你需要支持一个新的移动平台时通常只需要修改最底层当你需要更换一个渲染API时只需要修改渲染子系统的内部实现而对外暴露的接口保持稳定。根据我的经验目录结构是否整洁、依赖是否清晰基本预示了后续开发的顺畅程度——依赖混乱的引擎开发到后期效率会直线下降。2. 核心基础设施为上层构建提供稳固底座2.1 数学库不是简单的“开关库”每当我看到一个引擎自研数学库时关注的通常不是它支持多少种矩阵操作而是以下三个细节第一数据布局是否与图形API对齐。比如向量和矩阵是否按列主序存储SIMD优化是否做了16字节对齐。我曾经在一个项目中排查了整整一个下午的渲染错乱问题最后发现是矩阵存储方式与显卡驱动默认假设不一致导致的。第二浮点运算的一致性处理。不同的编译优化级别下浮点运算结果可能不严格一致在多人同步对战的游戏中这个问题会导致不同设备上物理表现不一样进而引发同步异常。所以在数学库里需要提供明确的浮点运算一致性控制策略。第三是否有NaN和Infinity的检测机制。调试时最痛苦的就是某些数值悄悄变成NaN然后在几帧之后才以扭曲的画面或失控的物理表现暴露出来。一个成熟的数学库应该在Debug构建中内置NaN/Inf断言在数值异常时立即暴露问题。数学库是引擎的“地基”地基不结实上层再漂亮的渲染算法也会摇摇欲坠。我见过一些初创团队为了省事直接用开源数学库这本身当然没太大问题但如果没有去理解它的数据布局和精度特性后续优化就会处处受制。2.2 内存管理引擎的性能命门在游戏引擎中内存管理直接影响帧率和卡顿感。原因在于游戏每一帧都需要分配和释放大量对象如果这些内存操作直接走系统堆分配器malloc/free性能损失会非常明显但更严重的问题是会产生大量内存碎片导致长时间运行后分配大块连续内存时频繁失败。因此成熟引擎都会实现自己的内存管理机制常见策略有以下几种池化分配器为特定类型对象如实体、组件、渲染命令预分配固定大小的内存块分配和释放都是常数时间操作而且不会产生碎片。帧分配器在一个帧内临时使用的数据比如一次渲染中生成的临时变换矩阵在帧末尾统一释放。因为生命周期锁死在帧内可以避免跟踪每一块小内存的生存状态。内存池/栈式分配配合帧循环的工作方式使用栈式内存布局分配临时对象帧结束时整体回退栈顶指针。实践中很关键的一条原则高性能路径中禁用系统malloc/free统一走引擎内存分配器这样不仅性能可控而且可以通过自定义分配器跟踪内存泄漏和统计占用分布。当然这里想提醒一下很多人以为“自定义内存分配器”就是把new/delete重载一两遍其实这只是入门。真正要注意的是分配器的线程安全性——很多引擎会为不同线程配置独立的内存分配器池以避免锁竞争。我在一个多线程渲染的项目里就是因为渲染线程和逻辑线程共享了同一个内存分配器导致约30%的帧耗时耗在分配器内部的锁上。这是血的教训。2.3 容器与字符串的衡权游戏引擎对容器的要求比标准库要高得多。标准库中的std::vector虽然方便但在性能敏感的代码路径上并不总是最优选择原因是它太均匀地兼顾了所有使用场景。我在自研引擎中看到的高性能容器通常有以下设计取向小对象优化对于较小尺寸的对象直接在栈上内联存储避免堆分配。那些每帧创建几万个只有几个字节的小结构体的场景省下的堆分配开销是非常可观的。微小向量支持指定最大容量的vector变体配合栈式内存或帧分配器使用。比如渲染命令列表它在每帧中的长度经常变化但极少超过几千条预先固定容量反而更加稳定高效。索引化访问优先在内存热点路径上优先选择连续的数组而非链表保持内存局部性和CPU缓存命中率。字符串处理则是另一个容易埋雷的地方。引擎中字符串拼接、格式化、哈希等操作非常密集而标准库的std::string虽然可靠但存在内存重分配和SSO边界性能波动问题。引擎内部常用64位哈希值代替字符串比较配合一个全局字符串表管理字符串的存续期。这样配合上面说的内存分配器字符串的代价就可以压得很低。一个实用心得引擎内部接口中尽量避免直接传递std::string而是传递字符串哈希 字符串句柄。这不仅仅是为了速度更是为了让日志系统和网络同步系统能方便地处理字符串比较和序列化。3. 帧循环引擎运转的脉搏3.1 为什么不能简单用while循环加sleep每一帧游戏都像一次“心脏跳动”——它必须完成输入收集、逻辑更新、物理模拟、渲染输出这些核心步骤然后循环往复。很多新手设计帧循环时会直接写一个while(true)循环在末尾加上操作系统级的sleep或delay来控制帧率。这在PC上也许能跑但在移动平台上往往会遇到大问题系统级睡眠的精度不高帧率容易波动而且睡眠会浪费掉宝贵的垂直同步节奏。成熟引擎的帧循环设计核心思路是先测量再决策也就是根据真实时间步长动态调整模拟逻辑的处理方式。具体来说我习惯把帧循环拆成三个部分输入收集与事件分发读取手柄/触摸/键盘输入生成事件供逻辑层消费。模拟更新以固定的时间步长通常是1/60秒更新物理、动画、游戏逻辑确保不同帧率下行为结果一致。渲染与呈现基于模拟结果生成渲染命令提交给GPU并最终呈现一帧画面。这里最关键的参数是模拟时间步长。如果不固定步长而是每帧都根据真实时间动态变化那么物理系统的数值积分会产生不稳定甚至爆炸。而如果固定步长又必须考虑真实时间落后时如何处理——最常见的做法是累加真实时间每达到一个固定步长就执行一次逻辑更新若落后太多则需要丢弃部分步长以避免“死亡螺旋”。我在一个模拟项目的实践中这么设定逻辑帧率固定为60Hz16.667ms但这不等于渲染帧率也必须是60帧。渲染帧率可以根据设备的垂直同步能力自由波动比如在30Hz与60Hz之间切换。逻辑时间与渲染时间分离逻辑相对独立地往前推进渲染在帧循环末尾用当前插值状态生成画面。这样做的好处是当帧率波动时游戏逻辑的确定性得到保证网络同步的基准也得以统一。而代价是需要在渲染层做插值也就是在两个逻辑状态之间做平滑过渡这是很多引擎为了提高视觉流畅度而花费不小精力的地方。3.2 时间管理从高精度计时器到帧率统计如果你以为帧循环的核心只是“循环”就错了其实时间管理才是更重要的一环。我在较早的项目中吃过一个大亏在Windows平台上用GetTickCount()来计时帧间隔结果因为它的精度只有约16毫秒计算出的帧间隔总是跳变导致模拟速度忽快忽慢。后来我总结了一套时间管理的规范计时器必须使用系统提供的高精度时钟接口并在引擎启动时校准一次频率。引擎内部所有计时请求统一走一个时间服务模块禁止直接在业务代码中调用平台时间函数。这个模块还负责维护游戏时间可暂停、加速和渲染时间用于动画插值之间的换算这样在菜单暂停时逻辑时间静止但UI动画仍在流畅播放。帧率统计方面不能简单地统计每帧的实际耗时然后求平均值因为这样会受个别极慢帧的影响失真。更合理的做法是维护一个滑动窗口记录最近若干帧比如120帧的耗时分布同时统计1%低帧的耗时——即最慢的那1%帧的平均耗时这是衡量卡顿感远比平均帧率更敏感的指标。我做过的项目中通常同时关注平均帧率和1%低帧因为后者往往反映了实际体验。3.3 多线程帧循环并行是趋势也是坑现代引擎已经很少是单线程帧循环了。因为一帧中的逻辑更新和渲染提交之间没有天然依赖而且硬件核心动不动就是八核起步如果还让所有工作挤在一个线程里那就是对计算资源的巨大浪费。我在多线程帧循环中采用过这样的拆分方式逻辑线程运行固定步长模拟更新负责游戏逻辑、物理、动画、导航等任务。渲染线程消费逻辑线程产生的渲染命令负责与GPU交互提交绘制资源。工作线程池并行处理大批量可独立计算的任务比如烘焙光照探针、筛选可见性、动画骨骼矩阵计算等。这样拆分后一个需要注意的设计原则是逻辑线程和渲染线程之间不应该在每帧末尾同步一次而应该采用双缓冲或三重缓冲的数据结构让渲染线程可以滞后逻辑线程一到两帧读取数据。这是为了减少阻塞等待的时间但代价是需要精心管理资源生命周期避免逻辑线程删除了渲染线程还在使用的资源。现实是多线程帧循环的复杂度比单体帧循环提升了不止一个量级死锁、资源竞争、时序问题层出不穷。新手如果一上来就搞复杂的多线程帧循环很容易在调试中崩溃。我的建议是先跑通单线程版本明确每一帧的工作清单再逐步把可并行的部分用任务系统改造。多线程是性能优化的工具不是炫技的手段。4. 资源管理引擎世界的“物流中心”4.1 资源类型与生命周期谁创建、谁持有、谁销毁资源管理在引擎架构中的重要性往往被高估或者低估——说高估是因为很多引擎初学者觉得资源管理就是“加载个贴图、放个模型”没什么可学的说低估是因为大型项目的卡顿、内存失控、崩溃有一半以上都跟资源生命周期管理不当有关。我习惯把引擎资源分成两类共享资源被多个对象同时引用的资源例如网格、纹理、材质、骨骼、动画片段。它们的加载代价高一般不轻易释放。独占资源某个逻辑对象独占持有例如临时生成的渲染纹理、动态构建的碰撞体。生命周期与持有对象强相关。核心要解决的问题是如何判断一块共享资源还能不能被安全释放常见做法有引用计数管理和资源句柄资源管理器两种。引用计数的思路比较直白每次被引用时计数加一解除引用时计数减一计数归零则释放资源。但引用计数有个麻烦——如果引用的关系是环形的计数永远不会归零这就会造成泄漏。所以很多引擎并不会把引用计数单独作为唯一的释放依据而是把它和资源引用图扫描配合起来使用。资源句柄方案则更进一步不直接让业务代码持有资源的原始指针而是持有句柄通过句柄到资源转换表间接访问。这样资源管理器在后台检测到某个资源确实不再被使用时可以把它从活跃表中移除而句柄因为指向表中的一个槽位就不会变成野指针。这个方案是我在接手较大型引擎项目后相较偏爱的方式因为它能从一开始就杜绝悬垂指针的问题。一个工程贴士在设计资源引用时务必让引用关系带方向性——谁加载了资源谁就应该负责释放。同时不要轻易在引擎内部实现“所有资源共享一条生命周期”的机制那是自动内存回收的思路但在引擎这种对实时性极敏感的领域不可预测的释放时机往往比内存泄漏更可怕。4.2 加载管线同步、异步与流式加载资源加载是另一个容易引发卡顿的环节。早期引擎的做法是同步加载游戏要进入关卡时加载线程阻塞在那里把场景所有资源一次性读入内存。这样会带来两个问题读取速度受硬盘/网络波动影响加载慢时整个引擎就卡死大型开放世界无法把整个地图都放进内存。所以现代引擎普遍采用异步加载加载请求进入一个后台加载管理器由独立的加载线程读取文件、解析数据然后准备好资源临时存储当主线程需要时再把它“交接”过来。这里有个细节资源的实际创建比如创建GPU纹理对象通常必须在渲染线程完成因为GPU资源访问跟渲染上下文绑定。所以异步加载只是解决了IO等待和解析计算的时间最终资源真正变得可用还是需要一个“上线”动作。流式加载则是异步加载的一种进阶应用根据玩家在游戏世界中的位置动态预测性地加载即将需要的资源同时卸载远离玩家的资源。这需要资源系统与场景管理模块紧密配合通常还要在关卡设置中提供每个资源块的空间范围信息。我在涉及到大地图的项目中广泛应用了这种方案整体效果还是很理想的。对于IOS/Android平台的开发者来说异步加载的影响更加直接。由于移动应用的内存限额比较严格把场景中所有资源同时驻留内存是不太现实的。所以移动端引擎对资源加载URGENCY的调度要求特别高——优先加载当前视野内的核心资源延迟加载次要资源并在后台根据视野变化调整加载优先级。这部分的调度策略经验性的优化能带来远比单纯的“加预算”更大的帧率收益。4.3 热更新与资源版本管理引擎做大了之后资源版本管理就成了绕不开的问题。特别是线上运营的游戏随时可能要出活动、修数值、换贴图不可能每次都发完整客户端这时候就需要热更新能力。一个健壮的资源版本管理方案通常包含这几层资源打包时记录每个文件的哈希值和版本号构建一个资源清单客户端启动时请求服务器端的清单对比本地清单下载变更部分下载后的新资源先落入临时目录在确认完整可用后再切换为生效状态。整个过程必须做好断点续传和容错处理——比如启动时发现本地文件损坏可以触发自动重下。这个过程中一个很容易踩的坑是资源更新后引擎里已经加载的旧版本资源仍驻留在内存中导致逻辑虽然读到了新数据但渲染还停留在旧数据上。我处理这个问题时采用方案是在资源系统中建立“版本订阅”机制共享资源更新后通知所有持有方重新绑定。固化的办法是每个资源元数据里附带版本号字段渲染、逻辑侧拿到资源句柄时都校验一下版本号不一致则重新解析资源句柄。5. 数据驱动让游戏策划不写代码也能改游戏5.1 配置化思想数据与逻辑分离的核心价值游戏引擎发展到现在几乎没有一个成熟引擎是完全靠硬编码写死游戏内容的。为什么因为游戏是一个内容生产行业策划、美术、音频等领域的同事需要频繁调整游戏表现和数据这些人绝大多数不具备写核心代码的条件和意愿。如果每次调数值、调表现都要改C代码再重新编译那游戏开发节奏会拖得非常慢甚至不可能完成。数据驱动的本质就是把“怎么做”的逻辑代码和“做什么”的内容数据彻底分开。比如技能的伤害计算逻辑是代码但具体哪个技能伤害多少、冷却几秒、施法范围多远都放在配置数据里。策划只要改数值配置不用动代码就能调整游戏手感。这种思想的适用范围非常广。我见过一些团队不仅玩法逻辑数据驱动连UI布局、角色动画状态机、武器配件规则都全面配置化开发效率确实提升了一大截。尤其是版本迭代频繁的项目数据驱动的威力更是尽显无疑。我们做引擎架构的人特别要思考的问题是配置数据的承载格式如何选。行业里常见的有脚本格式如Lua脚本、标记语言格式如JSON/XML、以及一些引擎自研的二进制格式。5.2 数据格式选型与序列化设计数据格式的选型直接决定了数据流的人机友好性和程序解析效率。JSON可读性好天然支持层级结构第三方库成熟几乎无所不能。缺点是需要额外的解析开销而且二进制资源比如纹理、音频如果硬塞进JSON体积和效率都不理想。XML严谨的标签结构对文档型内容比较友好但体积冗长、标签闭合很容易出错。在我看来XML已经不太适合作为游戏配置的主力格式它在某些配置文档比如动画状态机图的存续中仍有应用场景但维护起来确实不太舒服。二进制格式解析效率极高且体积紧凑适合运行时加载。但二进制数据的可读性几乎为零调试困难人写不了所以通常是编辑器导出运行时加载。实践下来成熟引擎常见的组合方式是编辑器和人打交道用可读写格式如JSON运行时使用预编译的二进制数据并配合资源构建管线将原始数据转换为引擎运行时格式。这样人机友好与程序效率各得其所。序列化方面引擎要在设计时考虑版本兼容。尤其是存档数据、网络协议数据、配置导出数据如果新版本引擎改了字段旧数据就读不出来了。所以设计序列化框架时最好带字段名或字段编号标记并且支持版本号和兼容读取逻辑。我早期的项目因为早期序列化格式没考虑版本兼容后来加新字段直接导致旧存档全部作废被用户投诉得很惨。5.3 运行时数据热重载的实践数据驱动的终极大招是热重载——修改配置文件后游戏不需要重启改动就实时生效。这对一些长时间运行的演示程序、联调状态下频繁调参的场景来说可以省下大量流程上的时间消耗。实现热重载的核心在于感知“文件变更”并触发对应的“数据重加载”流程。引擎常采用的做法是文件系统层持续监控资源目录内的文件变更事件比如文件的修改时间或者哈希值变化。变更事件被分发到资源管理器资源管理器找到对应资源ID并重新读入数据。重载完成后通过回调机制通知到所有已引用该资源的下游模块下游模块根据新数据重新初始化自身状态。我清楚地记得在做一个桌面演示项目时美术同事调整了一个角色模型的骨骼权重我有意把模型资源的生命周期做成了可热重载的形式这样无需重启程序角色动作和变形立刻按新权重更新前后视角对比非常直观。整个调整体验流畅得不像是还在调模型。不过需要谨慎对待的是热重载的适用范围已分配GPU资源的纹理、Mesh重新上传是有代价的如果玩家正处于一个激烈战斗的关键时刻热重载导致卡顿就得不偿失了。所以有经验的团队会在游戏内做一个“重载资源”的指令在保证设备安全的时机比如进入菜单、切关卡才执行重载。6. 常见问题与排查技巧实录6.1 加载场景卡顿从IO瓶颈到资源竞争症状进入关卡时画面短则半秒、长则数秒明显卡顿严重时直接白屏。排查思路如下 第一步先分辨是CPU卡顿还是IO卡顿。用性能剖析工具观察加载阶段的耗时分布若耗时集中在文件读取相关函数就是IO瓶颈若集中在资源解析或创建上就是CPU瓶颈。 第二步如果是IO瓶颈需要确认是否为磁盘读取速度受限还是读取模式效率低。如果资源文件零零散散非常多可以考虑打包成大文件块来规避磁盘寻道开销如果是加载线程在等待主线程处理则需要检查加锁或同步机制。 第三步如果是CPU瓶颈就要分析资源创建逻辑中是否有不必要的复杂度比如大量重复的字符串解析、冗余的内存拷贝、或者未开启异步加载的资源。我遇到过一个典型的案例某个关卡加载特别慢后来发现是加载脚本把几百个小型纹理文件分散读取每个文件都有固定开销累加下来成了灾难。改成资源包合并且预加载后启动时间从6秒降到了2秒左右。这遇到问题要多看多测轻易不要下结论说“硬件太慢”。6.2 帧率波动大寻找拖后腿的“异常帧”症状帧率统计显示平均50帧每秒但实际体验有顿挫感明显感觉不流畅。这种情况常见于帧耗时分布不均匀大部分帧很快但偶尔有一两帧特别慢。也就是说平均值被大量快速帧拉高但最差的那些帧决定了体验。排查方法是看1%低帧耗时并抓出那些最慢的帧样本。观察它们具体在做什么。我在实际项目中定位到的常见原因有偶发的加载事件比如资源动态加载触发了同步IO等待。垃圾回收机制被触发特别是用带GC的语言写的逻辑分配越频繁GC时卡顿显著。渲染线程和逻辑线程的不均衡某一帧逻辑更新复杂度突变渲染线程等待数据。解决方案各有不同。动态加载要尽量异步化GC压力要通过减少临时分配、使用对象池来缓解双线程不均衡则可以通过调整任务划分和双缓冲数量来优化。一个关键经验不要只盯着平均帧率而要把帧耗时的P99/P1低帧指标当作一个不断优化的目标。因为用户实际感受到的卡顿和1%低帧的表现强相关。很多团队优化了半年平均帧率玩家却还在喊卡就是走了冤枉路。6.3 存档数据错乱序列化版本兼容的教训症状游戏更新版本后玩家读旧存档某些数据加载异常、数值错乱、甚至直接崩溃。这个问题的根源几乎都出在序列化时没有版本管理或兼容策略。解决办法如下序列化时每个存档文件头部至少写入一个格式版本号。新版本读取时先判断版本号再进入对应版本的读取逻辑。新增字段时旧版本数据读入后由代码补齐默认值。删除字段时要对旧数据做忽略处理而不是直接报错。我处理这类问题时踩过一个坑早期图省事没有给序列化类加版本号后来加了一个字段后旧存档全废。为了挽回还得自己背锅我花了一个多星期写了兼容层把旧数据转出来。从此以后我对任何序列化结构的改动都要求在测试之中做前后版本兼容性测试这成了一个硬性流程。6.4 多线程资源竞争逻辑线程捣乱渲染线程症状不定时闪退崩溃栈大概率指向渲染资源相关的函数偶尔画面出现撕裂或错误的纹理内容。这类问题多半是逻辑线程释放了尚被渲染线程使用的资源。排查手段如下开启引擎的线程安全检测模式在Debug模式下对资源引用计数操作加锁和断言。检查资源释放路径确定销毁只由固定的系统通常是渲染帧的末尾执行避免逻辑线程直接删资源。排查页面切换与场景卸载时的竞态场景卸载往往同时断开了逻辑与渲染对资源的引用这里的次序设计要尤其小心。我最困难的一次竞态调试持续了快一周最后通过梳理引用计数日志才定位到问题是物理模块在子线程访问了骨骼网格资源而主线程同时把它释放了。后来给资源系统加了“资源状态机”管理后这类问题再也没发生过。7. 结尾架构设计是一种持久战写到这里引擎基础架构的各个方面——分层、基础设施、帧循环、资源管理、数据驱动、问题排查——基本都过了一遍。我要说的是架构设计并非一次性的工作它在引擎的整个生命周期里一直在进化。你不可能在第一天把所有事情设计到完美而是要在功能迭代和平台适配的过程中不断调整边界、优化依赖、重构模块。以我个人的体会打好基础架构的第一步不是急着写代码而是先在文档里画清楚模块依赖图、讲明白每一层存在的理由、约定好模块间的接口边界。而当项目规模扩大时则需要更严格的架构审查纪律——每次代码评审都应当检查是否出现了越层依赖、循环依赖或者过于紧耦合的接口。最后分享一个做法如果你正在看一个陌生引擎的源码建议把引擎启动的流程完整走一遍记录下来每个模块从Init到Update再到Shutdown的顺序和调用关系。这个过程会让你快速建立起对引擎“骨架”的整体认知之后再深入任何模块都会有的放矢。下一篇我可能会拆解“渲染系统架构与资源绑定”这也是我最近在回顾项目时觉得值得深入展开的部分。有想法的同学不妨自己先去翻翻引擎中渲染提交部分的代码到时候对照起来会更有实感。