0%
Theme NexT works best with JavaScript enabled
引擎架构分层与整体 pipeline
Layered Architecture of Game Engine
如何开始看游戏引擎的海量代码?
现代游戏引擎的 5 层架构
游戏引擎分层简介
工具层
Tool Layer
Chain of Editors
在游戏引擎中,首先看到的是各种各样的编辑器
功能层
Function Layer
让游戏能过够运行起来
Make It Visible, Movable and Playable
动画、渲染、物理系统、脚本、FSM(有限状态自动机)、AI、相机、交互界面、HUD(抬头显视设备)、输入输出
HUD
资源层
Resource Layer
数据和文件:Data And Files
核心层
平台层
Platform Layer
兼容不同的平台
兼容不同的输入设备
第三方库
SDK 形式,直接集成到引擎里面,编译的时候就需要编译进去
第三方工具,和游戏引擎的数据交换通过文件形式
5 层架构
如何实现 5 层架构?
如果想实现一个动起来的角色,5 层架构分别要做些什么?
以这个问题为线索,说明各层的作用
资源层
数据引擎化
拿到了资源之后,怎么样导入使用这些资源?
能不能直接加载?
在游戏引擎这么复杂的软件里面,我们不可能去逐个问资源的具体格式
另外,很多软件的数据格式是为了软件好操作而建构的,可能比较复杂,存在很多无效信息,直接加载效率非常低
会先将资源做一个转换(import),转换成引擎的高效数据,变成资产(asset)
例如图片,我们将可以图片都转成 dds 这种 GPU 友好的图片格式
dds:DirectDraw Surface,是 DirectX 纹理压缩(DirectX Texture
Compression,简称 DXTC)的产物
另外一个例子,word \(\to\) txt
word 本身提供各种功能,导致文件很大,如果只需要里面的文字,转成
txt,则文件很小
在预处理之后的 asset,加载和使用都会非常块
资产关联
composite asset
将不同的资产关联在一起,表明数据之间的关系
GUID:唯一识别号
Globally Unique Identifier
为每一个资产设定一个 GUID(类似人的身份证)
例如如下的资产关联模式
运行时管理
Runtime Asset Manager
一个虚拟的文件系统,通过文件路径来加载/卸载 assets
handle system:管理资产的生命周期、引用
资源的内存管理(内存是有限的 )
资产的生命周期
在现代游戏引擎架构中,GC 很重要
例如关卡切换的时候,如果 GC
没写好,很可能导致卡顿(大量资源的回收和生成)
延迟加载:边玩边加载
功能层
Tick
如何让角色动起来?
tick
在每一个 tick 时间,执行一遍所有操作
1 2 3 4 { tickLogic (deltaTime); tickRender (deltaTime); }
不管渲染还是不渲染,游戏世界中的时间都是向前推进的(事件都是在发生的)
logic 和 render 在代码是线上需要分开,不能混在一起
功能层很复杂,为游戏本身提供了很多功能模块
游戏中的循环周期性的更新系统的状态
游戏和游戏引擎关系复杂
有些模块到底是该位于游戏还是游戏引擎说不清楚
例如某个游戏可能需要有相机摇晃的感觉,这个功能应该是游戏还是游戏引擎呢?
一个评论:业务与底层的关系,但是业务可以沉到底层实现复用
有些功能是很清晰的属于游戏引擎的
多线程
多种架构
入门级实现
Entry(Fixed Thread):某个线程只做固定的事
主流架构
Mainstream(Thread Fork/Join)
将一些容易并行的计算使用多线程实现
核心层
数学库
为什么要抽象成一个库?
追求效率
\(\dfrac{1}{\sqrt{x}}\) :Quake-III
实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 float Q_rsqrt ( float number ) { long i; float x2, y; const float threehalfs = 1.5F ; x2 = number * 0.5F ; y = number; i = * ( long * ) &y; i = 0x5f3759df - ( i >> 1 ); y = * ( float * ) &i; y = y * ( threehalfs - ( x2 * y * y ) ); y = y * ( threehalfs - ( x2 * y * y ) ); return y; }
SIMD:Single Instruction Multiple Data,单指令流多数据流
数据结构和容器
C++ STL 的实现可能不适用于游戏中的应用场景
我们需要设计自己的数据结构,尽可能减少内存碎片,提高访问效率
内存管理
提高内存使用效率
Memory Pool / Allocator
Reduce cache miss
Memory alignment
Polymorphic Memory Resource (PMR)
高效内存管理
Put data together:把数据放到一起
Access data in order:访问数据的时候尽可能顺序访问
Allocate and de-allocate as a block:读写的时候尽可能一起去读写
核心层特点
平台层
平台无关性,掩盖平台的差异,上层调用能够无视平台特征
例子
文件路径
渲染引擎
Windows:DirectX(DX11、DX12)
MAC:Metal
Android:OpenGLES、Vulkan
重新定义一层 graphics 的 API,隐藏平台特征
Render Hardware Interface (RHI)
对不同的 GPU 架构和 SDK 都是透明的,隐藏差异
对目标平台及其进行自动优化
CPU 架构可能都不一样
Core variants: PPU and SPUs
UMA: unified memory access
平台层的设计很影响游戏引擎的效率,一个贴合硬件、指令集的编译能让效率提高很多
工具层
逻辑编辑器:蓝图
材质编辑器:保证预览的效果和实际游戏的效果都是一样的
level editor:以地图编辑器为主的各种编辑器
工具层是真正生产力的工具,能够实现你的各种想象,允许别人去创造游戏内容
涉及到游戏运行的代码需要注重效率,一般使用 C++ 开发
实现可以有很多灵活的选择,因为只是一个开发工具,和游戏的实时运行无关
C++ Qt:开发效率优先
C# WPF:控件
H5
DDC
Digital Content Creation
Assets Conditioning Pipeline
将不同的编辑器生成的资源通过统一的管线导入到我们的引擎中,生成
Assets
就是很多的导入/导出器,实现数据之间的互导
工具层 = 我们开发的编辑器 + 资产的导入器/导出器
为什么要分层
解耦(decoupling)和降低复杂度
底层实现和上层是无关的
上层不需要知道下层是怎么实现的
封装
类比于城市
一般而言底层的代码不会经常修改,追求稳定
上层代码为了追求效率、追求新功能经常会修改
越往上层越灵活(customizable),越往下层越稳定(stable)
分层架构是很重要的,清晰的架构,只能允许上层调用下层,不允许下层调用上层
Pilot