本笔记仅为个人的理解,如果有误欢迎指出
An Aspect-Based Engine Architecture 一种基于方面的引擎架构
不是很明白为什么GPU的书籍会有游戏引擎架构的文章。
这里Aspect在文章中的意义更像是表述一个功能模块,在Java中有将Aspect翻译成切面,但是Java切面主要是横向的代码注入,与本文的概念不相符。
大多数系统架构都会考虑将各个功能封装成模块或者组件,在面向对象编程的思想下,这个封装是基于对象去实现的,本文则描述了一种在引擎层面的封装功能的架构思想,封装后的产物被称为Aspect,每一个Aspect负责提供一些功能子集,并通过一个通用的接口与引擎核心通信。
引擎核心:
引擎核心的功能是保存游戏或者仿真时的数据结构以及相关状态,功能Aspect将会与这些数据进行交互。一般来说引擎核心会定义一些接口,外部的Aspect则通过接口访问当前的游戏数据

用MVC架构的角度去理解的话引擎核心相当于M层,而各个Aspect则相当于C层。
场景图、场景节点:
与市面上大多数的引擎类似,游戏和仿真环境都抽象成场景概念,交互的数据都以树形的场景节点整合


有虚幻,Unity等引擎经验的人都很熟悉,这就类似里面的场景以及节点,而Aspect则会处理这些数据。但是Node的结构文章中有特别说明,Node在这里设计的思路不是编程里习惯的想法用一个类来表示,在后面的内容中解释了Node的结构应该是一个Map或者Set容器的实例,这里的想法有点像Unity中面向数据的技术栈 (DOTS),这里则将类成员以及相关的值都转化到Map上。
数据访问:
用这种聚合的方式实现Node一个影响是没有一个直接访问节点内指定数据的结构,因此访问数据需要通过请求来完成,通过查询Node里面的内容并整合出一个访问接口让Aspect去处理。在多线程的环境下还需要通过添加锁来处理冲突问题,但一般是通过复制相关的数据到Aspect内部去处理
其他的还有事件系统,这是耳熟能详的基本功能,这里不再赘述。
方面(Aspects):
Aspect这个概念是这篇文章的核心。
引擎核心存储着数据当前的状态以及发生的任何变更,核心不关心游戏的具体内容或是运作方式。这些操作都交由Aspect去处理。
每个Aspect都是一个引擎的功能模块,他可能包括渲染、动画、物理、音频、甚至是游戏运行逻辑。每个Aspect都封装着第三方引擎、框架或者API。
Aspect之间不应该有相互调用,这样会造成且门面之间产生耦合与依赖,这破坏了Aspect的封闭性。这就是简单的分层思想,同层的模块之间不能有任何联系,如果实在要跨Aspect协作,那这应该要放到上层或者在引擎核心处理。

设计Aspect的时候应该避免一些类似当前的图形设备/上下文或窗口句柄之类的静态资源的共享,如果这些共享无法避免,则应该控制好访问的顺序,比如互斥锁之类的。
在这篇文章中Aspect要做到的是能够独立运行,所以才更容易维护与替换,所以每个Aspect都可以独立开发和测试,并通过引擎核心来互相交互,
引擎则会通过一个单一的接口来管理所有的Aspect的生命周期,初始化、更新、关闭等
场景解释(Scene Interpretation):
这个概念比较抽象,按我的理解这个小节的主要作用是想说Aspect如何去处理场景里面的数据。
每个Aspect都会维护一个内部结构,管理他所关注的Node。一般来说Aspect会查询Node上的一些属性值来去确定哪些Node是需要他去处理的,这部分应该是在下一节【节点接口】里面解释。比如物理Aspect则会处理每个Node里面刚体相关的数据,渲染Aspect则会处理节点里面关于网格材质相关的数据。
当一个Aspect被注册到引擎上后,它会遍历所有场景图里面的Node,把Aspect所关注的Node收集到Aspect的内部里,后面Node上的变更事件则会通知到这个Aspect,以此同步自身的内部结构。
节点接口(Node Interfaces):
这个节点接口理解的不是透彻
个人理解的是每个Aspect都会定义一个Interface用来给引擎核心判断当前Node是否符合Aspect这边的要求,同时Aspect也会用这个接口去同步数据到引擎核心侧

实现:
这个引擎架构的一个关键原则是Node不是一个实例化的类而是一个容器,文章给的示例如下:

通过这种Map的形式将Node的成员属性以及相关数据的实例化关联起来
对应的属性接口类则是如下设计:

方面的更新:
方面执行相关逻辑的时候会先向引擎申请锁以确保不会出现脏数据的情况,对于数据访问的管理引擎核心则是通过锁来完成,每次Aspect处理完数据后都要将锁交还给核心。

例子:受到物体受到伤害时改变颜色
假如子弹击中游戏中的一个角色并导致目标的材质值改变时可能发生的一系列操作:
1,在物理方面的更新过程中,子弹与角色的相交被检测到,这里便会生成一个事件并推送到事件队列中。完成这些操作后同步数据到引擎核心。
2,逻辑方面更新的时候,在事件队列里获得了碰撞事件,并调用子弹和角色的碰撞事件处理脚本函数,并更新相关的相关数据,比如生命值,颜色属性,子弹还会从当前场景中被移除。完成这些操作后同步数据到引擎核心。
3,渲染方面更新的时候,则按照当前的数据渲染出物体
目前这个面向方面思想设计的引擎实现的有Cohort Studios' proprietary engine。
分析:
与所有设计一样,基于方面构建引擎有其优势和局限性。基于方面的架构特性主要通过模块化和数据的灵活性有益于开发过程,但其僵化的结构和间接访问方式也对效率产生了限制。
**优势:**基于方面构建引擎的优势包括:
-
提倡数据驱动的开发理念,有助于吸引资产创作者和设计师参与。
-
高度模块化的即插即用架构允许对引擎进行快速更改。
-
模块化特性有助于更快地追踪和调试错误。
-
封装加速了第三方API的集成。
-
着色器和脚本输入的直连使得开发新图形技术和原型游戏功能更容易、更快速。
-
功能知识和管理的去中心化增加了不同方面程序员的自主性。
局限性: 以下是一些局限性:
-
在各个方面内部创建重复或冗余数据,以及在核心中存储数据所使用的聚合结构,可能会显著降低内存效率。
-
方面的异步特性可能使程序员难以处理,因为因果关系在代码中很少直接相邻。
-
试图在多个执行线程上的各个方面之间保持完全自主,需要额外的机制来协调更新顺序。
个人总结:
本文更像是讲述的一种在数据高度集中的情况下如何将各个功能在引擎层面中封装成模块,这种引擎的特点就是数据被管理到引擎核心中,功能模块化后对于引擎的修改会更加方便,因为只需要关注当前模块的功能即可。这边文章个人觉得理解难度不高,涉及的技术难点也不多。
参考链接: