关于Unity 2018的实体组件系统(通用名称ECS)二

关于Unity 2018的实体组件系统(通用名称ECS)二
 
将介绍如何在Unity上使用实体组件系统(通常称为ECS)。
这次的内容是Unity提供的ECS API的基本用法,一个小应用程序和并行化。
 
它不包括与Unity的GameObject / Component的合作,以及实际使用。
 
 
获取可以使用ECS的编辑器
Unity2018 和之后的版本都可以!
 
您可以从 https://github.com/Unity-Technologies/EntityComponentSystemSamples 下载官方的一个Demo。
 
之后,下载与您自己的操作系统匹配的编辑器并安装它。
 
 
创建一个可以使用ECS的项目
我将创建一个项目,但我无法使用ECS。
要启用ECS,需要两件事。
 
  • 使使用 .NET 4.x
  • 重写 manifest.json
正常启动Unity并打开 Edit> PlayerSettings> PlayerSettings。
之后,将Scripting Runtime Version脚本运行时版本更改为Stable (.net 4.x)。
 
 
接下来是重写manifest.json。
由于在项目的Root文件夹/ Packages中有一个名为manifest.json的文件,因此我们将按照https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/TwoStickShooter/Pure/Packages/manifest.json 与此处相同的方式重写内容。
 
准备工作完成。
 
最小的ECS项目
首先,尝试尽可能地构建最有意义的功能。
这次要组织的功能就是这样
  • 统计每个帧
 
1、 没有使用 ECS的代码 :
首先我会试着用MonoBehaviour来组织它。这是一个非常简单的代码。
编写完成后,您可以将Counter组件添加到适当的GameObject中。
using UnityEngine;
public class Counter : MonoBehaviour{ public int count;
void Update () { count++; }}
 
接下来,让我们对应于ECS。有三件事要做
它是什么?麻烦? ECS就是这样
 
  • CountData 计数的值
  • CountSystem 实际计数
  • ECSMain 实体
ComponentDatas.cs
using Unity.Entities;
 
// 实体
public struct CountData : IComponentData
{
public int count;
}
 
CountSystem.cs
using Unity.Entities;
 
public class CountSystem : ComponentSystem
{
// System所需的ComponentData列表
struct Group
{
public int Length;
public ComponentDataArray<CountData> countData;
}
 
[Inject] Group group; // 注入请求的ComponentData
 
// 调用每一帧
protected override void OnUpdate()
{
for(int i=0; i<group.Length; i++)
{
var countData = group.countData[i];
countData.count++;
group.countData[i] = countData;
}
}
}
ECSMain.cs
using UnityEngine;
using Unity.Entities;
 
public class ECSMain : MonoBehaviour
{
void Start ()
{
// 获取EntityManager
var entityManager = World.Active.GetOrCreateManager<EntityManager>();
 
// 定义实体的原型
var sampleArchetype = entityManager.CreateArchetype(typeof(CountData));
 
// 实际上基于原型生成实体
entityManager.CreateEntity(sampleArchetype);
}
}
 
 
 
 
之后,如果您将ECSMain附加到适当的对象并Play,则第一步完成。
在Play期间,打开Window > EntityDebugger,当它从Systems列表中找到CountSystem时,它会变白,并且如果实体存在 。
 
 
 
如果没有实体,那么您有可能在没有CountData的情况下创建实体,或者您没有首先创建实体。另外,如果您没有系统,则创建ComponentSystem的代码有问题。
 
 
 
一点评论
看以下内容。
对于每个角色的 ECS 是如下所示。
 
 
那么,首先ComponentData,这里重要的是struct(结构)并继承IComponentData。
将它用作ComponentData时,这两个都是必需的。
 
 
创建实体ECSMain是一种创建实体的翻译,但它从MonoBehaviour事件中调用它,等等。
 
正如你在注释中看到的那样,
 
  • 获取EntityManager
  • 创建实体原型
  • 根据原型信息创建实体
这是一个流程。
 
似乎没有必要考虑ComponentData存储在原型中的顺序以及创建它的时机。只有内在的东西才是重要的。
 
 
 
最后一个关于CountSystem作为ComponentSystem。这是一个两部分组成。
 
  • 组的定义
  • 处理内容
首先是Group的定义和实际injection的代码。
 
ComponentDataArray包含(指向)请求组件的指针,而Length包含具有请求ComponentData的实体数目。
 
在 [Inject] 中,自动注入对请求组的引用。
 
 
 
 
 
下半部分代码... OnUpdate() 部分只需添加数字。请注意,组的内容是一个结构,因此您不能直接分配它。
 
 
增加系统的例子
接下来我会尝试增加系统。
 
