再谈ECS架构

还是从我最开始接触游戏的时候说起,那时候的客户端架构使用了很多继承,这种设计的思路是“继承了基类也就意味着继承了基类的能力”,如果我们不从语言的角度来讨论继承和组合,只从框架逻辑来说,继承相当与组合的一种特例,继承是静态的组合,也就是说一旦你继承了基类,就无法进行替换或删除这种能力,继承和组合相当于UML中的聚合和组合的关系。这种继承关系将会使子类变的很臃肿。举个例子,比如移动模块,我们可以在天空中飞行,陆地上行走,河水中游泳,每一种移动所需的逻辑和数据是不同的,因此我们我们将移动模块抽象成三个类分别是fly,run,swim。object会根据需求选择继承哪些基类,比如鸟不会游泳,它就继承fly和run这两个基类。但是游戏中这种能力太多了,一个object可能会继承很多个基类,这些基类又划分了很深的层次,对于框架设计来说这十分的不优美。而且这些基类的数据将会都塞进子类中,造成数据的臃肿。为了解决这个问题,我们可以使用组合来替代继承,因为组合是可以动态替换组件的,我们可以抽象一个move组件,然后通过多态实现不同的移动规则。使用组合不仅可以去掉臃肿的继承关系,同时也可以降低数据的臃肿,因为数据都存储在组件中,如果不使用的话甚至可以销毁组件来降低内存的占用。如果不考虑性能其实OOP的组合版本其实已经很完美了。
OOP与OOD在逻辑上最本质的区别是OOP可以立即处理行为,比如AI判断当前玩家需要行走,那么OOP可以立即调用行走函数处理逻辑,而OOD则需要延迟处理,因为需要先收集所有需要移动的数据后再通过system处理逻辑。OOP在逻辑处理上更自由,但是它带来的问题是性能不佳,模块之间耦合太多。OOD正是因为它有限制所以才会容易进行优化,而且因为system之间禁止相互调用,从而强制进行逻辑解耦,OOP很像CPU,而OOD很像GPU。
OOD是面向数据编程因为它的内存是SOA布局,这样可以充分利用高速缓存(提高缓存命中率)提高数据读取的速度,现在的硬件瓶颈大多在内存读取上,cpu的计算能力已经很强大了。SOA和AOS很像列式数据库和行式数据库的区别,而游戏的逻辑大部分是按列读取,因为一个逻辑单元只需要少量的属性进行处理,因此SOA布局对内存访问更加友好。要想充分利用高速缓存,需要满足两个条件,第一内存数据要连续,第二要批量读取,因此ECS架构的核心是SOA内存布局以及system统一处理逻辑。另外因为system统一处理逻辑它在代码缓存命中上是秒杀OOP的。ECS架构最大的问题是组件的改变会导致内存的拷贝,这是不可避免的,因为你要保证高效的内存布局,所以通常在用ECS架构的时候,最好在创建Entity的时就把所有组件创建出来,不要在逻辑中创建或者销毁组件。
OOD的另一大优势是很容易使用多线程进行优化,这点就像gpu一样,用固定的逻辑单元批量处理数据,通常我们可以使用fork/join框架来充分利用多核的cpu。而且如果system之间没有依赖,system之间也可以进行多线程优化。
ECS架构也不是没有问题,比如system的排序是个麻烦的事情,再比如system的处理是流水线式的,不能来回调用。最后给出是否使用ECS架构的个人建议,如果你的游戏需要处理的逻辑单元数量很大且差异不大,为了最求性能最好用ecs架构比如SLG,MMO类型的游戏。如果你的游戏是格斗,moba,arpg游戏尤其是要求实时性比较高的游戏,我还是建议使用OOP,因为OOD的延迟处理可能会影响游戏的手感,而且框架优化只是所有优化工作中很小的一部分,还要考虑团队的接受程度。

你可能感兴趣的:(游戏引擎开发)