ECS entity component 概述

Entity Component System

ECS entity component 概述_第1张图片

The Entity Component System (ECS)是 Unity Data-Oriented(面向数据) 技术栈的核心. ECS 有三个主要部分:

  • Entities —实体, 它存在于你的游戏或者程序中
  • Components — 和你的entities关联的数据, 它是由本身数据组织的而不是实体. (这种组织上的差异是面向对象设计和面向数据设计之间的关键差异之一。)
  • Systems — 将component data组件数据从当前状态转换为下一个状态的逻辑 ,例如,一个系统可能会根据所有移动实体的速度乘以自上一帧以来的时间间隔来更新它们的位置。

Entities

Entities 是Entity Component System 架构的三元素之一. 它们代表你的游戏或程序中的个别“事物”.一个实体既没有行为也没有数据;相反,它标识哪些数据属于一起. Systems提供behavior. Components 存储 data.

实体本质上是一个ID. 你可以把它当成一个轻量级的游戏物体,甚至没有名字. entity ID'是稳定的. 它们是存储对另一个组件或实体的引用的唯一稳定的方法。

EntityManager管理所有的实体  EntityManager 维护实体列表并组织与实体关联的数据以获得最佳性能。

尽管entities没有类型, 但是它们可以通过挂在在他们身上的component的类型来分组. 当你创建entities并添加component的时候,  EntityManager跟踪现有实体上组件的唯一组合.这样一个独特的组合叫做 Archetype(原型). 当你创建一个实体并添加组件的时候,EntityManager创建一个EntityArchetype . 你可以使用已经存在的 EntityArchetypes 创建符合该原型的新实体.你也可以事先创建一个 EntityArchetype,并使用它来创建实体.

Creating Entities

创建实体的最简单方法是在Unity编辑器中.你可以在场景中设置游戏对象,也可以在运行时将预制组件转换成实体. 对于游戏或程序中的动态部分,可以在job创建一个 spawning systems来创建多个实体. 你也可以使用 EntityManager.CreateEntity 方法一次创建一个实体.

Creating Entities with an EntityManager

使用 EntityManager.CreateEntity 方法创建一个entity. 实体与EntityManager创建在同一个世界中。.

您可以通过以下方式逐个创建实体:

  • 使用ComponentType对象数组创建具有组件的实体.
  • 使用entityprototype创建带有组件的实体.
  • 使用Instantiate复制现有实体,包括其当前数据
  • 创建没有组件的实体,然后向其添加组件。(您可以立即添加组件,也可以根据需要添加其他组件。)

您还可以一次创建多个实体:

  • 使用CreateEntity用具有相同原型的新实体填充NativeArray.
  • 使用Instantiate用现有实体(包括其当前数据)的副本填充NativeArray。
  • 显式创建使用指定数量的实体填充的块,并使用CreateChunk创建给定原型

添加和删除组件

 给实体添加或者删除组件或影响实体的原型, EntityManager 必须将更改后的数据移动到一块新的内存中, 以及在原始的内存块中压缩组件

改变一个实体导致实体结构的改变 — 也就是说,添加或删除组件会改变SharedComponentData的值;销毁实体 entity — 不能在job中执行,因为这会使job正在处理的数据无效.相反,您可以把这些类型的更改额命令添加到 EntityCommandBuffer ,然后在job完成后执行这些命令

EntityManager 提供了从单一实体上或者从一组实体上删除组建的方法. See Components for more information.

遍历实体

 遍历具有一组具有特定组件的所有实体是ECS体系结构的核心. See Accessing entity Data.

World

一个 World 拥有一个 EntityManager 和一套ComponentSystems. 你可以根据你的需要创建很多World 对象.通常您会创建一个模拟World and渲染 World.

默认当我们进入Play Mode 时创建一个World 并且用项目中所有可用的ComponentSystem对象填充它 但是您可以禁用默认World 的创建,通过全局定义用您自己的代码替换它。

#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD 禁用默认runtime运行时world的生成

#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_EDITOR_WORLD 禁用默认editor world的生成

#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP 禁用以上两个默认world的生成

  • Default World creation code (see file: Packages/com.unity.entities/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs)
  • Automatic bootstrap entry point (see file: Packages/com.unity.entities/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs)

 

Components

