GAMES104.王希.02.引擎架构分层

引擎架构分层与整体 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

核心层

  • Core Layer
  • 将底层代码封装成核心函数库

平台层

  • Platform Layer
  • 兼容不同的平台
    • 硬件平台
    • 软件平台
  • 兼容不同的输入设备

  • 不同的输入设备都需要翻译成游戏中的一个统一的语言

第三方库

  • SDK 形式,直接集成到引擎里面,编译的时候就需要编译进去
  • 第三方工具,和游戏引擎的数据交换通过文件形式
    • simplygon:面片简化工具

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 很重要
    • 例如关卡切换的时候,如果 GC 没写好,很可能导致卡顿(大量资源的回收和生成)
  • 延迟加载:边玩边加载

功能层

Tick

  • 如何让角色动起来?
  • tick
  • 在每一个 tick 时间,执行一遍所有操作

  • 游戏每一帧的逻辑如下
1
2
3
4
{
tickLogic(deltaTime);
tickRender(deltaTime);
}

  • 不管渲染还是不渲染,游戏世界中的时间都是向前推进的(事件都是在发生的)
  • logic 和 render 在代码是线上需要分开,不能混在一起
    • 本身做的就是两件事
  • 功能层很复杂,为游戏本身提供了很多功能模块
  • 游戏中的循环周期性的更新系统的状态
  • 游戏和游戏引擎关系复杂
    • 有些模块到底是该位于游戏还是游戏引擎说不清楚
    • 例如某个游戏可能需要有相机摇晃的感觉,这个功能应该是游戏还是游戏引擎呢?
    • 一个评论:业务与底层的关系,但是业务可以沉到底层实现复用
  • 有些功能是很清晰的属于游戏引擎的
    • 绘制、pipeline、资产管理

多线程

  • 多种架构
  • 入门级实现
    • Entry(Fixed Thread):某个线程只做固定的事

  • 主流架构
    • Mainstream(Thread Fork/Join)
    • 将一些容易并行的计算使用多线程实现

  • 未来游戏引擎的实现
    • Advanced(JOB System)

  • 多线程的问题
    • 需要处理数据依赖
  • 未来的引擎架构一定是多核的

核心层

数学库

  • 变换

  • 为什么要抽象成一个库?
    • 追求效率
    • \(\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

  • 课程的 Mini Engine
    • 逐步开放

  • Github
  • 分层架构、支持基本引擎的功能
  • ECS 架构