深入理解ECS框架

实体组件系统(ECS)

实体组件系统(ECS)是Unity面向数据的技术堆栈的核心。顾名思义,ECS包含三个主要部分:

  • 实体 -填充游戏或程序的实体或事物
  • 组件 -与您的实体相关联的数据,但由数据本身而非实体来组织。(这种组织上的差异是面向对象和面向数据的设计之间的关键差异之一。)
  • 系统 -将组件数据从其当前状态转换为下一状态的逻辑-例如,系统可能会通过其速度乘以自上一帧以来的时间间隔来更新所有移动实体的位置。

深入理解ECS框架_第1张图片

world

一个World拥有EntityManager和一组ComponentSystems。您可以创建任意数量的World对象。通常,您将创建模拟World,渲染或演示World

public static World Active { get; set; }
public static ReadOnlyCollection AllWorlds { get; }
//实体管理
public EntityManager EntityManager { get; }
public bool IsCreated { get; }
public string Name { get; }
public bool QuitUpdate { get; set; }
public ulong SequenceNumber { get; }
//系统
public IEnumerable Systems { get; }
public int Version { get; }

entitymanager

//每次系统更新后都会增加一个计数器,表示版本
public uint GlobalSystemVersion { get; }
//提供调试信息和操作的对象。
public EntityManager.EntityManagerDebug Debug { get; }
//内部实体数组的容量。
public int EntityCapacity { get; }
//排他实体事务的Job依赖关系。
public JobHandle ExclusiveEntityTransactionDependency { get; set; }
//报告EntityManager是否已初始化。
public bool IsCreated { get; }
//匹配所有组件的EntityQuery实例。
public EntityQuery UniversalQuery { get; }
//最新的实体的版本。
public int Version { get; }
//当前的世界,一个世界有一个EntityManager,而EntityManager管理一个世界的实体。
public World World { get; }

EntityManager提供了一个用于创建,读取,更新和销毁实体的API。

ComponentSystemBase

典型的系统在一组具有特定组件的实体上运行。系统使用EntityQuery(JobComponentSystem)或 EntityQueryBuilder(ComponentSystem)识别感兴趣的组件。然后,系统找到与查询匹配的实体并对其进行迭代,读取和写入数据,并根据需要执行其他实体操作。

//控制此系统在调用其OnUpdate函数时是否执行。
public bool Enabled { get; set; }
public EntityManager EntityManager { get; }
//该系统缓存的查询对象。
public EntityQuery[] EntityQueries { get; }
//此World中当前的更改版本号。
public uint GlobalSystemVersion { get; }
//系统当前版本
public uint LastSystemVersion { get; }
public World World { get; }

 componentSystem

//选择并迭代所有的entity
protected EntityQueryBuilder Entities { get; }
//系统更新功能完成后,要播放的与实体相关的命令队列。
public EntityCommandBuffer PostUpdateCommands { get; }

    当遍历具有Entities的实体的集合时,系统禁止会导致该集合无效的结构更改。此类更改包括创建和销毁实体,添加或删除组件以及更改共享组件的价值。而是将结构更改命令添加到此PostUpdateCommands命令缓冲区。在此系统的OnUpdate()函数返回之后,系统按顺序执行添加到该命令缓冲区的命令。

Entity:

//实体内部列表的索引。
public int index;
//当前实体的版本
public int version;

    尽管实体没有类型,但是可以按与实体相关联的数据组件的类型对实体组进行分类。创建实体并向其中添加组件时,EntityManager会跟踪现有实体上组件的唯一组合。这种独特的组合称为原型。将组件添加到实体时,EntityManager会创建一个EntityArchetype结构。您可以使用现有的EntityArchetypes创建符合该原型的新实体。您也可以预先创建EntityArchetype并使用它来创建实体。

{Entity两个int字段,A组件:一个int字段,B:两个int,C:三个double},如果有六个Entity甲乙丙丁戊己,且都有上面假设的A,B,C组件的话,其Chunk布局如图(顺序为从左到右再由上到下):