Components 是Entity Component System 架构的三元素之一. 它们表示你程序或者游戏中的数据. Entities 本质上是索引components的标识符,也就是通过entities索引. Systems 提供 behavior.

具体地,ECS的一个component是一个具有下列标记接口的结构 :

  • IComponentData — 用于 general purpose 和 chunk components.
  • IBufferElementData — 用于把 dynamic buffers 和一个entity关联起来.
  • ISharedComponentData —用于根据原型中的值对实体进行分类或分组. See Shared Component Data.
  • ISystemStateComponentData — 用于将系统特定的状态与实体关联,并用于检测何时创建或销毁单个实体. See System State Components.
  • ISharedSystemStateComponentData — shared and system state data的组合. See System State Components.

 EntityManager 将出现在实体上的独特组件组合组织为原型 Archetypes. 它将具有相同原型的所有实体的组件一起存储在称为块的内存块中 Chunks. 在给定Chunks块中,所有实体都具有相同的组件原型.

ECS entity component 概述_第2张图片

Shared components 和chunk components 存储在chunk之外; 这些组件类型的单个实例适用于适用块中的所有实体,因为所有的实体的组件类型都是一样的.此外你还可以将动态缓冲区 dynamic buffers 存储在chunk外. 即使这些类型的组件没有存储在块中,  在你查询访问尸体的时候,这些这些组件和其它组件是一样的

General purpose components:一般类型的组件

Unity中的ComponentData组件 ,在标准ECS术语中也称为组件,是一个只包含实体的实例数据的结构. ComponentData 不应该包含超出实用程序库之外的函数来访问结构中的数据,. 所有的游戏逻辑和型维都应该在systems中实现. 把它放在旧的Unity系统中,这有点像一个旧的组件类,但它只包含变量

Unity ECS 提供了一个 IComponentData 接口

IComponentData

传统的Unity组件(包括MonoBehaviour)是面向对象的类,它包含数据和方法. IComponentData 是一个 纯粹的ECS-style 的组件, 他只有数据 data. IComponentData 是一个结构体而不是一个类, 表示它是通过值类型来拷贝而不是引用 . 您通常需要使用以下模式来修改数据

var transform = group.transform[index]; // Read

transform.heading = playerInput.move; // Modify
transform.position += deltaTime * playerInput.move * settings.playerMoveSpeed;

group.transform[index] = transform; // Write

IComponentData 结构体可能不包含对托管对象的引用. 因为ComponentData 存在一个简单的没有垃圾收集的无托管的内存块中chunk memory.

See file: /Packages/com.unity.entities/Unity.Entities/IComponentData.cs.

Shared Component Data

Shared components 是一个特殊的数据组件data component ,你可以根据在shared component的特殊值(除了它们的原型)来细分实体t.当你给entities添加一个 shared component 组件时, EntityManager 把所有拥有一样的shared data值得实体放在同一个块中. Shared components 允许systems一起处理实体, 比如,  shared component Rendering.RenderMesh, 是Hybrid.rendering 报的一部分,包括几个字段,如: mesh, material, receiveShadows, etc. 等,当渲染的时候,处理所有具有相同字段值的3D对象是最有效的. 因为这些属性是在共享组件中指定的 , EntityManager将这些匹配到的实体放在一块内存中,所以系统能够快速得找到遍历它们。

Note: 过度使用 shared components 会导致chunk得利用率低下,因为这涉及到一个基于原型archetype的内存块chunk得组合扩展, 每一个共享组件字段的唯一值. 避免向共享组件share component添加不必要的字段,使用  Entity Debugger 查看当前块的利用率

如果给实体 entity添加或删除组件, 或者改变共享组件SharedComponent的值,  EntityManager 将实体移动到另一个内存chunk块,必要时创建一个新块.

IComponentData 通常适用于实体之间有不同的数据, 比如存储一个world position, 管理本身的碰撞点 hit points, particle 粒子的生命周期, etc. 与此相反, ISharedComponentData 适用于当许多实体共享某些内容时. 例如,在Boid演示中,我们实例化了许多实体 通过同一个Prefab and 因此Boid 实体的 RenderMesh 都是一样的

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

    public ShadowCastingMode    castShadows;
    public bool                 receiveShadows;
}

ISharedComponentData 比较好的地方在于基于每一个实体的内存消耗几乎为零

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

  • RenderMeshSystemV2 (see file: Packages/com.unity.entities/Unity.Rendering.Hybrid/RenderMeshSystemV2.cs)

