ECS 一 简介

Entity Component System

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,并使用它来创建实体.

1.1 Creating Entities

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

1.1.1 使用 EntityManager创建实体

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

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

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

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

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

1.2 添加和删除组件

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

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

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

1.3 遍历实体

 遍历具有一组具有特定组件的所有实体是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是一个具有下列标记接口的结构 :

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

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

3.1 General purpose components:一般类型的组件

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

Unity ECS 提供了一个 IComponentData 接口

3.1.1 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.

3.2 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)

3.2.1 关于SharedComponentData重要的点:

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

3.3 System State Components:系统状态组件

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

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

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

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

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

但是,如果有 SystemStateComponentData,它不会被删除,只会删除所有非state组件.只有在删除了所有SystemStateComponentData之后,才会重用实体ID。

3.3.1 Motivation:动力

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

3.3.2 Concept:概念

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

比如:

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

3.3.3 Detecting Component Add:检测组件添加

每一帧都会检测实体身上的组件,当有 FooComponent组件没有 FooStateComponent 时,表示该实体被创建,可以执行一些操作,然后添加上  FooStateComponent和一些需要的内部状态

3.3.4 Detecting Component Remove:检测组件的删除

当每一帧检测到的实体,没有 FooComponent组件但有FooStateComponent 组件,表示该实体被销毁,执行销毁的逻辑,然后 FooSystem 会移除 FooStateComponent 并修复任何内部需要的状态.

Entity e = EntityManger.CreateEntity(
    typeof(Translation), typeof(Rotation), typeof(FooSystemState), typeof(BarSystemState));

// Because the entity has system state components, it is not actually destroyed.
// Instead, the Translation and Rotation components are removed. 
EntityManager.DestroyEntity(e);

// We can use the entity like normal, and even add more components.
EntityManager.AddComponent<Translation>(e);

// However, removing all of the components destroys the entity.
EntityManager.DestroyEntity(e, new ComponentTypes(typeof(FooSystemState), typeof(BarSystemState), typeof(Translation)));

// Now the entity has actually been destroyed, so this returns false.
bool b = EntityManager.Exists(e);

3.3.5 Detecting Destroy Entity:检测销毁实体

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

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

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

3.3.6 SystemStateComponent

SystemStateComponentData 类似 ComponentData ,经常使用

struct FooStateComponent : ISystemStateComponentData
{
}

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

3.3.7 SystemStateSharedComponent

 SystemStateSharedComponentData 类似于 SharedComponentData ,经常使用.

struct FooStateSharedComponent : ISystemStateSharedComponentData
{
  public int Value;
}