引用自:https://blog.csdn.net/yudianxia/article/details/80498015

深入理解ECS框架_第2张图片

IComponentData

    传统的Unity组件(包括MonoBehaviour)是面向对象的类,其中包含行为的数据和方法。IComponentData是纯ECS样式的组件,这意味着它没有定义任何行为,仅定义了数据。IComponentData是一个结构而不是一个类,这意味着默认情况下它是通过值而不是通过引用复制的。

    您会看到每个结构都继承自 IComponentData。这将数据标记为实体组件系统要使用和跟踪的类型,并允许在后台以智能方式分配和打包数据,同时您可以完全专注于您的游戏代码。ComponentDataWrapper 类允许您将这些数据公开到其附加的预制件的检视窗。

    IComponentData结构可能不包含对托管对象的引用。因为所有人都ComponentData生活在简单的非垃圾收集的跟踪大块内存中。

using System;
using Unity.Entities;

namespace Shooter.ECS
{
    [Serializable]
    public struct MoveSpeed : IComponentData
    {
        public float Value;
    }

    public class MoveSpeedComponent : ComponentDataWrapper { }
}

Shared ComponentData

IComponentData适用于实体之间变化的数据,例如存储World位置。ISharedComponentData当许多实体有共同点时,此功能很有用。例如,在Boid演示中,我们从相同的Prefab实例化许多实体,因此,RenderMesh许多Boid实体之间的实体完全相同。

[System.Serializable]
public struct RenderMesh : ISharedComponentData
{
    public Mesh                 mesh;
    public Material             material;

    public ShadowCastingMode    castShadows;
    public bool                 receiveShadows;
}

最棒的ISharedComponentData是,每个实体的内存成本实际上为零。

我们使用ISharedComponentData相同的InstanceRenderer数据将所有实体分组在一起,然后有效地提取所有矩阵以进行渲染。生成的代码简单而高效,因为数据的布局与访问时的布局完全相同。

SystemStateComponents

的目的SystemStateComponentData是允许您跟踪系统内部的资源,并有机会根据需要适当地创建和销毁这些资源,而不必依赖各个回调。

SystemStateComponentDataSystemStateSharedComponentData分别与ComponentData和类似SharedComponentData,但在一个重要方面:

  1. SystemStateComponentData 实体被销毁时不会删除。

DestroyEntity 的简单流程:

  1. 查找引用此特定实体ID的所有组件。
  2. 删除那些组件。
  3. 回收实体ID以重复使用。

但是,如果SystemStateComponentData存在,则不会将其删除。这使系统有机会清除与实体ID相关联的任何资源或状态。实体ID仅在全部SystemStateComponentData删除后才能重新使用。

Dynamic Buffers

DynamicBuffer是一种组件数据,它允许将可变大小的“可伸缩”缓冲区与实体相关联。它的行为类似于承载一定数量元素内部容量的组件类型,但是如果内部容量已用尽,则可以分配堆内存块。

使用这种方法时,内存管理是全自动的。与关联的内存 DynamicBuffers由进行管理,EntityManager以便在DynamicBuffer 删除组件时,也会自动释放任何关联的堆内存。

DynamicBuffers 取代已删除的固定阵列支持。

声明缓冲区元素类型

    A DynamicBuffer是一种组件数据,它允许将可变大小的“可伸缩”缓冲区与实体相关联。它的行为类似于承载一定数量元素内部容量的组件类型,但是如果内部容量已用尽,则可以分配堆内存块。

    使用这种方法时,内存管理是全自动的。与关联的内存 DynamicBuffers由进行管理,EntityManager以便在DynamicBuffer 删除组件时,也会自动释放任何关联的堆内存。DynamicBuffers 取代已删除的固定阵列支持。要声明一个 Buffer,请使用将要放入的元素类型进行声明Buffer