关于SharedComponentData重要的点:

  • 拥有SharedComponentData的实体被分组在同一个内存块中. SharedComponentData的索引按块存储一次,而不是按实体存储一次. 因此,SharedComponentData在每个实体上的内存开销为零。
  • 使用 EntityQuery 可以遍历同种类型的所有实体
  • 除此之外,使用 EntityQuery.SetFilter() 可以遍历具有特定属性的实体的值SharedComponentData value. 由于数据布局(都分布在一起,在一个内存块中),此遍历具有较低的开销
  • 使用 EntityManager.GetAllUniqueSharedComponents 我们可以检索到所有被添加到实体身上的SharedComponentData
  • SharedComponentData 自动会计算被引用的次数
  • SharedComponentData 不应该频繁的改变. 改变一个 SharedComponentData涉及到把那个实体的ComponentData 的内存拷贝到另一个内存块

System State Components:系统状态组件

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

SystemStateComponentData and SystemStateSharedComponentDataComponentData and SharedComponentData 是一样的, 除了一个重要的方面:

  1. SystemStateComponentData当一个实体被销毁时不会被删除.

DestroyEntity 销毁实体其实是同时做了以下工作:

  1. 找到所有引用这个实体ID的所有组件,也就是拿到这个实体上的所有组件
  2. 删除这些组件.
  3. 回收实体ID,以便重复利用.

但是,如果有 SystemStateComponentData,他不会被删除. 它会让system系统清理所有和该实体相关联的资源和状态.只有在删除了所有SystemStateComponentData之后,才会重用实体ID。

Motivation:动力

  • Systems 可能需要保持一个基于 ComponentData内部的状态. 例如,可以分配资源.
  • Systems 需要能够把这些状态当成值来管理 ,状态的改变是由其它systems来决定,比如, 当组件中的值改变了或者添加或删除组建的时候
  • "No callbacks" 是ECS设计规则的重要组成部分

Concept:概念

 SystemStateComponentData 一般是用来反应用户组件,提供内部状态

比如:

  • FooComponent (ComponentData,用户指定的)
  • FooStateComponent (SystemComponentData, 系统指定的)

Detecting Component Add:检测组件添加

当用户添加 FooComponent组件时, FooStateComponent 不存在的时候.  FooSystem更新有 FooComponent 没有FooStateComponent 的查询,可以推断它们已经被添加. 在那个阶段,  FooSystem 会添加 FooStateComponent和一些需要的内部状态

Detecting Component Remove:检测组件的删除

当移除 FooComponent组件,但是FooStateComponent 仍然存在.  FooSystem 会更新有 FooStateComponent 没有FooComponent 来推测它是否已被移除. ,然后 FooSystem 会移除 FooStateComponent 并修复任何内部需要的状态.

Detecting Destroy Entity:检测销毁实体

DestroyEntity 销毁实体其实是做了以下的事情:

  • 找到所有引用该实体ID的组件.
  • 删除这些组件.
  • 回收重复利用entity ID.

 SystemStateComponentDataDestroyEntity 的时候不会移除,直到删除最后一个组件,实体ID才会被回收。这使系统有机会以与删除组件完全相同的方式清理内部状态.

SystemStateComponent

 SystemStateComponentData 类似 ComponentData ,经常使用

struct FooStateComponent : ISystemStateComponentData
{
}

 SystemStateComponentData 和组件控制的方式相同(using private, public, internal)但是,一般来说,SystemStateComponentData 在创建它的系统之外是只读的 ReadOnly

SystemStateSharedComponent

 SystemStateSharedComponentData 类似于 SharedComponentData ,经常使用.

struct FooStateSharedComponent : ISystemStateSharedComponentData
{
  public int Value;
}

system 使用state components的例子:

