重谈游戏开发中的ECS设计模式

今晚上听大沐讲了一下他框架中对ECS设计模式的运用,加深了一些理解,故记叙此文供改日再用。

ECS模式主要目的是为了解耦,Unity就是基于此诞生的,据说Unreal也借鉴了这个模式。

之前一直没有机会去尝试ECS模式,用的时候总是很疑惑到底是不是这么设计的。在听大沐讲了一晚上后算是有了一点眉目。

在大沐的ray框架中,ECS模式具体表现在GameObject和GameCompoment上。和Unity有些不同,GameObject拥有一些基本的属性,比如Transform,而GameObject拥有若干个GameCompoment,GameCompoment通过工厂模式可以直接通过命名实例化,而后通过配置加载属性。而GameCompoment之间也可以通过直接获取另一个GameCompoment或者发送消息的方式进行通信(消息是广播的,并且通过Hash配合typeid去标识消息的类型)。

这些设计应该说很正常,基本ECS就应该这么设计。稍微不同的就是GameObject原本应该是个纯粹的容器,在这里还放置了一些常用的属性。事实上我自己的设计也是这么做的,因为没必要为了一些很常见的、公用的属性多放几个Compoment、浪费查询的性能。

但是有一点,是我至今都没弄对的。

看大沐的框架可以很清楚的看到GameObject和GameCompoment,对应ECS中的E和C。那么S呢?

在我的代码中,我有一些为了用ECS而去用的想法,非得抽象出一个System。那么问题来了,我设计一个CollisionCompoment,就非要设计一个CollisionSystem来处理碰撞;如果我设计一个SpriteCompoment存储精灵数据难道还非得设计一个SpriteSystem吗?甚至Component之间的界限很模糊,一个Component也可能被若干个System使用,这种设计下来ECS非但没能做到解耦,反而带进来了很多耦合。

看大沐的设计,事实上这里并没有显式的提出了System的概念,相反,Compoment完完全全作为了一个容器,正真的System则是那些基础组件。比如碰撞,Compoment只是维护了Bullet物理引擎中的一个实体,转发消息、更新位置。比如Camera,CameraCompoment只是维护了渲染系统中的一个摄像机对象,在挂接的时候设置摄像机的场景数据。

换言之,在ECS系统中,设计并不是从E开始的,而是应该从S抽象出C,最后组装到E中的。即为了完成某个目标我需要设计一个什么样的系统,这个系统可以独立运行,但是需要对象的某些属性,会触发某些事件。然后在这个基础上将一组属性抽象出Compoment,最后结合到GameObject中。

这才应该是ECS框架的正确用法吧。