// This describes the number of buffer elements that should be reserved
// in chunk data for each instance of a buffer. In this case, 8 integers
// will be reserved (32 bytes) along with the size of the buffer header
// (currently 16 bytes on 64-bit targets)
[InternalBufferCapacity(8)]
public struct MyBufferElement : IBufferElementData
{
    // These implicit conversions are optional, but can help reduce typing.
    public static implicit operator int(MyBufferElement e) { return e.Value; }
    public static implicit operator MyBufferElement(int e) { return new MyBufferElement { Value = e }; }

    // Actual value each buffer element will store.
    public int Value;
}

尽管描述元素类型而不是描述元素Buffer本身似乎很奇怪,但这种设计在ECS中具有两个主要优点:

  1. 它支持具有多个DynamicBuffertype float3或任何其他公共值类型。您可以添加任意数量的Buffers利用相同值类型的内容,只要元素被唯一地包装在顶层结构中即可。

  2. 我们可以在中包含Buffer元素类型EntityArchetypes,它的行为通常类似于具有组件。

JobComponentSystem

自动作业依赖管理

管理依赖关系很困难。这就是为什么JobComponentSystem我们会为您自动进行。规则很简单:来自不同系统的作业可以IComponentData并行读取相同类型的内容。如果作业之一正在写入数据,那么它们将不能并行运行,并且将根据作业之间的依赖关系进行调度。

public class RotationSpeedSystem : JobComponentSystem
{
    [BurstCompile]
    struct RotationSpeedRotation : IJobForEach
    {
        public float dt;

        public void Execute(ref Rotation rotation, [ReadOnly]ref RotationSpeed speed)
        {
            rotation.value = math.mul(math.normalize(rotation.value), quaternion.axisAngle(math.up(), speed.speed * dt));
        }
    }

    // Any previously scheduled jobs reading/writing from Rotation or writing to RotationSpeed 
    // will automatically be included in the inputDeps dependency.
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var job = new RotationSpeedRotation() { dt = Time.deltaTime };
        return job.Schedule(this, inputDeps);
    } 
}

这是如何运作的?

所有作业以及系统都声明其读取或写入的ComponentType。结果,当JobComponentSystem返回JobHandle时,它将自动向EntityManager和所有类型注册,包括有关其正在读取或写入的信息。

因此,如果一个系统写入component A,然后另一个系统随后从component读取A,则将JobComponentSystem浏览它正在从中读取的类型的列表,从而将对第一个系统的作业的依赖性传递给您。

JobComponentSystem只需在需要的地方将作业链接为依赖项,从而不会在主线程上造成停顿。但是,如果非作业ComponentSystem访问相同的数据会怎样?由于声明了所有访问权限,因此ComponentSystem在调用之前,会自动完成针对系统使用的组件类型运行的所有作业OnUpdate

Entity Command Buffer

EntityCommandBuffer课程解决了两个重要问题:

  1. 在工作时,您无法访问EntityManager
  2. 当您访问EntityManager(例如,创建实体)时,会使所有注入的数组和EntityQuery对象无效。

通过EntityCommandBuffer抽象,您可以将更改排队(从作业或从主线程进行),以使更改可以稍后在主线程上生效。有两种使用方法EntityCommandBuffer

ComponentSystem在主线程上更新的子类有一个可用的自动调用PostUpdateCommands。要使用它,只需引用该属性并使更改排队。从系统Update功能返回后,它们会立即自动应用于世界。

这是一个例子:

PostUpdateCommands.CreateEntity(TwoStickBootstrap.BasicEnemyArchetype);
PostUpdateCommands.SetComponent(new Position2D { Value = spawnPosition });
PostUpdateCommands.SetComponent(new Heading2D { Value = new float2(0.0f, -1.0f) });
PostUpdateCommands.SetComponent(default(Enemy));
PostUpdateCommands.SetComponent(new Health { Value = TwoStickBootstrap.Settings.enemyInitialHealth });
PostUpdateCommands.SetComponent(new EnemyShootState { Cooldown = 0.5f });
PostUpdateCommands.SetComponent(new MoveSpeed { speed = TwoStickBootstrap.Settings.enemySpeed });
PostUpdateCommands.AddSharedComponent(TwoStickBootstrap.EnemyLook);

