这是侑虎科技第605篇文章,感谢作者放牛的星星供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)
作者主页:https://www.zhihu.com/people/niuxingxing,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!
一、ECS设计思想
ECS设计理念并不是一个新兴的事物,早在90年代就存在了。但是走入大众视野则要归功于《守望先锋》这款游戏。2017年的GDC大会上,《守望先锋》团队在大会上分享了《Overwatch Gameplay Architecture and Netcode》,但他们设计的初衷是用来解决预测和回滚的问题。
1、编程思想的演变
对于我们这代程序员来说,接触和学习的时候就已经是面向对象Object普及的时代。很多人只是在打基础的时候接触过C语言的过程编程。现在又出了一种面向数据的编程,所以现在一起来分析这三种编程思想的不同:
比如:现在有一群狗和一群猪,我们要让它们的尾巴摇起来:
可以看到,面向什么,就重视什么。
2、面向数据的编程
从2017年到现在,ECS在游戏程序员里应该是急速膨胀的话题,有很多优秀的文章都介绍过ECS了。尤其是对于Unity的开发人员而言,除了Unity本身的设计理念相近,面向数据栈的编程也是Unity蓝图计划里的一个部分。用ECS插件,Jobs System Burst编译器等技术内容,来打造一个DOTS的开发理念。
所以说了这么多,ECS究竟是什么?
这里的理解仅仅是从概念上的理解,而不是代码层面的理解,因为Unity的GameObject和Component还是比较重度的继承关系,不适合描述ECS关系的本质。
用上面的例子,写最基础的代码示例对比。
3、OOP示例
定义Dog和Pig类:
处理摇尾巴的过程:
这是我们常规能理解的面向对象编程,当然它是有一定优化空间的,我们可以引入interface来抽象摇尾巴这个动作,那么改良之后长这样:
定义了一个interface,然后让两个动物的类从接口继承。
这个时候处理过程就会变成如下:
这里其实就淡化了狗和猪的本体,只关注它们相近的可以摇尾巴的这个特性。但是它所处理的过程仍然是需要找到对象本身,虽然我们不关注它是猪还是狗,但是我们必须要拿到这个对象才能调用它的方法或者是改变它的属性。
4、OOD示例
再看一下ECS的部分:
首先我们需要一个实体类,这个类真正意义上是一个空对象,只会包含一些常用的组件处理:
这里其实只提供了接口,实现并没有去写,实际的Entity需要对Component进行的管理要复杂一些。这里引入了一个IComponent的组件基类,我们也看一下:
就是一个空的基类,这里为什么要使用class,因为C#语言特性,struct不能继承。interface只关注方法,而我们需要的Component其实是一个数据的集合,所以这里作为演示代码就不写那么复杂的设计,理解概念就好。
这样我们就定义好了Entity和Component的组合关系,接下来实现刚才的例子。因为所有的对象都是一个无意义的Entity,那么我们要标识一个Entity是猪还是狗,直接给它绑定对应的Component就好。(是不是很像Unity的GameObject,绑定Button组件它就是Button,绑定Text组件就是Text)如下:
好了,现在我们能标识这个Entity是什么动物了,但也仅仅如此而已,这个动物有没有尾巴取决于什么?没错,还是组件:
如上图所示,我们再定义尾巴的组件,只有绑定了tail组件的Entity才有尾巴,哪怕它不是动物也有尾巴。
现在知道如何标识Entity,那么接下来如何创建呢?如下:
代码展示了,创建100个对象,前面50个是狗,后面50个是猪,并且它们都有尾巴。(假如这些狗里有泰迪,你可以不用绑定tail组件【手动滑稽】)。
现在E和C都OK了,再看看S长什么样:
瞧,这就是一个摇尾巴的System,简单至极。现在ECS都有了,怎么协同工作?如下:
这里的演示没有考虑性能和设计,只是展示了这个部分的组合工作。前面我们创建了100个Entity,然后用一种方式收集所有的尾巴,交给尾巴的System去摇。(这里的System肯定不是用到一次New一个,只是方便展示)
5、ECS的优势
经过上面两个示例来看,ECS在写法上面要比传统OOP的方式复杂很多,明明一个对象就可以集中包含的数据要多写这么多的Componet来管理,并且System也是多余的,完全可以在类的对象里写完处理逻辑,不是吗?
是的,所以这就是ECS的魅力所在,它让设计分离了。