最近在学习unity的ecs框架,转载几篇写的比较好的文章帮助理解
原文日期 2019-12-5 避免误导未来使用正式版的开发者。
在ECS中,查询实体数据是非常重要的,也是我们不断在做的事情,之前有简单地提到了EntityQuery,这次我们来详细地解释。
首先,EntityQuery相当于是保存筛选条件的对象,然后从所有的实体中筛选满足条件的数据。EntityQuery提供了多种筛选数据的方式,接下来,先回忆之前提到过的最简单的使用方式。
我们来创建三个组件:
1 2 3 4 5 |
public struct ComponentA : IComponentData { }
public struct ComponentB : IComponentData { }
public struct ComponentC : IComponentData { } |
这三个组件没有任何字段,很简单。
至于创建实体的代码,我就不展示了。
接下来看看最简单的EntityQuery的使用方式,下面是System代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using Unity.Entities; using Unity.Jobs;
public class System_EntityQuery : JobComponentSystem { private EntityQuery m_query;
protected override void OnCreate () { base.OnCreate();
m_query = GetEntityQuery(typeof(ComponentA), typeof(ComponentB)); }
struct EntityQueryJob : IJobForEach { public void Execute (ref ComponentA c) { } }
protected override JobHandle OnUpdate (JobHandle inputDeps) { return new EntityQueryJob().Schedule(m_query, inputDeps); } } |
我们通过GetEntityQuery(typeof(ComponentA), typeof(ComponentB))获取了一个EntityQuery对象,这个对象指明了要筛选同时包含ComponentA和ComponentB组件的实体。
然后在调用Job的Schedule函数时,把EntityQuery对象传递进去。
于是我们在EntityQueryJob中能获取的实体必定是包含ComponentA和ComponentB组件的。
也许大家会觉得,IJobForEach不也可以筛选特定组件的实体吗?
没错,通过给IJobForEach指定泛型,如:IJobForEach
但是,EntityQuery更加灵活,因为它能满足更复杂的筛选方式。
接下来, 我们来看看EntityQuery更强大的筛选方式,直接看代码:
1 2 3 4 5 6 7 8 9 |
protected override void OnCreate () { var query = new EntityQueryDesc { All = new ComponentType[] { typeof(ComponentA), ComponentType.ReadOnly };
m_query = GetEntityQuery(query); } |
以上是创建EntityQuery对象的代码,这次我们先创建了一个EntityQueryDesc对象,然后再将EntityQueryDesc对象传给GetEntityQuery来创建EntityQuery对象。
在创建更复杂的EntityQuery对象时,就需要用到EntityQueryDesc,上面代码中,EntityQueryDesc有一个All字段,我们给All字段赋值了一个ComponentType数组,代表要筛选同时包含这个数组里所有类型的组件的实体。
其中,ComponentType.ReadOnly代表这个组件筛选出来之后是只读的。
因此,以上代码创建的EntityQuery将筛选出同时包含ComponentA和ComponentB的实体,并且筛选出来的ComponentB组件只能进行读取操作。
除了All以外,还有两种字段——Any和None
Any:实体包含指定的任意组件类型即可满足筛选条件
None:实体不可包含指定的任意组件类型
All、Any、None可以一起使用,如:
1 2 3 4 5 6 7 8 9 10 11 |
protected override void OnCreate () { var query = new EntityQueryDesc { All = new ComponentType[] { typeof(ComponentA), typeof(ComponentB) }, Any = new ComponentType[] { typeof(ComponentC), typeof(ComponentD) }, None = new ComponentType[] { typeof(ComponentE) } };
m_query = GetEntityQuery(query); } |
以上代码创建的EntityQuery将筛选出满足以下条件的实体:同时包含ComponentA和ComponentB组件,且包含ComponentC或ComponentD中的其中一个组件,且不能包含ComponentE组件。
假设我们有一个共享组件SharedComponentA,它有一个字段:int num。
我们希望筛选出num = 1的那些共享组件的实体,那么,我们可以这么做:
1 2 3 4 5 6 |
protected override void OnCreate () { m_query = GetEntityQuery(typeof(SharedComponentA));
m_query.SetSharedComponentFilter(new SharedComponentA { num = 1 }); } |
首先创建一个EntityQuery,并且筛选条件是包含SharedComponentA。
然后,调用SetSharedComponentFilter函数指定更细致的筛选条件:SharedComponentA的num字段必须等于1。
SetSharedComponentFilter函数可以在任何时候调用,并不仅限于OnCreate函数,比如你可以在OnUpdate调用。
至于为什么只有共享组件才能做这种筛选,我暂时没有找到相关的说明。
再来看一段代码:
1 2 3 4 5 |
protected override void OnCreate () { m_query = GetEntityQuery(typeof(ComponentA), typeof(ComponentB)); m_query.SetChangedVersionFilter(typeof(ComponentA)); } |
上面的代码是什么意思呢?
它代表:需要筛选同时包含ComponentA和ComponentB的实体,并且,只有ComponentA的内容发生了改变的那些实体。
这样的话,我们就可以只对那些修改过的实体进行操作了。
但是,要注意,这里有一些坑。
“某个组件被修改过”这句话,和我们正常的理解有点出入。
这里指的是:该组件在其他System中被筛选了且被标记为读写。
什么意思呢,比如某个System执行了下面这个Job:
这个Job筛选了ComponentA,并且使用了ref标记,代表是读写操作。
那么,不管Execute函数内是否对ComponentA进行了赋值操作,ComponentA都算是”被修改”了。
也因此,ComponentA通过了SetChangedVersionFilter(typeof(ComponentA))的筛选。
并且,这种标记为”被修改”的状态,是会影响到整个块(Chunk)的。
比如上面这个System2_EntityQuery,它筛选了包含ComponentA的实体,并且ComponentA标记为可写。
那么,该System的筛选出来的所有实体,其所在的块(Chunk)都会被标记为”ComponentA已修改”。
我的描述有点乱,原文是这样的:
Note that for efficiency, the change filter applies to whole chunks not individual entities. The change filter also only checks whether a system has run that declared write access to the component, not whether it actually changed any data. In other words, if a chunk has been accessed by another Job which had the ability to write to that type of component, then the change filter includes all entities in that chunk. (This is another reason to always declare read only access to components that you do not need to modify.)
也因此,当我们不需要写入操作时,记得把组件标记为只读,否则会有不良的影响。
好了,关于EntityQuery,就介绍到这吧。
注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。