2.引擎架构分层

2.引擎架构分层


2.1 游戏引擎分层简介

  • 现代引擎常用“5+1”纵向分层:工具层 → 功能层 → 资源层 → 核心层 → 平台层,并在各层按需接入第三方中间件(如PhysX物理、Wwise音频、植被、LOD 等)。此分层思路适用大部分游戏引擎,便于初学者“由上而下”建立整体地图。

    • 对于一个新手来说,打开一个游戏引擎会直接看到各种类型的编辑器。这个直接和开发者进行交互的层称为工具层(tool layer)。


    • 工具层只能实现一些资源的编辑,想让游戏运行时灵活得动起来,模拟真实的世界,就需要许多功能的支持。动画、物理仿真、AI、渲染……这一类的功能称为功能层功能层(function layer),用来实现游戏的渲染、动画、交互等不同类型的功能。


    • 游戏中的功能表现依赖于美术、音频、动画资源。如何管理组织这些资源对象,使得工作流与游戏高效运行,所以还需要资源层(resource layer)来管理各种各样的场景美术资源。

    • 游戏对性能十分敏感,底层功能的计算需要极其严苛的处理。引出了核心层(core layer),它包括支持游戏渲染、动画、物理系统、内存管理等不同系统的核心代码,提供符合游戏需求的高效计算方式。

    • 使用上面四层功能完成游戏开发,要将游戏发布到不同硬件平台时(PS\PC\Switch\IOS\Android\VR……),又会十分头疼。不同硬件的输入方式、渲染接口、功能特性各不相同,如果要为每个平台都做一个版本,那开发人员可能会疯掉。能够让游戏在不同平台兼容运行的功能称为平台层(platform layer),一般包括各种图形API、输入设备支持以及不同游戏平台的底层代码。


    • 除此之外各种不同类型的中间件也会贯穿整个游戏引擎的不同层。

    • 以上基本构成了游戏引擎的基本架构。当然,不同的人对于游戏引擎的分层可能不太一致,但需要处理的问题是一样的。
      5+1引擎体系

  • 分层的设计原则:上层依赖下层,禁止反向耦合;“下层稳定、上层灵活”。这样既能在工具/功能层高速迭代玩法,又能保证核心/平台层多年可复用。

  • 举例说明:就像城市的水电系统(平台层)支撑物流(资源层),物流支撑商业活动(功能层),商业活动需要店铺(工具层)——每层只专注自己的事,既避免模块耦合,又让迭代更灵活。比如升级物理引擎(第三方库),只需修改功能层接口,无需动核心层的内存管理;新增关卡编辑器功能(工具层),也不会影响资源层的资产加载逻辑。 平台层、核心层几乎不随玩法迭代变动(可以把不同城市比作不同操作系统,Windows和Mac的路径适配十年不变);而功能层、工具层需快速响应创新(如新增“开放世界天气系统”只需改功能层,加个天气编辑器改工具层)——这种平衡,正是引擎能同时支撑“技术稳定”与“玩法创新”的核心。

  • 中间件示例:PhysX、Havok 等专用库通常以 SDK 形式接入,引擎只需在适配层统一接口。

  • 从创作流程看:在工具层编辑内容 → 功能层把世界“跑起来” → 资源层负责资产导入/引用/生命周期 → 核心层提供数学、容器、内存等高性能基建 → 平台层屏蔽操作系统/硬件差异。


2.2 资源层

一句话总结:资源层 = 资产入口 + 引用稳定性 + 生命周期调度(从“能进来”“指得准”“用得稳”三个维度,为上层系统提供可靠的数字资产支撑)。

从资源(resource)到资产(asset)

原始资源(美术用3DMax/PS/Maya制作的.max/.psd/.fbx等)存在两大问题:一是格式杂乱(不同工具输出格式不同,引擎无法统一解析),二是含编辑冗余(如PSD的图层历史、Max的建模日志),直接加载会严重拖慢运行效率。
资源层的核心动作是“引擎化转换”:

  1. 统一格式:将所有资源转为引擎专属高效格式——贴图(JPG/PNG)转DDS(GPU可直接读取,无需实时解压)、模型(FBX)剔除冗余数据(类似Word文档转轻量TXT,只保留顶点/索引等运行必需信息),降低体积与运行时解析开销;
  2. 打包复合资产:用“描述对象/关系脚本”把同一实体的模型、材质、动画、音频打包为一个“复合资产(Composed Asset)”,例如:机器人复合资产 = 模型 + 皮肤贴图 + idle/walk动画),引擎读取时自动加载所有关联内容,无需逐个引用,便于整体加载与版本控制;
  3. 提取有效数据:过滤原始资源中的无用信息(如PSD的隐藏图层、Max的临时缓存),压缩存储体积,提升加载速度。