下面的示例显示了一个简化的系统,该系统演示了如何使用system state components系统状态组件管理实体 . 该示例定义了一个通用的IComponentData实例和一个系统状态 ISystemStateComponentData 实例. 同时基于这些实体也创建了三个 queries :

  • m_newEntities 选择具有componentData但是没有system state component系统组件的实体 . 此查询查找系统以前未见过的新实体. 系统使用这些新的实体运行一个job去查询那些添加的系统组件
  • m_activeEntities选择同时具有componentData组件和system state components组件的实体.在实际应用程序中,其他系统可能是处理或销毁实体的系统。
  • m_destroyedEntities 选择有 system state但是没有general-purpose component的实体. 因为 system state component 不会自己添加到实体上entity ,通过这条查询被选择的实体应该是被删除过的, 不管是被这个系统还是被另一个系统.系统运行一个a job使用删除了的实体查询从实体上移除掉的系统组件, 这样 ECS就可以重复使用 Entity identifier.

注意,这个简化的示例并不维护系统中的任何状态.系统状态组件的一个目的是跟踪何时需要分配或清理持久性资源。

using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using UnityEngine;

public struct GeneralPurposeComponentA : IComponentData
{
    public bool IsAlive;
}

public struct StateComponentB : ISystemStateComponentData
{
    public int State;
}

public class StatefulSystem : JobComponentSystem
{
    private EntityQuery m_newEntities;
    private EntityQuery m_activeEntities;
    private EntityQuery m_destroyedEntities;
    private EntityCommandBufferSystem m_ECBSource;

    protected override void OnCreate()
    {
        // Entities with GeneralPurposeComponentA but not StateComponentB
        m_newEntities = GetEntityQuery(new EntityQueryDesc()
        {
            All = new ComponentType[] {ComponentType.ReadOnly()},
            None = new ComponentType[] {ComponentType.ReadWrite()}
        });

        // Entities with both GeneralPurposeComponentA and StateComponentB
        m_activeEntities = GetEntityQuery(new EntityQueryDesc()
        {
            All = new ComponentType[]
            {
                ComponentType.ReadWrite(),
                ComponentType.ReadOnly()
            }
        });

        // Entities with StateComponentB but not GeneralPurposeComponentA
        m_destroyedEntities = GetEntityQuery(new EntityQueryDesc()
        {
            All = new ComponentType[] {ComponentType.ReadWrite()},
            None = new ComponentType[] {ComponentType.ReadOnly()}
        });

        m_ECBSource = World.GetOrCreateSystem();
    }

    struct NewEntityJob : IJobForEachWithEntity
    {
        public EntityCommandBuffer.Concurrent ConcurrentECB;

        public void Execute(Entity entity, int index, [ReadOnly] ref GeneralPurposeComponentA gpA)
        {
            // Add an ISystemStateComponentData instance
            ConcurrentECB.AddComponent(index, entity, new StateComponentB() {State = 1});
        }
    }

    struct ProcessEntityJob : IJobForEachWithEntity
    {
        public EntityCommandBuffer.Concurrent ConcurrentECB;

        public void Execute(Entity entity, int index, ref GeneralPurposeComponentA gpA)
        {
            // Process entity, possibly setting IsAlive false --
            // In which case, destroy the entity
            if (!gpA.IsAlive)
            {
                ConcurrentECB.DestroyEntity(index, entity);
            }
        }
    }

    struct CleanupEntityJob : IJobForEachWithEntity
    {
        public EntityCommandBuffer.Concurrent ConcurrentECB;

        public void Execute(Entity entity, int index, [ReadOnly] ref StateComponentB state)
        {
            // This system is responsible for removing any ISystemStateComponentData instances it adds
            // Otherwise, the entity is never truly destroyed.
            ConcurrentECB.RemoveComponent(index, entity);
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var newEntityJob = new NewEntityJob()
        {
            ConcurrentECB = m_ECBSource.CreateCommandBuffer().ToConcurrent()
        };
        var newJobHandle = newEntityJob.ScheduleSingle(m_newEntities, inputDependencies);
        m_ECBSource.AddJobHandleForProducer(newJobHandle);

        var processEntityJob = new ProcessEntityJob()
            {ConcurrentECB = m_ECBSource.CreateCommandBuffer().ToConcurrent()};
        var processJobHandle = processEntityJob.Schedule(m_activeEntities, newJobHandle);
        m_ECBSource.AddJobHandleForProducer(processJobHandle);

        var cleanupEntityJob = new CleanupEntityJob()
        {
            ConcurrentECB = m_ECBSource.CreateCommandBuffer().ToConcurrent()
        };
        var cleanupJobHandle = cleanupEntityJob.ScheduleSingle(m_destroyedEntities, processJobHandle);
        m_ECBSource.AddJobHandleForProducer(cleanupJobHandle);

        return cleanupJobHandle;
    }