3.3.8 System 使用state components的例子

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

  • m_newEntities 选择具有componentData但是没有system state component系统组件的实体 . 此查询查找system以前未见过的新实体. system运行一个job去查询那些添加的系统组件的实体
  • m_activeEntities选择同时具有componentData组件和system state components组件的实体.在实际应用程序中,其他系统可能是处理或销毁实体的系统。
  • m_destroyedEntities 选择有 system state但是没有general-purpose component的实体. 因为 system state component 不会自己添加到实体上entity ,通过这条查询被选择的实体应该是被删除过的, 不管是被这个系统还是被另一个系统。system运行一个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<GeneralPurposeComponentA>()},
            None = new ComponentType[] {ComponentType.ReadWrite<StateComponentB>()}
        });

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

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

        m_ECBSource = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    }

    struct NewEntityJob : IJobForEachWithEntity<GeneralPurposeComponentA>
    {
        public EntityCommandBuffer.Concurrent ConcurrentECB;

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

    struct ProcessEntityJob : IJobForEachWithEntity<GeneralPurposeComponentA>
    {
        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<StateComponentB>
    {
        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<StateComponentB>(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.)
    }
}

3.4 Dynamic Buffers:动态缓存

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

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

DynamicBuffers 用来取代已经移除的固定阵列(fixed array)

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

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

//InternalBufferCapacity(8)该属性表示该动态buffer的容量是8个MyBufferElement,
//如果没有声明该属性,默认的buffer大小为128字节,可以通过DefaultBufferCapacityNumerator修改
[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;
}

动态缓存组件和其它组件一样,也是存储在chunk中,它的释放和chunk保持一致,chunk释放掉,该buffer也释放掉,但是如果有元素超出了buffer的大小,则会把数据复制到chunk 外的一块内存中,在用不到时,才会释放。

可以通过Length、Capacity 来访问容量,和数组一样

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

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

Using AddBuffer()

entityManager.AddBuffer<MyBufferElement>(entity);

Using an archetype

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

3.4.3  Accessing Buffers

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

Direct, main-thread only access

DynamicBuffer<MyBufferElement> buffer = entityManager.GetBuffer<MyBufferElement>(entity);

Entity based access:基于实体的访问

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

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

3.4.5 Reinterpreting Buffers (experimental):重新释放缓冲区

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

var intBuffer = entityManager.GetBuffer<MyBufferElement>().Reinterpret<int>();

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

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

3.5 Chunk component data:块组件

chunk 组件里的数据是为整个chunk独有的,而不是为单个实体,比如,如果你有一个块,里面代表了3D物体,你可以用chunk组件来标记那些实体存储在一个框内.

和shared components的区别:

  • 它继承与 IComponentData,share 继承与IShareComponentData
  • 它在逻辑上属于块的数据,而不是实体的数据
  • 更改它的值,里面的实体不会转移到其它的块中
  • 它重复的数据也不会删除,以为每一个chunk component 都是一个实例
  • Chunk components 里面是未托管的 struct values, not managed objects.
  • 一个实体移动由于原型改变或者share component 改变,而移动到一个新的块中,这并不会影响chunk component 中的值.

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

如果块中的实体更改它身上的块组件的值,这会更改块中所有的chunk component的值. 如果你改变了实体的原型,导致实体移动到了新的块中,则实体在原来块中的chunk component数据,也会拷贝到新的块中,和原来的块已经没有联系了,是一个新的实例,对应上面不同的第四点

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

Relevant APIs

PurposeFunction
DeclarationIComponentData
ArchetypeChunk methods
ReadGetChunkComponentData(ArchetypeChunkComponentType)
CheckHasChunkComponent(ArchetypeChunkComponentType)
WriteSetChunkComponentData(ArchetypeChunkComponentType, T)
EntityManager methods
CreateAddChunkComponentData(Entity)
CreateAddChunkComponentData(EntityQuery, T)
CreateAddComponents(Entity,ComponentTypes)
Get type infoGetArchetypeChunkComponentType(Boolean)
ReadGetChunkComponentData(ArchetypeChunk)
ReadGetChunkComponentData(Entity)
CheckHasChunkComponent(Entity)
DeleteRemoveChunkComponent(Entity)
DeleteRemoveChunkComponentData(EntityQuery)
WriteEntityManager.SetChunkComponentData(ArchetypeChunk, T)

3.5.1 声明一个 chunk component

Chunk components 继承于 IComponentData.

public struct ChunkComponentA : IComponentData
{
    public float Value;
}

3.5.2 创建一个 chunk component

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

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

和块中的实体一起添加

给定目标块中的实体,可以使用 EntityManager.AddChunkComponentData<T>(),使用此方法,您不能立即为chunk组件设置值。​​​​​​​

EntityManager.AddChunkComponentData<ChunkComponentA>(entity);

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

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

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

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

使用EntityArchetype

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

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

或者

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

3.5.3 读取chunk component

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

通过实体访问

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

if(EntityManager.HasChunkComponent<ChunkComponentA>(entity))
    chunkComponentValue = EntityManager.GetChunkComponentData<ChunkComponentA>(entity);

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

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

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

通过ArchetypeChunk实例访问

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

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

3.5.4 更新 chunk component

你可以根据块组件所属的块来更新.有两种方法:

1.在 IJobChunk 里, 可以通过 ArchetypeChunk.SetChunkComponentData.

2.在主线程中, 可以使用, EntityManager.SetChunkComponentData.

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

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

通过JobComponentSystem,在job中读写

在一个JobComponentSystem的 IJobChunk中,把 chunk 作为参数传递给 IJobChunk中的 Execute 方法来访问chunk component.  IJobChunk Job中的任何块组件, 你必须传递一个 ArchetypeChunkComponentType<T> 对象给job,它是结构体的一个属性,用它来访问组件

下面的system定义了一个查询,选择所有的包含ChunkComponentA组件的实体.然后运行一个 IJobChunk Job 遍历所有选择的块,并访问它们的组件. Job使用 ArchetypeChunk GetChunkComponentData 和 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<ChunkComponentA>()}
      };
      ChunksWithChunkComponentA = GetEntityQuery(ChunksWithComponentADesc);
  }

  [BurstCompile]
  struct ChunkComponentCheckerJob : IJobChunk
  {
      public ArchetypeChunkComponentType<ChunkComponentA> 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<ChunkComponentA>()
    };
    return job.Schedule(ChunksWithChunkComponentA, inputDependencies);
  }
}

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

使用Entity 实例

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

实例所在的块

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

3.5.6 删除chunk component

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

3.5.7在查询中使用 chunk component

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

EntityQueryDesc

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

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

EntityQueryBuilder lambda 方法

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TO_ZRG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值