GUID:游戏资产的身份识别号

现代游戏包含数十万甚至上百万个资产(贴图、动画、音效等),且资产常需重命名、迁移路径,若按“文件路径”查找,极易出现“资产失联”。
资源层通过”GUID(全局唯一标识符)”解决这一问题:

  1. 唯一身份标识:为每个资产分配专属GUID,存储在元资产文件中(如Unity的PNG资源会自动生成.meta),相当于资产的“身份证号”;
  2. 脱离路径依赖:资产查找不再依赖文件路径——即便资产从“角色/主角”文件夹迁移到“备份/角色”,或重命名,引擎仍能通过GUID精准定位,避免“朋友搬家就找不到”的问题;
  3. 支撑资产关联:现代游戏资产间呈网状关联(如角色动画依赖模型骨骼、特效依赖角色位置),GUID是维系这些关联的核心——复合资产能准确加载关联资源,本质就是通过GUID匹配。


Unity中PNG的meta文件

运行时资产管理器(runtime asset manager):动态管控,稳性能

游戏运行时内存有限,不可能将所有资源都读取到内存,且需随玩家进度动态调整资产状态(加载新关卡、卸载旧关卡),因此资源层还需要处理运行时的资源加载与卸载。综上需要一个“运行时资产管理器”,管理所有这些资产的生命周期,以及加载卸载,资源回收等:

  1. Handle系统:安全访问资产:上层模块不直接持有资产的“内存地址”,而是持有“句柄(Handle)”(类似“邮箱号”)。即便资产卸载后重新加载(内存地址变化),句柄仍稳定,避免“空指针崩溃”;
  2. 生命周期调度
    • 延迟加载:玩家“走到哪加载哪”(例:虚幻引擎中角色贴图先模糊再清晰,就是延迟加载的表现,优先加载低精度贴图,再补全高清细节);
    • 关卡卸载:通关后批量释放当前关卡资产(如离开“蒙德城”后,该区域的NPC、建筑资产自动卸载),避免内存堆积;
    • 垃圾回收(GC):精细化调度资产回收时机,避免“大量资产同时加载/回收”导致的“卡顿尖峰”(如关卡切换时,分帧回收非关键资产);
  3. 平衡核心维度:在“硬件性能(内存上限)”“加载耗时(同步/异步选择)”“回收耗时(避免阻塞主线程)”三者间找平衡,确保运行流畅。



2.3 功能层

功能层靠Tick让世界“动”,靠多核让运行“快”——前者定节奏,后者提性能,俩都得吃透才能做流畅游戏

Tick的魔力—— Tick Logic 和 Tick Render

  • Tick = 世界的最小时间片:现实里物理过程有最小单位,叫普朗克时间。在游戏中,每一个Tick,就是我们构建的这个世界里的普朗克时间,当一个Tick之后,系统就会依次把所有该做的事情做完

  • 功能层用“Tick”驱动一切;每个 Tick 先推进逻辑世界,再输出可见画面。逻辑和表现要强分离!典型流程是:Tick Logic → Tick Render。Tick Logic把这个世界模拟出来,然后再去渲染它。我们应该严格逻辑先于渲染,避免“看起来命中但实际未命中”的判定错位。

  • Unity 的生命周期中也体现为 PlayerLoop 的顺序分工(物理、游戏逻辑、相机/收尾),渲染提交在逻辑之后。

  • 固定步长 vs 可变步长:为物理/数值稳定,逻辑常用固定步长(如 60Hz);渲染可跟随显示刷新(30/60/120Hz)。若仅用可变步长,积累误差会带来“抖动/穿模”;推荐做法:固定逻辑步长 + 插值渲染(Gaffer 的 “Fix Your Timestep” 方案)。

  • 剔除与提交:逻辑产出“真实状态”(位置/动画帧/粒子触发);渲染阶段基于该状态做可见性裁剪、LOD、阴影/光照计算,并提交到图形 API(RHI/Graphics API)统一接口。

  • 引擎功能层和游戏业务层功能层在很多引擎的架构中,它经常和具体的游戏关联在一起,让你无法区分。比如相机控制,第三人称的射击游戏,希望这个相机有手持摄像机的摇晃感,那这个时候相机的摇晃、镜头的模糊、拉远拉伸等等,它其实是跟这个游戏有密切的关系的。这个模块是不是应该作为游戏的代码,而不是作为游戏引擎的代码呢?所以很多时候哪些功能属于游戏,哪些功能属于引擎,基本上就会在功能层上打架。

