介绍完ECS的大致理念之后,我们接着单独来了解下Entity,Component和System的概念以及使用,首先我们先从Entity开始入手。

Entity概念

我们来看下Entity的组成,代码如下,只有Index和Version两个字段。

public struct Entity
{/// <summary>/// The ID of an entity./// </summary>public int Index;public int Version;
}

我们可以理解为Entity就是游戏中不同的物体,其中只有一个ID,也就是Index用来进行区分。Entity中不存在任何数据和行为,这些交给了Component和System去办了。

在每个World中,会有一个EntityManager来管理该World下所有的Entity,EntityManager利用NativeArray来管理Entities并且纪录了每个Entity和Component的关系。

World

我们可以将其理解成一种Manager,每一个World都拥有一个EntityManager和一系列的System,我们可以自由的创建World:

World newWorld = new World("NewWorld");

ECS中会有一个默认的World(DefaultWorld),可以通过 World.DefaultGameObjectInjectionWorld 来获取它。系统自带的System和我们自定义的System默认都会被添加进这个World。

可以在Entity Debugger(Window -> Analysis 中打开)中查看World

创建Entity

创建一个Entity

前面我们说到Entity是由EntityManager来管理的,而EntityManager又存放与World之中,因此要创建Entity,我们先要获得EntityManager,然后就可以利用其CreateEntity方法来创建我们的Entity了。

EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
Entity entity = mEntityManager.CreateEntity();

若要为Entity添加组件,也需要使用EntityManager来完成,例如添加一个用于存放坐标信息的Translation Component并且赋值:

entityManager.AddComponentData(entity, new Translation(){Value = new float3(0, 0, 0)});

若要删除Entity上的Component,自然是要用RemoveComponent方法了:

entityManager.RemoveComponent<Translation>(entity);

除此之外,我们也可以在创建的时候就指定好需要添加的组件,然后再利用 SetComponentData 赋值,例如:

Entity entity = entityManager.CreateEntity(typeof(Translation), typeof(OtherComponent), ...);
entityManager.SetComponentData(entity, new Translation(){Value = new float3(0, 0, 0)});

前面我们提到Component的不同组合被称为Archetype,因此我们也可实现声明好Archetype,然后用其创建Entity,这样生成的Entity就会带有Archetype所代表的所有Component(其实和上面那种方法是等价的)。

EntityArchetype entityArchetype = entityManager.CreateArchetype(typeof(Translation), typeof(LocalToWorld), typeof(RenderMesh));
Entity entity = entityManager.CreateEntity(entityArchetype);
entityManager.SetComponentData(entity, new Translation(){Value = new float3(0, 0, 0)});

对于已存在的Entity,我们可以使用EntityManager.Instantiate方法来进行copy

Entity entity2 = entityManager.Instantiate(entity);

创建多个Entity

CreateEntity不仅可以创建一个Entity,也可以同时创建多个Entity,前提是要事先声明好EntityArchetype,例如

NativeArray<Entity> entityArray = new NativeArray<Entity>(10, Allocator.Temp);
entityManager.CreateEntity(entityArchetype, entityArray);
entityArray.Dispose();

等价于

entityManager.CreateEntity(entityArchetype, 10, Allocator.Temp).Dispose();;

对于已存在的Entities我们可以进行整体copy,Entities中的数据都会被copy到新的Entities中。

NativeArray<Entity> copyEntityArray = new NativeArray<Entity>(10, Allocator.Temp);
entityManager.Instantiate(entityArray, copyEntityArray);
copyEntityArray.Dispose();

Instantiate也可将单个Entity进行多份copy

entityManager.Instantiate(entity, 10, Allocator.Temp);

销毁Entity

entityManager.DestroyEntity(entity);

对于生成的Entity,我们可以在Entity Debugger中查看

EntityQuery

注:建议先看了Component和System相关内容后再来看。

