02 - 引擎工具链高级概念与应用
本文将简要介绍一些游戏引擎工具链的相关的高级概念与应用,内容包括:
- 世界编辑器;
- 编辑器插件架构;
- 设计叙述工具;
- 反射;
世界编辑器
UE的世界编辑器的UI如下,可见它集成了许多功能:
基础功能
视口
就是编辑器模式下的运行时游戏引擎,给用户自由编辑世界。此外,有时候还会要求有多个不同视角的视口出现,需要好好架构。
万物皆可编辑
编辑器中所有的对象都可被编辑,例如天上的云、人物等。编辑的内容通常有SRT变换,其他属性等。
Gizmo
因此,编辑器通常需要提供如下编辑工具。
鼠标选取
此外,别忘了首先要实现鼠标选取功能。一种方法是使用Ray Casting方法来选择,这需要物理引擎的辅助,且精确性不高;另外一种方法则需要一个额外的Framebuffer,内容是当前视角中所有物体的ObjectID形成的纹理,然后根据选中的位置得到选中的物体。
需要注意的是透明物体的选取,这需要编写一些额外代码来解决。
用不同视图管理对象
除了在场景管理View中列出所有对象外,还需要根据用户需求去给这些对象分类分组。
Schema驱动的对象属性编辑
编辑器还需要编辑选中对象的属性,这些属性是通过Schema组织的数据结构+反射实现的。
内容浏览器
这也是世界编辑器中不可缺少的功能,它可以给所有资产提供预览,也能在不同的游戏项目中共享资源。
地形编辑
大体思路就是根据高度贴图和纹理贴图绘制好一个地形,然后再通过放置植被实例和贴花等装饰地形。
高度刷
高度刷允许用户自定义地形,通过实时绘制高度贴图实现。如何让地形过渡看起来丝滑是一个挑战。能实现自己导入高度贴图,自己定义笔刷更佳。
实例刷
实例刷则允许用户快速生成特色地形,但内存占用较大。
环境编辑
还有环境编辑,例如光源的摆放,天空、路、河流等的生成。可由插件拓展。
规则约束系统
环境之间也有一些规则约束,例如对路的约束:路上不应该有树,路应该适配地形等。
道路的规则系统需要程序来帮忙,通过自定义环境中树、物体等分布的贴图,然后交给程序进行处理,最终留下符合规则的结果。
编辑器插件架构
实际上不可能一下子在编辑器上实现各种各样的功能,这些功能应该以插件的形式拓展编辑器。商业软件中的插件架构如下:
作用范围
对于提供给编辑器使用的插件,它的作用范围是任何系统或对象类型。例如针对粒子系统额外实现了GPU-Driven的版本,针对NPC额外实现了新的寻路功能。这些特性都能作为插件补充编辑器的功能。
插件的组织
对于多个插件的组织,主要有如下形式:
覆盖式
有时候新插件需要完全覆盖掉编辑器原生/老插件的功能,例如地形的编辑。
分布式
各种插件分工合作,将自己独立完成的结果整合到一起作为输出。
管线式
各种插件按顺序合作,插件的输出可当作另一个插件的输入,最终获得成果。
洋葱圈式
在管线式基础上,输出被进一步处理,然后作为输入进入反向管线进行逆处理,获得和最初输入格式相同的输出格式。
版本控制
有时候引擎会升级,但它的插件没有升级,这时候强行使用旧版插件可能会出现兼容问题。因此需要一个合理的版本控制系统,认为某些插件“过时”就会强行弃用它们。
设计叙事工具
如今游戏的叙事越来越重要,会让玩家身临其境的叙事工具才是好工具。如下图所示,叙事工具的核心就是时间轴,在各个时间点上进行不同的事件,从而形成一个绘声绘色的故事。
序列器Sequencer
UE5的Sequencer就是一个很强大的叙事工具,它有如下组件:
- Track:用于选中在Sequencer中要改变的Actor,例如任意角色、摄像机等;
- Property Track:用于选中要改变Actor的属性;
- Timeline:在离散帧中描述时间的轴;
- Key Frame:标记某个特殊帧为关键帧,Actor的相关属性会在这一帧中改变为设定好的;
- Sequence:Sequencer的数据。
反射
反射是游戏引擎中的核心功能之一,引擎是如何知道当前对象可修改的数据并进行修改的,是反射帮的忙。
传统硬编码的局限性
对于传统硬编码,要想从外部调用特定类型的特定函数,只能这样写:
随着Human类型的函数种类越来越多,右边的代码也会越来越冗余,很麻烦。而反射能让我们知道当前对象是什么类,有什么成员、什么方法,然后再自由调用。例如Java,C#等都支持反射了,C++也将在C++26标准实现反射。
反射、代码与工具链
反射是代码和工具链之间的桥梁,代码通过反射后会生成相关元数据、Getter和Setter方法等供引擎调用。
反射的实现
从代码中获取类型信息
对于高级语言来说,它的编译管线已经是约定俗成的了:
我们要做的就是从编译器(如Clang)中获取这个抽象语法树AST的信息:
生成Schema
获得AST中的关键信息(如类型名、成员变量名、成员变量类型等)后,需要在内存中将其转换为我们定义的Schema。
实际上,并不是所有类都需要反射,Piccolo引擎通过宏来限定哪些类该反射,这个类的哪些成员该反射:
Piccolo引擎中宏的定义如下:
访问反射信息
接下来需要访问按Schema格式生成的反射信息,有三大类访问器:
- 对于类,需要生成类型信息的getter;
- 对于成员变量,需要生成访问它们的getter和setter;
- 对于方法/函数,需要生成可以调用触发它们的invoke;
这些访问器可不是程序员手写的,是用代码渲染工具自动生成的。代码渲染将代码和数据分离,通过把数据填入代码模板而自动生成相应的代码。
例如使用模板元编程来自动生成代码,使用工具(如Mustache)来自动生成代码,这些操作都是代码渲染。
协同编辑
对于内容创作者来说,游戏引擎的协同编辑功能也很重要。
冲突解决
协同编辑时常遇到的问题就是冲突,如何解决它很重要。
常见的冲突解决方案如下:
把世界进行分层:
难的是分层标准不好确定,且不同层之间的对象可能存在依赖。
把世界划分为不同区块:
解决了分层的缺点,但对于跨区块的物体来说有困难。
现在UE5中解决冲突的方法是,为每个对象创建一个单独文件,粗暴但有效地解决冲突问题。
存在的问题也显而易见,要进行版本管理的文件超级多,需要一个额外的打包过程。
跨平台兼容性
此外还需要解决的就是在一个局域网下,不同设备/操作系统的协同编辑。这通常需要一个服务器,它负责管理用户发过来的命令(原子化操作),然后同步到大家的编辑会话中。
同步性
协同编辑就像多线程一样,也会出现类似竞争条件的问题。因此也可以通过加锁来保证当前操作的原子性,可能用到的锁如下:
实例锁:防止多人同时编辑一个实例。
资源锁:防止多人同时编辑一个资产。
加锁确保操作的原子性,保护用户所操作的资源不受篡改。但它仍然无法防止多人进行撤消重做操作时的竞争条件:
这种情况的解决方法如下:
参考资料
- GAMES104 (boomingtech.com)
待阅读的资料
编辑器
- GPU-Based Run-Time Procedural Placement in ‘Horizon: Zero Dawn’, Jaap van Muijden, GDC2017:https://www.gdcvault.com/play/1024700/GPU-Based-Run-Time-Procedural
- Ray casting:https://en.wikipedia.org/wiki/Ray_casting
- Unreal Sequencer Overview:https://docs.unrealengine.com/4.27/en-US/AnimatingObjects/Sequencer/Overview/
- Unity Timeline:https://docs.unity3d.com/Packages/com.unity.timeline@1.7/manual/index.html
- Plug-in (computing):https://en.wikipedia.org/wiki/Plug-in_(computing)
反射
- C++ Reflection, Austin Brunkhorst, 2016:https://austinbrunkhorst.com/cpp-reflection-part-1/
- Clang:https://en.wikipedia.org/wiki/Clang
- Reflective programming:https://en.wikipedia.org/wiki/Reflective_programming
- Unreal Engine UProperties:https://docs.unrealengine.com/5.0/en-US/unreal-engine-uproperties/
可视化脚本/蓝图
- Unreal Blueprints Visual Scripting:https://docs.unrealengine.com/5.0/en-US/blueprints-visual-scripting-in-unreal-engine/
协同编辑
- Unreal One File Per Actor:https://docs.unrealengine.com/5.0/en-US/one-file-per-actor-in-unreal-engine/
- Uses of layers in Unity:https://docs.unity3d.com/Manual/use-layers.html
- Unreal World Partition:https://docs.unrealengine.com/5.0/en-US/world-partition-in-unreal-engine/
- How Figma’s multiplayer technology works, Evan Wallace, 2019:https://www.figma.com/blog/how-figmas-multiplayer-technology-works/
- Unreal Multi-User Editing:https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/MultiUserEditing/
- Conflict-free Replicated Data Types, Marc Shapiro,Nuno Preguiça, Carlos Baquero, Marek Zawirski,2011:https://pages.lip6.fr/Marc.Shapiro/papers/RR-7687.pdf
- Operational Transformation Frequently Asked Questions and Answers:https://www3.ntu.edu.sg/scse/staff/czsun/projects/otfaq/