ECS架构概述
ECS术语
实体Entity:像容器一样
组件数据Component Data:要存储在实体中的数据(不包括处理)
组件系统ComponentSystem:处理
组Group:组件系统运行所需的ComponentData列表
Unity官版ECS主要特征如下:
- 数据和行为分离
- 在通常的Unity开发中,我们会将Monobehavior组件挂载到一个Gameobjec上,而ECS中,则将设计为将组建附加到Entity上
- 使用一个池子(pool)来存放所有的Entity
- 可以给Entity设定分组(group)
- 通过matcher来获取指定的Entity
ECS与Unity的GameObject /Component相比,有一个稍微接近的地方。如果完全粗略地说明,则它与以下项目匹配。
Entity=GameObject
ComponentData =Component的字段
ComponentSystem =Component的Update方法
Group = 无
环境
需要环境如下:
- Unity 2018及以上,理论上2017的最后几个版本也可以,主要是要支持.net 4.6
- 安装ECS包
步骤
- 下载最新版Unity,建议官方下载
- 开Unity,新建一个项目:
- 进入Unity后,设置.net版本:依次点击File - BuildSetting - PlayerSetting - Other Setting - Scripting Runtime Version 设置为.NET 4.x Equivalent,会提示重启,点Restart重启Unity即可。
- 打开项目目录(即创建项目的文件夹),找到Packages目录,并找到里面的manifest.json文件
- 打开manifest.json文件,并将以下内容复制进去,并保存:
{
"dependencies": {
"com.unity.entities": "0.0.12-preview.5"
},
"registry": "https://packages.unity.com",
"testables": [
"com.unity.collections",
"com.unity.entities",
"com.unity.jobs"
]
}
- 在此进入Unity的环境时,会进行下载ECS包和导入,导入完成后,我们会发现顶部菜单栏多了Jobs项,
- 菜单栏打开Window - Packages Manager 也可以看到Entities已经安装:
- 例一:
- 接着我们来写个让cube旋转的测试样例:
- 新建一个Rotator.cs脚本
代码如下:
using UnityEngine;
public class Rotator : MonoBehaviour {
public int speed;
}
- 没错,这个脚本继承自monobehavior,但是,他就只有一个字段,即,只有数据
- 再新建一个类,叫RotateSystem.cs,这个类我们让它继承ECS
using Unity.Entities;
public class RotateSystem : ComponentSystem
{
}
- ComponentSystem来自Unity.Entities命名空间,所以,在开头需要进行引用。
- 创建一个结构体,作为容器来存储数据:
struct Components
{
public Rotator rotator;
public Transform transform;
}
- Rotator就是上面创建的数据类,同时这里还存了一个transform,因为我们需要使用transform的实例方法
- 因为继承了ComponnetSystem,所以这个类还需要实现基类的一个OnUpdate方法:
protected override void OnUpdate()
{
}
- 这个方法同monobehavior一样,也是每帧调用。
- 在这个方法中,我们遍历下场景的所有Entity,使用GetEntities接口进行遍历即可
foreach (var item in GetEntities<Components>())
{
}
- 然后就是对遍历出来的子项做一些行为操作:
foreach (var item in GetEntities<Components>())
{
item.transform.Rotate(0f, item.rotator.speed * Time.deltaTime, 0f);
}
- 到这里,代码算是告一段落了,我们需要在Unity中进行一些操作设置
- 在Unity界面下的Hierrarchy窗口下右键创建一个Cube,然后挂载Rotator.cs脚本
var deltaTime = Time.deltaTime;
foreach (var item in GetEntities<Components>())
{
item.transform.Rotate(0f, item.rotator.speed * deltaTime , 0f);
}
- 再给cube添加一个GameObjectEntity脚本(ECS系统内置脚本,不需要你自己写)
- 现在点击运行,并给cube身上的rotator脚本设置speed的值,cube就可以转起来了。
- 你可以多复制几十或者几百个测试下效率。
- 当然,上面那个遍历脚本还可以优化一下,就是将Time.delaTime缓存一下,而不是放到循环里去,
- 最后我们可以通过Windows->Debug->Entity Debugger来查看下信息
例二:
创建一个Cube作为Player,点击Add Component添加GameObjectEntity脚本,然后编写InputComponent脚本,编写代码并添加的Cube上。
代码就这么简单,ECS中Component是数据容器,仅包含与Entity相关的值字段。
using UnityEngine;
public class InputComponent : MonoBehaviour {
public float Horizontal;
public float Vertical;
}
接下来编写移动的System,我们新建一个MovementSystem,用来做对移动行为的控制System
using Unity.Entities;
public class MovementSystem : ComponentSystem {
private struct Components
{
public Transform Transform;
public InputComponent InputComponent;
}
protected override void OnUpdate ()
{
var deltaTime = Time.deltaTime;
var speed = 10.0f;
//遍历所有的所有包含结构体中组件的Entity
foreach(var e in GetEntities<Components>())
{
var vector = new Vector3(e.InputComponent.Horizontal, 0, e.InputComponent.Vertical);
e.Transform.Translate(vector * deltaTime * speed);
}
}
}
然后是InputSystem,代码如下:
using Unity.Entities;
public class InputSystem : ComponentSystem
{
private struct Data
{
//结构体长度,即数据数量
public readonly int Length;
//获取所有包含InputComponent的数组
public ComponentArray<InputComponent> InputComponents;
}
//Unity会自动注入满足此字段的对象
[Inject] Data data;
protected override void OnUpdate()
{
var horizontal = Input.GetAxis("Horizontal");
var vertical = Input.GetAxis("Vertical");
for (int i = 0; i < data.Length; i++)
{
data.InputComponents[i].Horizontal = horizontal;
data.InputComponents[i].Vertical = vertical;
}
}
}
我们可以通过Entity Debuger来查看下信息,可以看到InputComponent下成功找到Entity 0这个对象了,说明我们的注入是成功的。
按下上下左右按键,我们的方块成功移动起来了,可以看到使用ECS,我们Entity对象上仅仅只有包含数据的Component,而我们无需关心其他System是如何运作的,每个System只关心自己本身的实现就行了。