在ECS中,Entity关联着Component,Component存储着所有的数据,若要读取或者修改这些数据,自然首先要找到哪些数据是我们所需要的。而 EntityQuery 就包含了这些我们想要的数据,其主要功能如下

  • 运行一个Job来处理选定的Entities和Components
  • 获取一个NativeArray 包含所有选定的Entity
  • 根据Component类型获取多个包含选定Component的NativeArray

EntityQuery在返回Entity或Component 的NativeArray时保证是并行的,也就是相同的下标每次获取到值都是一样的。

定义一个Query

想要得到包含我们想要的数据的EntityQuery,那就需要一个查询规则来查询。SystemBase为我们提供了GetEntityQuery的方法来获取EntityQuery,我们可以通过Component Type来进行查询,也就是作为参数传递进去,例如下列代码,可以帮我们找到所有带有Translation和LocalToWorld组件的Entity。

EntityQuery m_Query = GetEntityQuery(typeof(Translation), ComponentType.ReadOnly<LocalToWorld>());

注:对于一些只需要读取的组件,我们使用 ComponentType.ReadOnly<T> 可以提高执行效率

在Entity Debugger中选中我们的System,即可看见该System下我们设置的EntityQuery:

EntityQueryDesc

我们可以使用EntityQueryDesc 来更详细的制定查询规则,带有三个关键字分别为 AllAnyNone(和 Entities.ForEach 几乎一样,就不详细介绍了),例如

var queryDesc = new EntityQueryDesc
{All = new ComponentType[] { typeof(Translation), ComponentType.ReadOnly<LocalToWorld>() },Any = new ComponentType[] { typeof(Scale), typeof(NonUniformScale) }None = new ComponentType[] {typeof(LocalToParent)},
};
EntityQuery entityQuery = GetEntityQuery(queryDesc);

会为我们找到包含Translation和LocalToWorld,且至少包含Scale和NoUniformScale中一个的,同时不包含LocalToParent的Entity。

同时还可以使用多个EntityQueryDesc进行查询,例如

var query0 = new EntityQueryDesc
{All = new ComponentType[] {typeof(RotationQuaternion)}
};var query1 = new EntityQueryDesc
{All = new ComponentType[] {typeof(RotationSpeed)}
};EntityQuery m_Query = GetEntityQuery(new EntityQueryDesc[] {query0, query1});

除了前面提到的 AllAnyNone 三种属性外,我们还可以设置 Options 属性,其值是一个 EntityQueryOptions 枚举,如下:

  • Default: 不设置options的值默认即为Default
  • IncludePrefab: 含有特殊的 Prefab tag component。
  • IncludeDisabled: 含有特殊的 Disabled tag component。
  • FilterWriteGroup: 查询是会检查Component的WriteGroup 标签

FilterWriteGroup:

在定义Component的时候,我们可以利用 WriteGroup 标签来将Component写入一个组中,不同的Component可以设置相同的组,如下,C2和C3都关联在C1的组中

public struct C1: IComponentData{}[WriteGroup(typeof(C1))]
public struct C2: IComponentData{}[WriteGroup(typeof(C1))]
public struct C3: IComponentData{}

我们定义几个EntityQueryDesc,并加上FilterWriteGroup属性,如下:

var query1 = new EntityQueryDesc{All = new ComponentType[]{typeof(C1), typeof(C2), typeof(C3)},Options = EntityQueryOptions.FilterWriteGroup
};
var query2 = new EntityQueryDesc{All = new ComponentType[]{typeof(C1), typeof(C2)},Options = EntityQueryOptions.FilterWriteGroup
};
var query3 = new EntityQueryDesc{All = new ComponentType[]{typeof(C1)},Options = EntityQueryOptions.FilterWriteGroup
};
var query4 = new EntityQueryDesc{All = new ComponentType[]{typeof(C2)},Options = EntityQueryOptions.FilterWriteGroup
};

结果是

query1:找到所有带有C1,C2,C3的Entity

query2:找到所有带有C1,C2,并且不带有C3的Entity

query3:找到所有带有C1,并且不带有C2,C3的Entity

query3:找到所有带有C2的Entity

