Unity3D Entitas ECS 框架

一个使用ECS框架,制定Agent移动, 最后使用倒播功能还原路径的功能演示。

20180502 Unity 2018.1 正式版出来了, 带来了内置的ECS和Job System。 在UnityECS中, Entity对应的是GameObject, Component对应的是MonoBehavior, 而System对应的是ComponentSystem。
不过在写这篇文章的测试demo时使用的还是GitHub上的版本, 两者之间仅有API的名称上的区别。
GitHub地址:https://github.com/sschmid/Entitas-CSharp

安装:
版本 1.52
https://github.com/sschmid/Entitas-CSharp/releases

1.使用已编译的版本 https://github.com/sschmid/Entitas-CSharp/releases/download/1.5.2/Entitas.zip
直接将文件解压到Assets目录下即可

2.使用源码安装 git clone https://github.com/sschmid/Entitas-CSharp.git
1.将Libraries/Dependencies 里的文件全部拷贝到 Plugins 目录下
2.将Entitas 和 Addones 目录拷贝到Assets 目录下
3.随便打开一个C#文件,让Unity生成项目文件后,就可以在工具栏启动 Tools/Entitas/Perfererces
4.如果在第三步出错了,那是因为源码文件中 EntitasResources.cs 取资源目录里的文件Version.txt 失败了, 这是由于Unity生成的dll文件不会包含资源,因此要么修改这个函数直接返回版本号(不取Version),要么直接使用官方已经编译好的Entitas.dll

ECS架构概述

ECS架构看起来就是这样子的。先有个World,它是系统(译注,这里的系统指的是ECS中的S,不是一般意义上的系统,为了方便阅读,下文统称System)和实体(Entity)的集合。而实体就是一个ID,这个ID对应了组件(Component)的集合。组件用来存储游戏状态并且没有任何的行为(Behavior)。System有行为但是没有状态。

这听起来可能挺让人惊讶的,因为组件没有函数而System没有任何字段。

from:http://gad.qq.com/article/detail/28682

ECS框架其实在许多年前就已经诞生了,这几年名声大噪源于GDC2017守望先锋的一次技术分享,ECS的理念是组合模式优于面向对象模式,ECS解决的问题是之前OOP框架开发时状态和行为混合,在对象功能非常庞大时维护的成本很高。
ECS的全称是 Entity , Component, System. 其中Entity是对象实体, Component维护了对象的所有状态, 而System则是利用对象身上的Compoent实现各种行为, 在实际编程发现这种编程方式非常类似行为树的构架,开发过行为树逻辑的同学非常熟悉开发模式就是讲各种逻辑进行拆分,分散一个个处理细节的函数,使这些函数可以被行为树任意组合复用。

C#版的ECS利用了许多C#的特性,比如分散类 partical. ECS中Component 实际上是把OOP中的成员属性变成一个个单独的partical class进行拆分,但是本质上组合后还是属于他的成员。
举个例子, 在OOP中一个玩家包含了生命和移动速度的属性 Like this

1
2
3
4
public class Player {
    public uint HP;
    public uint MoveSpeed;
}

而在ECS构架中这个类会变成这样

1
2
3
4
5
6
7
8
9
10
HPComponent.cs

public partial class Player{
    public HPComponent hpComponent;
}

MoveSpeedComponent.cs
public partial class Player{
    public MoveSpeedComponent moveSpeedComponent
}

这里我为什么要把类名标识出来, 这是因为对于程序员开发来说,当你要修改Player的某个属性时,只要关注属性类即可, 不需要去Player.cs中大海捞针找属性在哪,这是一种开发上的快捷。 而对于编辑器来说,这些类都都是partial 聚合类, 本质上都是Player.cs 的一部分与OOP无差。

然后我们来说说ECS的性能问题, 官方宣称在Unity范例中 ECS的性能是原生UnityComponent的100倍, 这是有一定的条件的,首先传统的MonoBehavior都是讲状态和行为不加分离的写在一起,而ECS是拆封成 C,S 的。然后ECS实现了一套Component对象池,同时复用控制Entity行为的System。 举个例子 我就喜欢举例子…

一个简单的角色AI,包含了 MoveSpeed 移动速度 和 AttackPower 攻击力。 在传统OOP中,当你有100个AI的时候,实际上创建了100个MoveSpeed和100个AttackPower。 而对于ECS来说,当玩家进入攻击后,MoveSpeed这个属性实际上是不需要了,因为此刻不需要移动,而ECS构架可以将此组件从Entity上移除这个组件并丢入对象池,给其他此刻需要移动的Entity使用,因此可以节省大量的内存, 这也是ECS的特性可以大量重复使用Compoent,因为Component上没有任何行为!

当这个角色死亡的时候呢,他身上有控制他的行为System 比如 MoveSystem, 此时ECS也会使用对象池缓存这个S,在新的Entity创建时赋予它, 这在MONO撑大内存无法回去问题上也可以节省大量的内存。

但是ECS对于运行效率来说不如传统的状态机那么高效, 因为所有的Entity的成员属性都被拆分成了众多的Component, 而每个System都需要遍历在关注列表中注册所有Component组件,这当中的查询在传统OOP中是不需要的。

那么为什么守望先锋使用ECS框架来开发呢?
1.AI对象少,因此Component少,System处理的组件并不会指数级上升,所以性能ok.
2.帧同步与状态同步以及服务器验证机制, 当把玩家的操作整理到一个PlayerControllerSystem中的Execute中去处理后,只需要记录每帧的操作数据,就可以完成帧同步机制。 而只保存每帧所有的Component,无视所有的System,就可以完成状态同步机制。 可以说非常轻松的实现了帧同步和状态同步的两种功能,可以说一次开发完状态同步功能后,以后新加的功能都不需要再次维护了。 而传统OOP每次新增一个状态概念,比如魔法值之类的,可能就要做状态抽离保存等二次重构的时间。
3.游戏规模大,逻辑复杂,迭代地狱。像文章之前提到的,将Compoent和System拆分到多个单一class(行为树是单一function)中去实现,每个类的长度只有几百行,维护起来根据功能可以快速定位到class,然后快速找到实现function,快速迭代。 我曾经见过一个Player对象有3000 行。。。不装插件定位的话,根本找不到功能。

那么什么项目适合ECS呢?
ECS主要消耗还是在System的消耗上, 因此如果将状态机概念设计进System中,可以解决多个System 同时loop 多个trigger 的情况,具体还是看项目类型,仁者见仁智者见智咯。

最后放上使用状态记录倒播的Demo地址,使用ECS和框架可以非常轻易的找出对象的所有状态和 所有的玩家行为,因为完全被格力开了,这个demo的状态记录只花了5分钟写完了(完全没考虑性能),完全解耦(划重点)来实现状态记录。
https://github.com/dreamfairy/Unity3D_Entity_ECS_DEMO

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.