Unity Entity Component System 框架

PAGE ONE

前言

  随着Unity新版本迭代,ECS也逐渐走入大家的视野,甚至有人在Unity官方论坛发问ECS是否已经成熟稳定可以商用。本人也是在学习当中,网上已经有各种深入浅出的说明,但缺乏一个可以跟着敲代码的demo、让人短时间内就能从代码层面了解ECS的教程。

  本文就致力于用最简单的方式,让你跟着敲一遍代码就大概知道什么是ECS。

需要的的准备

  • 新建一个工程,确认你的版本支持ECS(Unity 2018 以上的版本都支持ECS),我这里用的是2018.3
    • 在 Window 的 package manager 中安装(导入)好 Entity package
    • 修改PlayerSetting->Other Setting->Configuration->Scripting Runtime Version为4.x
    • 接下来在工程的Package目录(Assets同级)放入manifest文件以启用ECS框架 点此获取官方manifest样例

  在ECS框架中,原本类似MonoBehavior的职能被拆分成纯数据以及操作数据的系统,所以你将会看到大量xxxComponent,xxxSystem的类。比如接下来我们就要建立一个只带移动方向的移动组件、修改移动方向的输入系统以及控制移动的移动系统。

Demo内容: 场景里放个球,键盘上下左右控制位移。

新建几个C#文件:

表示移动数据的MoveComponent.cs

1
2
3
4
5
6
7
using Unity.Entities;
using UnityEngine;

public class MoveComponent : MonoBehaviour
{
public Vector3 moveDir;
}

表示移动系统的MoveSystem.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Unity.Entities;
using UnityEngine;

public class MoveSystem : ComponentSystem
{
public struct Filter
{
public Transform tf;
public MoveComponent moveComponent;
}

protected override void OnUpdate()
{
foreach (var entity in GetEntities<Filter>())
{
Vector3 pos = entity.tf.position + entity.moveComponent.moveDir * Time.deltaTime * 3;
entity.tf.position = pos;
}
}
}

表示输入系统的InputSystem.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Unity.Entities;
using UnityEngine;

public class InputSystem : ComponentSystem
{
struct Data
{
public ComponentArray<MoveComponent> moveArray;
}

[Inject] private Data _data;
protected override void OnUpdate()
{
for (int i = 0; i < _data.moveArray.Length; ++i)
{
_data.moveArray[i].moveDir = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);
}
}
}

  场景中新建一个球体,添加一个GameObjectEntity组件以及我们刚刚新建的MoveComponent组件

  启动游戏,按一下上下左右,是不是可以控制球的移动了?

原理

  在上面的例子里,输入系统每帧检测输入,把输入赋给移动组件,_data是包含所有MoveComponent的数组,这个数组是通过[Inject]属性标签注入进去的。这个注入的标签很有意思,它会匹配好里面你需要的组件给你放进去,它的原理我们找时间再深入研究。而移动系统就更简单了,就是遍历所有移动组件,然后根据moveDir去移动它对应的transform。

  大概你也注意到了InputSystem和MoveSystem的遍历方式是不一样的,这是为了演示可以有不同的遍历方式。而且要注意,上面例子里MoveComponent继承自MonoBehavior,如果它继承自IComponentData,它就只能是一个struct,而struct并不能够像MoveSystem那样去遍历,而且也不能直接用组件方式添加到一个GameObject上了。

  继承自IComponentData的好处自然是让纯数据的组件以及系统与GameObject分离,试想一下你可以同时跑100w个移动的对象,但这些对象却不一定需要一个GameObject,这就是ECS带来性能提升的一大原因。

  你可能觉得奇怪,球上只添加了两个组件,怎么就移动了,MoveSystem既没new出来也没拖到什么对象上,谁在激活这些系统?答案是世界,当然这是另一个话题了,在本篇里你只需要知道有个“世界”在运行你写的System就够了,只要你写了个系统继承自ComponentSystem,它就会被执行。


PAGE TWO