    protected override void OnDestroy()
    {
        // Implement OnDestroy to cleanup any resources allocated by this system.
        // (This simplified example does not allocate any resources.)
    }
}

 

Dynamic Buffers:动态缓存

 DynamicBuffer 是一个大小可变的动态组件数据类型, 是一个与实体相关的弹性缓冲区. 它作为一种组件类型,可以存储一定数量的元素,但是如果内部容量耗尽,则可以分配堆内存块。

使用这种方法时,内存管理时完全自动的.与 DynamicBuffers 相关联的的内存是通过 EntityManager 来管理的,所以 当一个 DynamicBuffer组件移除时, 与之相关的堆内存也会被自动释放

DynamicBuffers 取代已经移除的固定阵列supersede fixed array support which has been removed.

Declaring Buffer Element Types:声明缓冲区中的元素类型

 声明一个缓冲区 Buffer, 首先要声明你要放进去的元素的类型 :

//下面描述了再64位机器上存储8个整数(32字节)和一个缓冲头(16)共48字节,
[InternalBufferCapacity(8)]
public struct MyBufferElement : IBufferElementData
{
    // 这些隐式转换是可选的,但有助于减少输入.
    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. 它支持有一个以上的类型为float3的DynamicBuffer,或者任何其他常见的值类型. 你可以添加任意数量内部数据元素类型相同的缓冲区, 只要这些元素是在定义结构中是唯一包装的.

  2.  我们可以在 EntityArchetypes 中包含Buffer 的元素, 它的行为就像一个组件.

Adding Buffer Types To Entities:向实体添加缓冲区类型

要向实体添加缓冲区,可以使用向实体添加组件类型的常规方法:

Using AddBuffer()

entityManager.AddBuffer(entity);

Using an archetype

Entity e = entityManager.CreateEntity(typeof(MyBufferElement));

Accessing Buffers

有多种访问 DynamicBuffers 的方法,就像访问其它常规组件的方法一样

Direct, main-thread only access

DynamicBuffer buffer = entityManager.GetBuffer(entity);

Entity based access:基于实体的访问

可以通过JobComponentSystem 访问每一个实体上的Buffers :

    var lookup = GetBufferFromEntity();
    var buffer = lookup[myEntity];
    buffer.Append(17);
    buffer.RemoveAt(0);

Reinterpreting Buffers (experimental):重新释放缓冲区(还在实验)

Buffers 可以被释放位原来相同的大小的内存. 目的就是重复利用. 调用Reinterpret<T>:

var intBuffer = entityManager.GetBuffer().Reinterpret();

 释放Buffer是安全的. 它们使用相同的底层的 BufferHeader, 因此,修改一个缓冲区其它的缓冲区也会改变

注意,这里不涉及类型检查,因此 uint and float 是完全可行的(个人理解,在缓冲区内存放uint和float值).

 

Chunk component data:块组件

使用 chunk components 组件将数据与特性的chunk(内存块)相关联起来

Chunk components 包含特定块中应用于所有实体的数据,比如,如果你有一个块,里面代表了3D物体,你可以用chunk组件来为那些实体存储在一个框内. Chunk components 也是IComponentData,继承于它.

尽管Chunk components可以具有对单个块惟一的值,它们仍然是块中实体原型的一部分. 因此,如果从实体中删除块组件,则该实体将移动到另一个块(也有可能新创建一个块),因为每个块都是不一样的,存储的实体也不一样,你更改了实体组件,所以原来的块就不适合它了. ; 块组件的添加不会影响原始块中的其余实体。

如果块中的实体更改它身上的块组件的值,这会更改块中所有的chunk component的值. 如果你改变了实体的原型,导致实体移动到了新的块中,则实体在原来块中的chunk component数据,也会拷贝到新的块中

使用块组件和通用组件之间的主要区别是使用不同的函数来添加、设置和删除它们. 块组件也有自己的ComponentType函数,用于定义实体原型和查询。

Relevant APIs

Purpose Function
Declaration IComponentData
  ArchetypeChunk methods
Read GetChunkComponentData(ArchetypeChunkComponentType)
Check HasChunkComponent(ArchetypeChunkComponentType)
Write SetChunkComponentData(ArchetypeChunkComponentType, T)
  EntityManager methods
Create AddChunkComponentData(Entity)
Create AddChunkComponentData(EntityQuery, T)
Create AddComponents(Entity,ComponentTypes)
Get type info GetArchetypeChunkComponentType(Boolean)
Read GetChunkComponentData(ArchetypeChunk)
Read GetChunkComponentData(Entity)
Check HasChunkComponent(Entity)
Delete RemoveChunkComponent(Entity)
Delete RemoveChunkComponentData(EntityQuery)
Write EntityManager.SetChunkComponentData(ArchetypeChunk, T)

Declaring a chunk component

Chunk components 使用 IComponentData.

public struct ChunkComponentA : IComponentData
{
    public float Value;
}

Creating a chunk component

您可以使用目标块中的实体或使用选择一组目标块的实体查询直接添加块组件. Chunk components不能在Job内部添加,也不能通过EntityCommandBuffer添加.

你也可以把 chunk components 作为 EntityArchetype 的一部分 或者ComponentType 的列表,去创建实体,使用 ComponentType.ChunkComponent or ComponentType.ChunkComponentReadOnly 这些方法. 否则,该组件将被视为通用组件,而不是块组件。

With an entity in a chunk:和块中的实体一起添加

给定目标块中的实体,可以使用 EntityManager.AddChunkComponentData():

EntityManager.AddChunkComponentData(entity);

使用此方法,您不能立即为chunk组件设置值。

