(好久之前学的东西整理的笔记了,最近准备找工作,拿出来整理复习下)
游戏引擎的定义:虚幻世界的技术基础,创造生产力的工具,复杂的艺术
对于程序员来说,一个好的游戏引擎需要一个高自由度的API。
引擎的协作工具链是非常重要的。
由于实时更新的要求,引擎的各个部分间解耦度需要非常高
引擎内容:基础框架,渲染,动画,物理,游戏规则系统,特效系统,网络游戏
工具体系:c++反射体系等。
游戏引擎的基本架构:
工具层(Tool Layer):编辑器,与用户直接交互
功能层(Function Layer):设定可视化,可动,可交互的相关功能,如动画,渲染,物理功能等
资源层(Resource Layer):管理游戏相关的数据和文件
核心层(Core Layer):一些最基础且最泛用的功能,如容器分配,内存管理,线性变换等等。
平台层(Platform Layer):操作系统平台,使用的硬件系统平台等等。
第三方库(Third Party Libraries):独立的工具或者一套API等。
由于数据本身可能存在大量冗余信息,且格式多样,因此在导入外来资源时需要进行一层的转换,将其变为符合我们引擎框架的可用的资产(Asset)。而舍弃数据中无用的信息,将其转化为资产的这一过程被称为Imorting Resource。
每个资产都具有一个唯一的识别号,这个识别号被称为全局唯一编号(GUID)。
资产中的数据之间可能会存在一定的关联,而这种关联可以通过关系脚本来描述。
Runtime asset manager:一个实时的文件资产管理器,负责管理各个资产在游戏中的生命周期,与游戏的实时加载相关。不同的资源在游戏中有不同的生命周期,由于内存的限制,不用的资源需要被动态地释放。而实时文件资产管理器正是负责这一部分的,它负责对资源的延时加载和资源回收。
在一个周期(Tick)内,游戏会将各个功能部件全部更新一遍。
Tick logic和Tick Render:一个Tick中必须需要做的事可以分为两类,即Tick Logic和Tick Render,Tick Logic决定游戏中事物发展的逻辑,而Tick Render则负责逻辑发展后结果的画面渲染。
功能层:提供游戏引擎中主要的功能部件。在每个Tick中都会循环执行一次,决定功能中哪些部分属于引擎,哪些部分属于游戏本身。
多线程的功能层:一个功能层的多线程越强大,其执行的效率越高。
核心层定义了引擎功能实现中所需要用到的基本函数。其中,数学库是最重要的。在数学库中,对于数学运算效率的要求很高。数据结构和容器同样重要,编程语言中自带的容器由于其访问效率和内存分配相关算法一般不适用于引擎本身,因此一般引擎都需要自己定义基本容器(如Vector,Map等)以提高访问效率和优化内存管理(如数据存储,顺序访问,批处理等)。
平台层的作用是抹平平台(包括硬件和操作系统)之间的差异度,使其能以一种统一的方式来访问。
RHI(Render Hardware Interface):重新定义Graphics相关的API,将图形硬件(GPU)之间的差别抹掉。
工具层:允许任何人通过可视化界面和脚本来制作游戏,包括自己的编辑器和导入其它软件结果的导入器导出器。一般游戏引擎都会有自己的Asset格式。由于需要经常更新,因此工具层的维护门槛比较高。
DCC(Digital Content Creation):第三方软件生成的文件数据,需要通过Asset Conditioning Pipeline(资产调节管线)来导入或导出到自己的引擎中。
引擎结构的一些基本原则:
1.越底层的代码越不应随意改变。
2.各层之间一般只允许上层调用下层功能,而不能反向调用。
本节的三点核心:
游戏中的物件有:Dynamic game object(动态物),Static game object(静态物),Environments(环境):Sky(天空),Vegetation(植被),Terrain(地形)Trigger Area(触发区),Air wall(空气墙),Navigation,Ruler等等。这些东西都会被统一地抽象为Game Object,简称GO。
注意区别GO和Asset:Asset是存储资源的方式,而GO是游戏里的组件。前者是数据,而后者是游戏中的对象。在描述GO时,描述的内容分为两类:属性(Property),行为(Behavior)。Asset可以定义GO的属性或行为(如脚本),或者成为GO属性所指向的部分(如贴图,模型等)。
组件化:一个Game Object可以被看作由多个Object组成的对象。其中每个更基础的Object被称为组件(Component)。
基于GO的Tick:在每个循环里将按GO的顺序将Tick都执行一遍。这样非常直观,但执行效率并不高。
基于组件的Tick:按组件的顺序执行各个代码(现代游戏引擎的常用方法),由于其批处理的特性,其执行效率比较高。
Event机制(GO之间交互的核心机制):一个GO可以向其它GO发送消息,其它的GO会在下一Tick收到消息并执行相关操作。但是,实际的操作中一般不允许物件和物件之间直接交互,而是将信息发送到一个统一的event管理系统中,用于解决时序性的问题。由于组件之间的消息传递可能会有轻微的循环依赖,因此有时需要延时帧来保证消息传递的时序性。
Scene Management(场景管理):场景中的GO由一个场景管理系统管理,GO根据其特定ID或位置进行排序。若不进行场景管理而直接在一帧中计算全部GO,可能会导致游戏要求算力过高,且实时性被极大地影响。常见的场景管理的方法包括网格,四叉树,八叉树,BSP,BVH等等(这些用于场景管理的数据结构在后面要讲的资源加载和渲染中会起到很大的作用)。
总结:游戏中的全部事物都是Game Object。GO可以用基于组件的方式来描述,其状态在Tick Loop中被更新。GO之间通过event机制系统进行交流,并使用高效的场景管理系统进行管理。
Q1:一个Tick时间如果过长怎么办?
A1:解决办法1:分帧处理,将大量运算平摊到几帧中。
解决办法2:直接tick两帧,比较危险。
Q2:空气墙是怎么实现的?
A2:一般由没有mesh的普通几何体实现。
Q3:tick时,渲染线程和逻辑线程怎么同步?
A3:一般会先执行逻辑线程后执行渲染线程。会分为独立的线程进行。
Q4:空间划分怎么处理动态游戏对象?
A4:如果静态物体较多,则可以采用八叉树等结构。
如果动态物体较多,则一般用BVH等更新比较轻量的结构。
Q5:组件模式的缺点?
A5:基础的编译时效率不如一个整体的class高。同时组件之间也有一套通讯接口机制,而频繁的询问会带来较高的代价。
Q6:event机制如何进行调试?
A6:一般的引擎都支持逐帧查看物体接收或发出的消息。