因此添加了FilterWriteGroup属性后,如果一个组件(如C2,C3)写入另外一个组件(如C1)的组中时,当组拥有者的组件(C1)被查询时,ECS会为我们剔除(类似 None 属性)改组下没有明确声明要被查找的组件。如query2,C1,C2被声明的要查找,所以C3就会被剔除。

创建EntityQuery

在前面我们利用 SystemBase 提供的 GetEntityQuery 方法来获取一个EntityQuery,若在System外,我们可以利用 EntityManager.CreateEntityQuery() 方法来创建一个EntityQuery

EntityQuery query = CreateEntityQuery(typeof(Translation), ComponentType.ReadOnly<LocalToWorld>());

filters

我们还可以对EntityQuery对象设置filter来细分查询的对象

SharedComponentFilter:

基于shared component的值为指定的值做查询,例如下面代码,只会查询到带有SharedGrouping共享组件且 SharedGrouping.Group 的值等于1的数据。

struct SharedGrouping : ISharedComponentData
{public int Group;
}class ImpulseSystem : SystemBase
{EntityQuery query;protected override void OnStartRunning(){base.OnStartRunning();query = GetEntityQuery(typeof(SharedGrouping));query.SetSharedComponentFilter(new SharedGrouping { Group = 1 });}
}

ChangedVersionFilter:

只查询Component发生了改变的数据,如下面代码,只有当别的System修改了Translation时(此修改只需要给予到Translation写的权限即可),该System才能查询到这些被修改了的数据

query = GetEntityQuery(ComponentType.ReadWrite<Translation>());
query.SetChangedVersionFilter(typeof(Translation));

为了效率基于change的过滤应用于整个Chunk而不是单个Entity。并且是否改变的判断只检测Component是否获得了写的权限,而不是其值是否变化,这也印证了是应用于整个Chunk的理念。

可能描述的不太好,举个例子:例如我们有Entity1带有C1组件,Entity2和Entity3都带有C1,C2两个组件,那么Entity1肯定存在Chunk1中,Entity2和3存在Chunk2中。若我们通过C1,C2的Query来查询可以找到Entity2和Entity3,若我们要修改Entity2的C1的值,那么必须给予C1写的权限。那么Chunk2将被标记为改变,即使只是改变了其中Entity2的C1值。此时若有个通过C1的Query来查询,正常会找到Enity1,2,3三个,但是若我们设置了ChangedVersionFilter(typeof(C1)),那么就会找到Entity2和3而不是仅仅只找到值改变了的Entity2。

我们可以在任何时候进行Filter的设置,同时使用 ResetFilter 方法可以清除这些设置。

获取数据

我们可以通过下列方法来获取到我们需要的数据

  • ToEntityArray() :将查询到的数据转换为Entity数组
  • ToComponentDataArray<T>() :将查询到的数据转换为类型为T的Component数组
  • CreateArchetypeChunkArray():返回查询到的数据的Chunk信息,例如我们查询到的Entity分别存放在了三个Chunk中,那么就会返回带有这三个Chunk信息的数组。