如果计数器超出范围,则该过程的内容就像删除一样。
功能解释忽略了更多的效率。
 
 
ECS基本上由类似皮带输送机的流程任务组成,系统处理一些ComponentData并将其传递给下一个系统....
 
因此,“添加计数器并在计数器超出范围时移除计数器”
 
  • 称为“实体包括计数器”的容器
  • 向系统添加计数器
  • 系统 检查计数器并丢弃它,如果它是无用的
 
 
 
为了实现这一点,我会再增加一些。
 
  • Common ComponentData 获取范围
  • 计数超出范围时删除实体的 系统
  • CountSystem之间的依赖关系
 
代码在这里:
ComponentDatas.cs
using Unity.Entities;
 
// 実体
public struct CountData : IComponentData
{
public int count;
}
 
[System.Serializable]
public struct RangeData : ISharedComponentData
{
public int min, max;
}
ECSMain.cs
using UnityEngine;
using Unity.Entities;
using Unity.Collections;
 
public class ECSMain : MonoBehaviour
{
[SerializeField] RangeData range;
 
EntityArchetype sampleArchetype;
 
void Start()
{
var entityManager = World.Active.GetOrCreateManager<EntityManager>();
sampleArchetype = entityManager.CreateArchetype(typeof(CountData), typeof(RangeData));
}
 
private void Update()
{
if(Input.anyKey)
{
var entityManager = World.Active.GetOrCreateManager<EntityManager>();
var entity = entityManager.CreateEntity(sampleArchetype);
entityManager.SetSharedComponentData<RangeData>(entity, range);
}
}
}
RangeSystem.cs
using Unity.Entities;
using Unity.Collections;
using Unity.Jobs;
 
[UpdateAfter(typeof(CountSystem))]
public class RangeSystem : ComponentSystem
{
struct Group
{
public int Length;
public EntityArray entities;
[ReadOnly] public ComponentDataArray<CountData> countData;
[ReadOnly] public SharedComponentDataArray<RangeData> rangeData;
}
 
[Inject] Group group;
 
protected override void OnUpdate()
{
for (int i=0; i<group.Length; i++)
{
var range = group.rangeData[i];
var data = group.countData[i];
if ( data.count > range.max || data.count < range.min )
{
PostUpdateCommands.DestroyEntity(group.entities[i]);
}
}
}
}
 
解释一下代码:
 
首先,我们添加了RangeData,它是用于范围判断的ComponentData。
但是,由于该“范围信息”在大多数数据中具有相同的值,因此使用对多个实体公用的IShardComponentData。
还添加了Serializable属性,以便可以使用Inspector进行设置。
 
 
 
ECSMain添加了这个CountData。
 
(1) 首先,RangeData显示在Inspector上,以便编辑,(2) RangeData作为类型添加到原型中,(3) RangeData设置在创建的Entity中。
 
之后,我们通过按下按钮来改变实体的类型。
 
 
最后是系统。
虽然它是一个系统,当它超出范围时删除实体......当然,它有必要获得“范围”。因此,在(2) 中,RangeData包含在组中。
 
在③中,使用DestroyEntity删除实体。
 
这里值得注意的是Update ① 和 [ReadOnly] 。这两个用法大致相同,似乎如果[ReadOnly]存在,它将在使用[WriteOnly]等的系统之后被调用。同样,如果有UpdateAfter,它将在指定的系统之后被调用。
 
 
 
 
 
 
并行化
在这段时间结束时,尝试并行化ECS处理。目标是增加计数。
 
当处理负载相当长或者有很多对象时,并行化可能是一个好结果。
CountSystem.cs
using Unity.Entities;
using Unity.Jobs;
 
public class CountSystem : JobComponentSystem
{
AddCounterJob job;
 
protected override void OnCreateManager(int capacity)
{
base.OnCreateManager(capacity);
// 做一份工作
job = new AddCounterJob();
}
 
// 发布工作
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
return job.Schedule(this, 64, inputDeps);
}
 
[ComputeJobOptimization] // Burst编译器的优化属性
struct AddCounterJob : IJobProcessComponentData<CountData> //请求ComponentData
{
public void Execute(ref CountData data)
{
data.count++;
}
}
}
 
下图是处理大约6000个实体的时间比较。由于它将并行和burst结合在一起,所以存在很大的差异。
 
 
 
但是,有很多地方不能使用并行,因为这种数据处理和行为在可用数据和行为方面受到限制。在某些情况下,如果您使用C# Job System,您可能可以做更多...

posted on 2018-09-20 15:58  &大飞  阅读(970)  评论(0编辑  收藏  举报

导航