多核时代的游戏引擎架构

  • 现在计算机的架构,已经在十几年前逐渐从单核走向多核。未来的多核时代,也会是游戏引擎架构的一个核心方向。未来的引擎架构一定是个多核架构。

  • 三线程基线:现代引擎至少拆为游戏/逻辑线程(Game Thread)、渲染线程(Render Thread)、加载/IO线程;现在的商业引擎,比如Unity或者Unreal,他们会再往前走一步,会把一些特别容易并行化的计算(比如物理、一些其他的animation等)单独地fork出来。

  • 任务图 & Job System:将可并行的工作(动画蒙皮、可见性评估、粒子更新等)拆成原子任务(Job),由任务图/调度器按依赖关系在多核上分发执行;Unreal 的 Task Graph、Unity 的 C# Job System(配合 Burst/ECS)属于这一类。关键在依赖管理,很多计算中间是有一个依赖关系的,学术术语叫做dependency。(如”动画 → 粒子发射”的先后约束)。

  • 比如我现在拿手一挥,手上想打出个特效,那首先这个动画系统要算完,直到我的手到达特定位置,这时候才能把位置传导给粒子系统,然后把一个个小的光的粒子发出来。粒子系统的计算和动画系统的计算,对于角色来讲必须要有个先后关系。这一套非常高效率的多核并行架构,它难就难在dependency的管理上面,能够让它不出乱子。

  • 吃满 CPU 的正确姿势:理想状态是所有核心都保持高利用率,长耗时任务被切分并行而非“长尾拖帧”。实践上常见组合是:逻辑线程持续产出命令 → 渲染线程与 RHI 后端并行提交 → 后台 IO/解压/反序列化穿插


2.4 核心层

核心层是引擎的 “高性能基建”—— 靠定制数学库提速度、自研数据结构控内存、类操作系统内存管理稳性能,是上层功能(渲染、物理)的效率基石。
特点:实时优先而非绝对精确数据要贴Cache内存要自己管。这一层一旦写好,要非常稳定,非必要不要改。

数学(高性能)运算

  • 游戏引擎里面,数学用的并不是特别高深。所以游戏引擎所需要的数学库,基本的数学概念并不是特别复杂。基本上大学的够用了。

  • 真正落地时,核心数学库通常会提供这几类东西:

    • 基础线代:Vec2/3/4,Mat3/4,Quaternion,Transform;
    • 几何工具:AABB、OBB、Ray、Plane、Frustum,用来做裁剪、碰撞、视锥判断;
    • 数值小工具:Lerp / Slerp / Clamp / NearZero / FastInvSqrt 之类,都是为了让上层写动画、写摄像机、写物理时不用重复写数学。

  • 为什么在游戏引擎里面,会单独写这些数学库呢?因为游戏引擎的一切都是为效率服务的,它是一个实时(real time)的应用。所有用户的输入和反馈必须是实时(real time)的,数学库需要对性能非常敏感。为了在 1/60 秒内把一大堆向量、矩阵、四元数、碰撞参数统统算完。所以它会更偏向“近似但快”而不是“高精度但慢”。

  • 经典例子是 Quake III 里倒数平方根的近似写法,思路就是:先给你一个够用的近似,再用一两步迭代逼近,这比直接用标准库要快很多,符合游戏“实时交互”场景的要求。

  • 现代CPU发现游戏向量多、矩阵多,所以提供了 SIMD / SSE / AVX 这种“一条指令算一排数据”的能力。核心层的数学库会主动利用它,把4个向量加法、4个矩阵乘法打包一起做,提升吞吐量。数据能排成一条,就能喂满CPU。

  • 核心难点在“跑得快”,不是“公式多”。怎么把矩阵排成连续内存、怎么配合SIMD做批量乘法、怎么把旋转存成四元数减少内存带宽反而是重点。