ECS的简单入门(二):Entity相关推荐

  1. ECS的简单入门(一):概念

    官方文档:https://docs.unity3d.com/Packages/com.unity.entities@0.9/manual/ecs_core.html 视频资料:UUG Online直播 ...

  2. ECS的简单入门(六):传统GameObject模式转换到ECS模式

    在前面的介绍中,我们都是通过代码来实现物体在场景中的显示,例如我们要显示一个Cube,通过创建Entity,添加RenderMesh等Component,设置相应的Material和Mesh值即可.这 ...

  3. ECS的简单入门(四):System

    概念 在System中,我们就可以对Component中的数据进行处理了,例如我们每帧改变Translation Component的值,就可以使Entity移动起来. ECS为我们提供了以下几种Sy ...

  4. ECS的简单入门(三):Component

    Component概念 ECS中的Component和我们以往MonoBehaviour的Component不同,ECS的Component中,只用于存放数据,并不会有行为.操纵数据的行为我们交给了S ...

  5. mysql触发器的简单入门(二)

    2019独角兽企业重金招聘Python工程师标准>>> 第一篇链接 https://my.oschina.net/aijiaoer0624/blog/1529475 先贴上代码: C ...

  6. Python 简单入门指北(二)

    Python 简单入门指北(二) 2 函数 2.1 函数是一等公民 一等公民指的是 Python 的函数能够动态创建,能赋值给别的变量,能作为参传给函数,也能作为函数的返回值.总而言之,函数和普通变量 ...

  7. Vue简单入门及使用(二)---基本语法及组件使用

    Vue简单入门及使用(二)---基本语法及组件使用 Vue文件解读 基本语法 Vue组件使用 前言:做一个有梦想的程序猿! Vue文件解读 以HelloWorld.vue文件为例 一个vue文件里面包 ...

  8. ArtCAM入门简单教程(二)——浮雕

    记录过去,奋斗现在,展望未来 (给未来即将结束的工作的'存档') ArtCAM入门简单教程 二.浮雕 前言:这不是个很专业的浮雕设计教程,不过是很简单即可实现的入门教程,而更深入的浮雕学习建议查找更全 ...

  9. 有限元方法入门:有限元方法简单的二维算例(三角形剖分)

    有限元方法简单的二维算例(三角形剖分) 文章目录 有限元方法简单的二维算例(三角形剖分) 算例描述 变分问题 有限元离散 问题转化 有限元三要素 参考单元与一般单元 一般单元上的形函数 一般单元上的积 ...

  10. 有限元方法入门:有限元方法简单的二维算例(矩形剖分)

    #有限元方法简单的二维算例(矩形剖分) 算例描述 我们对下述椭圆边值问题 \label{eq1}{−Δu=fu∣∂Ω=0\left \{ \begin{aligned} & -\Delta u ...

最新文章

  1. WCDMA系统中的扰码规划
  2. 深度学习中的优化算法之MBGD
  3. php网页添加图片的代码,天天查询-PHP版的kindeditor编辑器加图片上传水印功能
  4. 为实验室部署Symantec Antivirus
  5. 用美颜照当广告犯法!要么就标注“照骗”,挪威针对明星网红出手了
  6. python内置变量__complex___Python 内置方法
  7. EMNLP 2020 《MUTANT: A Training Paradigm for Out-of-Distribution Generalization in VQA》论文笔记
  8. dubbo源码解析(十)远程通信——Exchange层
  9. ruby打印_Ruby程序打印一个数字的乘法表
  10. 安卓布局,GridLayout
  11. 码云推出企业 Git 和项目管理现场培训服务
  12. webstorm注释写出的提示
  13. Topaz Gigapixel AI 人工智能图像放大
  14. C/C++程序设计与算法第十一周:零点定理求方程的根
  15. SSL_2278 Oliver的救援
  16. python多线程模块threading学习笔记(5)之锁Lock
  17. 一台计算机两个硬盘怎么设置密码,怎样给硬盘加密码? 两招解决硬盘加密难题...
  18. matlab绘制布尔运算图,最强干货——PPT布尔运算最全攻略
  19. 2023最新自助下单彩虹云商城系统源码+免授权无后
  20. 深度学习:蒸馏Distill

热门文章

  1. 笔记本电脑f11功能键_笔记本电脑按键功能详细图解_笔记本电脑键盘功能详细介绍是什么-win7之家...
  2. 简单理解O2O商业模式
  3. 2015校园O2O商业模式解析——从社交切入
  4. mysql取出的日期数据缺少时分秒_datetimepicker只显示日期,不显示时分秒
  5. Win11修改Hosts文件无法保存怎么解决?
  6. android sqlite 单例模式,安卓SQLite基础使用指南
  7. 苹果自带相册打马赛克_科普 | 谨慎使用苹果自带的笔打码订单哦!
  8. 行杂记之Zookeeper SessionTimeOut分析
  9. 牛客刷题——两种排序方法
  10. aix oracle 10.2.0.1 升级 10.2.0.4,【江枫 】AIX平台升级到Oracle10.2.0.4的几个问题