如您所见,该API与该EntityManagerAPI 非常相似。在这种模式下,将自动模式EntityCommandBuffer视为方便的工具很有用,它使您可以在仍然对环境进行更改的同时防止系统内部的数组失效。

对于作业,必须EntityCommandBuffer在主线程上从实体命令缓冲区系统请求,然后将其传递给作业。当EntityCommandBufferSystem更新时,命令缓冲区将在顺序播放在主线程创建它们。需要执行此额外步骤,以便可以集中进行内存管理,并可以确保所生成实体和组件的确定性。

再次让我们看一下两杆射手示例,以了解其实际工作原理。

系统更新顺序

使用组件系统组来指定系统的更新顺序。您可以使用系统类声明中的[UpdateInGroup]属性将系统放在一个组中。然后,您可以使用[UpdateBefore]和[UpdateAfter]属性在组中指定更新顺序。

ECS框架会创建一组默认系统组,可用于在框架的正确阶段更新系统。您可以将一个组嵌套在另一个组中,以便组中的所有系统都在正确的阶段进行更新,然后根据其组内的顺序进行更新。

组件系统组

ComponentSystemGroup类表示应按特定顺序一起更新的相关组件系统的列表。ComponentSystemGroup是从ComponentSystemBase派生的,因此在所有重要的方面它都像组件系统一样工作-可以相对于其他系统进行排序,具有OnUpdate()方法等。最相关的是,这意味着可以将组件系统组嵌套在其中其他组件系统组,形成一个层次结构。

默认情况下,当Update()调用ComponentSystemGroup的方法时,它将在其成员系统的排序列表中的每个系统上调用Update()。如果任何成员系统本身就是系统组,则它们将递归更新自己的成员。生成的系统顺序遵循树的深度优先遍历。

使用IJobChunk

您可以在JobComponentSystem中实现IJobChunk以逐块迭代数据。JobComponentSystem为包含您要系统处理的实体的每个块调用一次Execute()函数。然后,您可以逐个实体地处理每个块中的数据。

与IJobForEach相比,使用IJobChunk进行迭代需要更多的代码设置,但是也更明确,并且代表对数据的最直接访问,因为它实际上是存储的。

使用按块迭代的另一个好处是您可以检查每个块中是否存在可选组件(使用Archetype。)并相应地处理块中的所有实体。

 

实施IJobChunk作业涉及的步骤包括:

  1. 通过创建EntityQuery标识要处理的实体。
  2. 定义Job结构,包括ArchetypeChunkComponentType对象的字段,以标识Job直接访问的组件的类型,并指定Job是读取还是写入这些组件。
  3. 在系统OnUpdate()函数中实例化Job结构并调度Job。
  4. 在Execute()函数中,获取Job读取或写入的组件的NativeArray实例,最后遍历当前块以执行所需的工作。

该ECS样本库包含了演示如何使用IJobChunk一个简单的例子HelloCube。

使用EntityQuery查询数据

EntityQuery定义原型必须包含的一组组件类型,系统才能处理其关联的块和实体。原型也可以具有其他组件,但是它必须至少具有EntityQuery定义的组件。您也可以排除包含特定类型组件的原型。

对于简单查询,可以使用JobComponentSystem.GetEntityQuery()函数,传入组件类型:

public class RotationSpeedSystem : JobComponentSystem
{
   private EntityQuery m_Group;
   protected override void OnCreate()
   {
       m_Group = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly());
   }
   //…
}