 使用EntityQuery

使用EntityQuery,选择要向其添加块组件的所有块,

EntityManager.AddChunkComponentData() function:

EntityQueryDesc ChunksWithoutComponentADesc = new EntityQueryDesc()
{
    None = new ComponentType[] {ComponentType.ChunkComponent()}
};
ChunksWithoutChunkComponentA = GetEntityQuery(ChunksWithoutComponentADesc);

EntityManager.AddChunkComponentData(ChunksWithoutChunkComponentA,
        new ChunkComponentA() {Value = 4});

使用此方法,可以为所有新块组件设置相同的初始值。

 使用EntityArchetype

在创建具有原型或组件类型列表的实体时,请在原型中包含块组件类型:

ArchetypeWithChunkComponent = EntityManager.CreateArchetype(
    ComponentType.ChunkComponent(typeof(ChunkComponentA)),
    ComponentType.ReadWrite());
var entity = EntityManager.CreateEntity(ArchetypeWithChunkComponent);

or list of component types:

ComponentType[] compTypes = {ComponentType.ChunkComponent(),
                             ComponentType.ReadOnly()};
var entity = EntityManager.CreateEntity(compTypes);

使用这些方法,作为实体构造的一部分创建的新块的块组件将接收默认的struct值. Chunk components in existing chunks are not changed. See Updating a chunk component for how to set the chunk component value given a reference to an entity.

Reading a chunk component:读取块组件

你也可以通过块中的实体来读取chunk component 或者使用 ArchetypeChunk 对象来代表实体.

With an entity in a chunk

可以通过EntityManager.GetChunkComponentData获得实体上的chunk component:

if(EntityManager.HasChunkComponent(entity))
    chunkComponentValue = EntityManager.GetChunkComponentData(entity);

如果你设置了 fluent query 只选择具有块组件的实体:

Entities.WithAll(ComponentType.ChunkComponent()).ForEach(
    (Entity entity) =>
{
    var compValue = EntityManager.GetChunkComponentData(entity);
    //...
});

注意,您不能将块组件传递给foreach查询的每个部分。相反,你必须通过 Entity object 并使用 EntityManager 来访问chunk component.

With the ArchetypeChunk instance:使用ArchetypeChunk实例

给了一个块,你可以通过  EntityManager.GetChunkComponentData 来访问块中的chunk component. 下面表示遍历所有符合条件的块中的类型为ChunkComponentA的chunk component

var chunks = ChunksWithChunkComponentA.CreateArchetypeChunkArray(Allocator.TempJob);
foreach (var chunk in chunks)
{
    var compValue = EntityManager.GetChunkComponentData(chunk);
    //..
}
chunks.Dispose();

Updating a chunk component:更新

