首页 / 电竞头条 / 游戏 / 正文

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

时间:2019-09-04 12:17:14 作者:

这是侑虎科技第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语言的过程编程。现在又出了一种面向数据的编程,所以现在一起来分析这三种编程思想的不同:

比如:现在有一群狗和一群猪,我们要让它们的尾巴摇起来:

  • 面向过程:1、摇(所有狗的尾巴);2、摇(所有猪的尾巴)
  • 面向对象:1、所有狗.(摇尾巴);2、所有猪.(摇尾巴)
  • 面向数据:1、收集所有的尾巴;2、摇(尾巴)

可以看到,面向什么,就重视什么。

  • 面向过程强调的是步骤和过程,所以它只要用过程来解决整体流程就好了。
  • 面向对象强调的是个体,所以它告诉个体,你要做什么。
  • 面向数据强调的是部件(部件是数据的容器),那么我要先收集所有的部件(尾巴),然后一起摇。

2、面向数据的编程

从2017年到现在,ECS在游戏程序员里应该是急速膨胀的话题,有很多优秀的文章都介绍过ECS了。尤其是对于Unity的开发人员而言,除了Unity本身的设计理念相近,面向数据栈的编程也是Unity蓝图计划里的一个部分。用ECS插件,Jobs System Burst编译器等技术内容,来打造一个DOTS的开发理念。

所以说了这么多,ECS究竟是什么?

  • E:Entity 一个不代表任何意义的实体(可以理解为Unity里的一个空的GameObject)
  • C:Component 一个只包含数据的组件(可以理解为Unity的一个自定义组件,里面只有数据,没有任何方法)
  • S:System 一个用来处理数据的系统(可以理解为Unity的一个自定义组件,里面只有方法,没有任何数据)

这里的理解仅仅是从概念上的理解,而不是代码层面的理解,因为Unity的GameObject和Component还是比较重度的继承关系,不适合描述ECS关系的本质。

用上面的例子,写最基础的代码示例对比。

3、OOP示例

定义Dog和Pig类:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

处理摇尾巴的过程:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

这是我们常规能理解的面向对象编程,当然它是有一定优化空间的,我们可以引入interface来抽象摇尾巴这个动作,那么改良之后长这样:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

定义了一个interface,然后让两个动物的类从接口继承。

这个时候处理过程就会变成如下:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

这里其实就淡化了狗和猪的本体,只关注它们相近的可以摇尾巴的这个特性。但是它所处理的过程仍然是需要找到对象本身,虽然我们不关注它是猪还是狗,但是我们必须要拿到这个对象才能调用它的方法或者是改变它的属性。

4、OOD示例

再看一下ECS的部分:

首先我们需要一个实体类,这个类真正意义上是一个空对象,只会包含一些常用的组件处理:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

这里其实只提供了接口,实现并没有去写,实际的Entity需要对Component进行的管理要复杂一些。这里引入了一个IComponent的组件基类,我们也看一下:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

就是一个空的基类,这里为什么要使用class,因为C#语言特性,struct不能继承。interface只关注方法,而我们需要的Component其实是一个数据的集合,所以这里作为演示代码就不写那么复杂的设计,理解概念就好。

这样我们就定义好了Entity和Component的组合关系,接下来实现刚才的例子。因为所有的对象都是一个无意义的Entity,那么我们要标识一个Entity是猪还是狗,直接给它绑定对应的Component就好。(是不是很像Unity的GameObject,绑定Button组件它就是Button,绑定Text组件就是Text)如下:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

好了,现在我们能标识这个Entity是什么动物了,但也仅仅如此而已,这个动物有没有尾巴取决于什么?没错,还是组件:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

如上图所示,我们再定义尾巴的组件,只有绑定了tail组件的Entity才有尾巴,哪怕它不是动物也有尾巴。

现在知道如何标识Entity,那么接下来如何创建呢?如下:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

代码展示了,创建100个对象,前面50个是狗,后面50个是猪,并且它们都有尾巴。(假如这些狗里有泰迪,你可以不用绑定tail组件【手动滑稽】)。

现在E和C都OK了,再看看S长什么样:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

瞧,这就是一个摇尾巴的System,简单至极。现在ECS都有了,怎么协同工作?如下:

Unity手游实战:从0开始SLG—ECS设计思想和Entitas插件

这里的演示没有考虑性能和设计,只是展示了这个部分的组合工作。前面我们创建了100个Entity,然后用一种方式收集所有的尾巴,交给尾巴的System去摇。(这里的System肯定不是用到一次New一个,只是方便展示)

5、ECS的优势

经过上面两个示例来看,ECS在写法上面要比传统OOP的方式复杂很多,明明一个对象就可以集中包含的数据要多写这么多的Componet来管理,并且System也是多余的,完全可以在类的对象里写完处理逻辑,不是吗?

是的,所以这就是ECS的魅力所在,它让设计分离了。

  • 想象一下你是一个重度的游戏,里面有一个Player对象,对象有非常非常多的数据和逻辑,有很多人的工作都和这个对象有牵连。当A在进行逻辑处理的时候,它不得不把整个Player对象传给对于的函数,对吧?如果它不小心
相关资讯