对于更复杂的情况,可以使用EntityQueryDesc。EntityQueryDesc提供了一种灵活的查询机制来指定组件类型:

  • All =此数组中的所有组件类型必须存在于原型中
  • Any =原型中必须至少存在此数组中的一种组件类型
  • None =原型中不能存在此数组中的任何组件类型

例如,以下查询包括包含RotationQuaternion和RotationSpeed组件的原型,但不包括任何包含Frozen组件的原型:

protected override void OnCreate()
{
   var query = new EntityQueryDesc
   {
       None = new ComponentType[]{ typeof(Frozen) },
       All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly() }
}
   };
   m_Group = GetEntityQuery(query);
}

该查询使用ComponentType.ReadOnly 而不是更简单的typeof表达式来表示系统未写入RotationSpeed。

您还可以通过传递EntityQueryDesc对象的数组而不是单个实例来组合多个查询。每个查询都使用逻辑或运算进行组合。以下示例选择包含RotationQuaternion组件或RotationSpeed组件(或两者)的原型:

protected override void OnCreate()
{
   var query0 = new EntityQueryDesc
   {
       All = new ComponentType[] {typeof(RotationQuaternion)}
   };

   var query1 = new EntityQueryDesc
   {
       All = new ComponentType[] {typeof(RotationSpeed)}
   };

   m_Group = GetEntityQuery(new EntityQueryDesc[] {query0, query1});
}

##编写Execute方法

IJobChunk Execute方法的签名为:

 public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)

chunk参数是内存块的句柄,该内存块包含在Job的此迭代中要处理的实体和组件。由于块只能包含一个原型,因此块中的所有实体都具有相同的组件集。

使用chunk参数获取组件的NativeArray实例:

var chunkRotations = chunk.GetNativeArray(RotationType);
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);

对齐这些数组,以使实体在所有数组中具有相同的索引。然后,您可以使用常规的for循环遍历组件数组。使用chunk.Count得到存储在当前块的实体的数量:

for (var i = 0; i < chunk.Count; i++)
{
   var rotation = chunkRotations[i];
   var rotationSpeed = chunkRotationSpeeds[i];

   // Rotate something about its up vector at the speed given by RotationSpeed.
   chunkRotations[i] = new RotationQuaternion
   {
       Value = math.mul(math.normalize(rotation.Value),
           quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
   };
}

如果您Any在EntityQueryDesc中使用过滤器,或者具有完全不包含在查询中的完全可选组件,则可以在使用之前使用该ArchetypeChunk.Has函数测试当前块是否包含这些组件之一:

if (chunk.Has(OptionalCompType))
{//...}

注意:如果使用并发实体命令缓冲区,则将chunkIndex参数作为jobIndex参数传递给命令缓冲区函数。

 

ComponentType

public ComponentType.AccessMode AccessModeType
public int TypeIndex
public bool HasEntityReferences { get; }
public bool IgnoreDuplicateAdd { get; }
public bool IsBuffer { get; }
public bool IsChunkComponent { get; }
public bool IsSharedComponent { get; }
public bool IsSystemStateComponent { get; }
public bool IsSystemStateSharedComponent { get; }
public bool IsZeroSized { get; }

使用ComponentSystem

您可以使用ComponentSystem来处理数据。ComponentSystem方法在主线程上运行,因此无法利用多个CPU内核。在以下情况下使用ComponentSystems:

  • 调试或探索性开发-有时在主线程上运行代码时,更容易观察发生了什么。例如,您可以记录调试文本并绘制​​调试图形。
  • 当系统需要访问只能在主线程上运行的其他API或与之交互时,这可以帮助您逐渐将游戏系统转换为ECS,而不必一开始就重写所有内容。
  • 系统执行的工作量少于创建和调度作业的少量开销。

重要提示:进行结构更改会强制完成所有作业。此事件称为同步点,可能会导致性能下降,因为系统在等待同步点时无法利用所有可用的CPU内核。在ComponentSystem中,应该使用更新后命令缓冲区。同步点仍会发生,但是所有结构性更改都是成批发生的,因此影响较小。为了获得最大效率,请使用JobComponentSystem和实体命令缓冲区。当创建大量实体时,您还可以使用单独的世界创建实体,然后将这些实体转移到主游戏世界。

public class RotationSpeedSystem : ComponentSystem
{
   protected override void OnUpdate()
   {
       Entities.ForEach( (ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
       {
           var deltaTime = Time.deltaTime;
           rotation.Value = math.mul(math.normalize(rotation.Value),
               quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
       });
   }


Entities.ForEach( (Entity entity, ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
{
   var __deltaTime __= Time.deltaTime;
   rotation.Value = math.mul(math.normalize(rotation.Value),
       quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * __deltaTime__));

   if(math.abs(rotationSpeed.RadiansPerSecond) <= float.Epsilon) //Speed effectively zero
       PostUpdateCommands.RemoveComponent(entity, typeof(RotationSpeed));               
});
OnUpdate()函数完成后,系统将在更新后缓冲区中执行命令。


Entities.WithAll().ForEach( (Entity e) =>
{
    // do stuff
});

Entities.WithAll().ForEach( (Entity e, ref Rotation r) =>
{
    // do stuff
});

Entities.WithAll().WithAny().ForEach( (Entity e) =>
{
    // do stuff
});

Entities.WithNone().ForEach( (Entity e) =>
{
    // do stuff
});

Entities.WithNone().With(EntityQueryOptions.IncludeDisabled).ForEach( (Entity e) =>
{
    // do stuff
});

 

手动迭代

您还可以在NativeArray中显式请求所有块,并使用Job等处理它们IJobParallelFor。如果您需要以某种方式管理块,而这种方式不适用于简单地迭代EntityQuery中所有块的简化模型,则建议使用此方法。如:

public class RotationSpeedSystem : JobComponentSystem
{
   [BurstCompile]
   struct RotationSpeedJob : IJobParallelFor
   {
       [DeallocateOnJobCompletion] public NativeArray Chunks;
       public ArchetypeChunkComponentType RotationType;
       [ReadOnly] public ArchetypeChunkComponentType RotationSpeedType;
       public float DeltaTime;

       public void Execute(int chunkIndex)
       {
           var chunk = Chunks[chunkIndex];
           var chunkRotation = chunk.GetNativeArray(RotationType);
           var chunkSpeed = chunk.GetNativeArray(RotationSpeedType);
           var __instanceCount __= chunk.Count;

           for (int i = 0; i < instanceCount; i++)
           {
               var rotation = chunkRotation[i];
               var speed = chunkSpeed[i];
               rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), speed.RadiansPerSecond * DeltaTime));
               chunkRotation[i] = rotation;
           }
       }
   }

   EntityQuery m_group;   

   protected override void OnCreate()
   {
       var query = new EntityQueryDesc
       {
           All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly() }
       };

       m_group = GetEntityQuery(query);
   }

   protected override JobHandle OnUpdate(JobHandle inputDeps)
   {
       var rotationType = GetArchetypeChunkComponentType();
       var rotationSpeedType = GetArchetypeChunkComponentType(true);
       var chunks = m_group.CreateArchetypeChunkArray(Allocator.__TempJob__);

       var rotationsSpeedJob = new RotationSpeedJob
       {
           Chunks = chunks,
           RotationType = rotationType,
           RotationSpeedType = rotationSpeedType,
           DeltaTime = Time.deltaTime
       };
       return rotationsSpeedJob.Schedule(chunks.Length,32,inputDeps);
   }
}

//以下代码段循环访问活动世界中的所有实体:
var entityManager = World.Active.EntityManager;
var allEntities = entityManager.GetAllEntities();
foreach (var entity in allEntities)
{
   //...
}
allEntities.Dispose();

//尽管此代码段遍历了活跃世界中的所有块:
var entityManager = World.Active.EntityManager;
var allChunks = entityManager.GetAllChunks();
foreach (var chunk in allChunks)
{
   //...
}
allChunks.Dispose();

使用EntityQuery查询数据

读取或写入数据的第一步是找到该数据。ECS框架中的数据存储在组件中,这些组件根据它们所属实体的原型在内存中分组在一起。要定义仅包含给定算法或流程所需的特定数据的ECS数据视图,可以构造EntityQuery。

创建EntityQuery之后,您可以

  • 运行作业以处理为视图选择的实体和组件
  • 获取包含所有选定实体的NativeArray
  • 获取所选组件的NativeArrays(按组件类型)
EntityQuery m_Group = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly());