 你可以根据块组件所属的块来更新. I在 IJobChunk Job, 可以通过l ArchetypeChunk.SetChunkComponentData. 在主线程中, 可以使用, EntityManager.SetChunkComponentData. 不能在 IJobForEach Job中访问它,因为你没有访问ArchetypeChunk 对象或者是 EntityManager.

With the ArchetypeChunk instance:使用ArchetypeChunk更新

在 Job中更新, see Reading and writing in a JobComponentSystem.

在主线程中更新,使用EntityManager:

EntityManager.SetChunkComponentData(chunk, 
                    new ChunkComponentA(){Value = 7});

使用Entity 实例

 如果你有块中实例的引用,而不是块的引用,你也可以通过 EntityManger获得

实例所在的块

var entityChunk = EntityManager.GetChunk(entity);
EntityManager.SetChunkComponentData(entityChunk, 
                    new ChunkComponentA(){Value = 8});

Reading and writing in a JobComponentSystem:在job中读写

  在 一个JobComponentSystem的 IJobChunk, 你可以通过把 chunk parameter 传递 IJ给obChunk Execute 方法来访问chunk component.  IJobChunk Job中的任何块组件, 你必须传递一个 ArchetypeChunkComponentType 对象给job,它是结构体的一个属性,用它来访问组件

 下面的system定义了一个查询,选择所有的包含ChunkComponentA组件的实体.然后运行一个 IJobChunk Job 遍历所有选择的块,并访问它们的组件. The Job使用 ArchetypeChunk GetChunkComponentData and SetChunkComponentData 来读写块组件中的数据:

using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;

public class ChunkComponentChecker : JobComponentSystem
{
  private EntityQuery ChunksWithChunkComponentA;
  protected override void OnCreate()
  {
      EntityQueryDesc ChunksWithComponentADesc = new EntityQueryDesc()
      {
        All = new ComponentType[]{ComponentType.ChunkComponent()}
      };
      ChunksWithChunkComponentA = GetEntityQuery(ChunksWithComponentADesc);
  }

  [BurstCompile]
  struct ChunkComponentCheckerJob : IJobChunk
  {
      public ArchetypeChunkComponentType ChunkComponentATypeInfo;
      public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
      {
          var compValue = chunk.GetChunkComponentData(ChunkComponentATypeInfo);
          //...
          var squared = compValue.Value * compValue.Value;
          chunk.SetChunkComponentData(ChunkComponentATypeInfo, 
                      new ChunkComponentA(){Value= squared});
      }
  }

  protected override JobHandle OnUpdate(JobHandle inputDependencies)
  {
    var job = new ChunkComponentCheckerJob()
    {
      ChunkComponentATypeInfo = GetArchetypeChunkComponentType()
    };
    return job.Schedule(ChunksWithChunkComponentA, inputDependencies);
  }
}

如果 chunk component是只读的,你需要使用 ComponentType.ChunkComponentReadOnly 当定义 entity query时,来避免创建不必要的  Job scheduling constraints.

Deleting a chunk component

  使用 EntityManager.RemoveChunkComponent 移除 ,你可以移除单个实体上的组件,也可以移除整个块中实体的组件. 如果你移除了单个实体上的组件,则实体会从该块中移动到另一个块中,因为原型改变了

 在查询中使用 chunk component

在实体查询时使用chunk component,你必须使用 ComponentType.ChunkComponent or ComponentType.ChunkComponentReadOnly 方法指定块组件的类型. 否则的话,组件不会被当作块组件,而是普通组件

在 EntityQueryDesc

  下面创建了一个查询,用来查找那些块中所有包含chunk component的组件的实体 ChunkComponentA:

EntityQueryDesc ChunksWithChunkComponentADesc = new EntityQueryDesc()
{
    All = new ComponentType[]{ComponentType.ChunkComponent()}
};

在 EntityQueryBuilder lambda 方法

 下面遍历块中所有含有ChunkComponentA组件类型的实体:

Entities.WithAll(ComponentType.ChunkComponentReadOnly())
        .ForEach((Entity ent) =>
{
    var chunkComponentA = EntityManager.GetChunkComponentData(ent);
});

Note: 你不能给lambda表达式传递一个 chunk component . 要在一个 fluent query中访问chunk component ,使用ComponentSystem.EntityManager 属性.修改块组件可以有效地修改同一块中所有实体的值;它不会将当前实体移动到不同的块。因为你只是修改了组件当中的值,并没有移动删除组件,所以原型并没有发生变化

 

你可能感兴趣的:(ECS)