数据结构与容器

  • 核心层需要为上层逻辑提供基础服务,其中重要的东西就是数据结构和容器。核心层需要做一套我们自己的数据结构,要让它几乎没有内存碎片,并且访问效率要非常高。

  • 理论上 C++ STL 也能用,但引擎不喜欢它的两个缺点:1) 扩容策略不可控,2) 容易产生碎片。比如标准 Vector 一旦不够,会按倍数扩、还要搬家;你要是刚好在一帧里扩好几次,就会看到帧时间抖一下。这就是为什么很多商业/自研引擎都会自己包一层“引擎容器”。

  • 引擎容器的常见需求:

    • 可控增长:不是 100 → 200 → 400 这样翻倍,而是一次性先给一大块;或者干脆固定容量,溢出报警。
    • 少碎片 / 好预测:尽量让同一类对象挤在一段连续内存里,提高CPU的Cache命中率。这是典型的 Data-Oriented Design(数据导向设计)。
    • 访问顺序友好:容器会鼓励你“顺着遍历”,而不是像业务代码那样来回跳索引;因为一跳索引就可能 Cache Miss。

内存管理

  • 做引擎像做一个迷你操作系统,内存管理是操作系统最重要的一个工作,比如通过堆栈来管理操作系统,管理我们的内存资源。

  • 图灵机效率高的原因:

    • 把数据放在一起,这样纸带依次访问的时候,速度就会快一点
    • 去访问的时候,数据尽可能沿着纸带的顺序来,就是1234567这种,不要是1473256
    • 如果要申请写/抹掉数据,不要一个一个把它写/抹掉,而要一次一批的写/抹掉
  • 总结:高性能引擎内存管理的“三金律”:数据放一起、顺序访问、批量读写


2.5 平台层

  • 平台层也叫做平台无关,就是把平台的差异性全部掩盖掉,这一层是现代游戏引擎非常核心的东西。

  • 平台层的本质是在上面写核心代码/功能/逻辑,可以无视平台的差别。

  • 例如不同平台间文件路径不同

  • 不同的平台往往使用了不同的图形接口,为了统一代码游戏引擎的平台层提供了RHI(render hardware interface)作为通用的图形编程API,利用虚函数对这些图形API进行封装。RHI包含了统一套程序在不同图形API上的实现,这样上层的图形应用中就可以使用统一的一套API进行编程。

  • 不同的平台甚至可能有着完全不同的硬件架构,比如CPU架构的不同。为了高效地利用平台的计算资源需要平台层将计算逻辑合理地分配到不同平台的计算硬件上。


2.6 工具层

特点

  • 工具层不需要实时(runtime)。

  • 工具层实际上就是允许别人以Level Editor(地图编辑器)为中心形成的一系列编辑器,比如我们很熟悉的蓝图编辑器。

  • 工具层是为游戏开发者提供支持的一层。工具层允许游戏开发者直观地预览不同美术资源在游戏环境中的表现,对于游戏开发者和设计师有着重要的意义。

Digital Content Creation

  • 除了游戏引擎自己的开发工具外,一般还需要导入其它软件和开发工具的资产。原始游戏资源可能2是在Maya、3DMax、Houdini等外部艺术工业软件中完成的,而且一个资源可能需要经过多个工业软件处理。游戏资源在软件中的流转就需要正确得导出与识别。
  • 这就需要asset conditioning pipeline来提供不同资源导入游戏引擎的统一管线。简单来讲就是各种各样的导出器和导入器,比如在3DMax、Maya里面,我们把资产导出去,然后再导入我们自己的Asset,最后进入到游戏引擎里面。所以,我们自己开发的编辑器再加上这个asset conditioning pipeline资产的导入器和导出器,共同构成了游戏引擎的工具层。

2.7 为什么分层架构

分层是管理引擎复杂度的核心——解耦分工、封装简化、平衡稳活,且遵循单向依赖

解耦分工,提升效率

拆分各层职责,让团队各司其职:底层(平台/核心)专注稳定优化,上层(功能/工具)聚焦快速迭代。改上层功能不影响底层,避免代码纠缠,降低协作成本。

封装细节,降低门槛

下层隐藏实现细节,上层仅通过接口调用,无需理解底层逻辑(如不用懂GPU如何读贴图),大幅降低系统认知与维护难度。这个世界如果没有封装,只是把所有的概念散落,那是不可被理解,也是不可被管理的。

平衡稳定与灵活

底层(平台/核心)长期稳定少迭代,上层(功能/工具)灵活响应新需求,兼顾技术根基与玩法创新。在架构中有个基础原则:越往底层的东西越不要去动它。

核心原则:单向依赖

各个层次之间的调用,仅允许上层调用下层接口,禁止反向耦合,避免“牵一发而动全身”的系统混乱。

小提醒

未来在做任何一个需求的时候,首先要去想的是做的这件事情应该属于哪一层,而不是着急地把算法写出来。


2.8 核心要点总结

  • 引擎采用分层架构设计

  • 底层更稳定,高层级更可定制

  • 虚拟世界由的时钟由Tick驱动



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