该查询使用ComponentType.ReadOnly 而不是更简单的typeof表达式来表示系统未写入RotationSpeed。尽可能始终指定只读,因为对数据的读取访问限制较少,这可以帮助作业计划程序更有效地执行作业。

EntityQueryDesc

对于更复杂的查询,可以使用EntityQueryDesc来创建EntityQuery。EntityQueryDesc提供了一种灵活的查询机制,可以根据以下几组组件指定要选择的原型:

  • All =此数组中的所有组件类型必须存在于原型中
  • Any =原型中必须至少存在此数组中的一种组件类型
  • None =原型中不能存在此数组中的任何组件类型

例如,以下查询包括包含RotationQuaternion和RotationSpeed组件的原型,但不包括任何包含Frozen组件的原型:

var query = new EntityQueryDesc
{
   None = new ComponentType[]{ typeof(Frozen) },
   All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly() }
}
EntityQuery m_Group = GetEntityQuery(query);

查询选项

创建EntityQueryDesc时,可以设置其Options变量。这些选项允许进行专门的查询(通常不需要设置它们):

  • 默认值-未设置任何选项。查询行为正常。
  • IncludePrefab —包含包含特殊Prefab标签组件的原型。
  • IncludeDisabled —包含包含特殊的Disabled标签组件的原型。
  • FilterWriteGroup —考虑查询中任何组件的WriteGroup。

设置FilterWriteGroup选项时,视图中将仅包含具有明确包含在查询中的写入组中那些组件的实体。具有来自同一WriteGroup的任何其他组件的实体将被排除。

例如,假设C2和C3是基于C1的同一写入组中的组件,并且您使用需要C1和C3的FilterWriteGroup选项创建了查询:

public struct C1: IComponentData{}

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

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

// ... In a system:
var query = new EntityQueryDesc{
    All = new ComponentType{typeof(C1), ComponentType.ReadOnly()},
    Options = EntityQueryDescOptions.FilterWriteGroup
};
var m_group = GetEntityQuery(query);

合并查询

您可以通过传递EntityQueryDesc对象的数组而不是单个实例来组合多个查询。每个查询都使用逻辑或运算进行组合。以下示例选择包含RotationQuaternion组件或RotationSpeed组件(或两者)的原型:

var query0 = new EntityQueryDesc
{
   All = new ComponentType[] {typeof(RotationQuaternion)}
};

var query1 = new EntityQueryDesc
{
   All = new ComponentType[] {typeof(RotationSpeed)}
};

EntityQuery m_Group = GetEntityQuery(new EntityQueryDesc[] {query0, query1});

 

ECS架构优点:

  1. 数据,方法分离,结构清晰,维护简单
  2. 数据连续存储,按块存储component数据,缓存命中高,读取快
  3. 方便预测与回滚机制的实现
  4. 突发编译器是实体组件系统更高效地组织数据所产生的后台性能增益。

参考:

https://blog.csdn.net/sigh667/article/details/73476938

https://docs.unity3d.com/Packages/[email protected]/manual/ecs_entities.html

https://docs.unity3d.com/Packages/[email protected]/api/Unity.Entities.ComponentType.html

你可能感兴趣的:(架构)