ECS:Components

谁说我不能喝 提交于 2020-02-16 02:35:11

Components

ECS的Component基于以下这些interface来实现:
 
类型
说明
IComponentData
常用组件,或者chunk components
IBufferElementData
用于将dynamic buffer和entity关联
DynamicBuffer<T>
ISharedComponentData
共享组件,用于按archetype中的值来对entity分类或分组
ISystemStateComponentData
用于将system-specific state与entity关联
用于检测单个entity的创建或销毁
ISharedSystemStateComponentData
shared和system state的组合
Blob assets
不算是component,但可以用来存储data。Blob assets可以被一个或多个component通过BlobAssetReference使用,并且是immutable的。
Blob assets允许你在assets之间共享data,并在c# job中访问这个数据。
存储在chunk外的component:
    (1)shared components
    (2)chunk components
    (3)dynamic buffers 超出capacity的部分存储在chunk外部
    这些组件的的单个instance应用于所有对应块中的entities。
    另外,可以将dynamic buffers的一些数据存储在chunk外面。
    虽然这些类型的component存储在chunk之外,在query entities时,你可以把它们看作和普通的component一样处理。
    大小限制:一个entity所有的components必须放在同一个chunk中,因此不能超过16K。
    有一些component,比如DynamicBuffer<T>和BlobArray<T>,因为存储在chunk外,所以没有此限制。

通用components

ComponentData只能包含数据或对数据进行访问的util函数,不能包含任何其他行为函数。
所有的game logic和behaviour都应该在systems里面实现。
 
IComponentData:general-purpose component type
(1)使用struct实现
(2)只能包含如下如数据类型:
c# blttable types
bool
char
a fixed-sized character buffer
BlobAssetReference<T>(a reference to a Blob data structure)
fixed arrays(in an unsafe context)
包含这些unmanaged、blitable fields的struct
注意:还可以使用包含一个单独的IBufferElementData component的DynamicBuffer<T>作为array-like数据结构。
(3)不能包含对managed object的引用
(4)值拷贝,数据修改方式:
    var transform = group.transform[index]; // Read

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

    group.transform[index] = transform; // Write
(5)功能原子化:一个IComponentData实现,包含的数据都应该是同时访问的,也就是说功能原子化。通常来说,使用许多小型的component比使用少量的大型的component效率要高。
(6)大多数component基本都会基于IComponentData来实现。
    
Managed IComponentData:托管组件
用途:用于将现有代码逐步转移到ECS架构
用法:
    (1)使用class实现IComponentData,用起来和value type IComponentData一样
    (2)必须实现IEquatable<T>
    (3)必须override Object.GetHashCode()
    (4)必须提供默认构造函数
限制:
    (1)不能使用Burst编译器加速
    (2)不能使用Job
    (3)不能使用chunk memory(内存非连续)
    (4)需要gc
禁用:UNITY_DISABLE_MANAGED_COMPONENTS

Shared Component

ISharedComponentData:value is shared by all entities in the same chunk.
SharedComponent的值会影响Entity存储的Chunk,但不影响其ArcheType。
优先使用SharedComponent,但是不能过度使用,因为没一个不同的值都会导致重新分配一个chunk。
尽量不要给ShardComponent添加不必要的数据,因为可能会导致更多的状态膨胀。
一些重要的点:
    (1)同一个ArcheType里面,拥有相同值的ShardedComponentDat的Entitiesa放在同样的Chunks里面;
    (2)SharedComponentData数据不存在每个Entity里面,而是存在Chunk上,每个Chunk记录它所关联的SharedComponentData的index,所以SharedComponentData对于每个Entity来说是zero memory overhead;
    (3)可以使用EntityQuery遍历所有有用相同type的entities;
    (4)可以使用EntityQuery.SetFilter()来遍历具有特定SharedComponentData的Entities,由于其内存布局是基于Chunk的,所以这个遍历是很快的;
    (5)E使用ntityManager.GetAllUniqueSharedComponents()获取所有状态唯一的SharedComponentData;
    (6)SharedComponentData自动计算引用计数;
    (7)SharedComponentData应该尽量不要修改其值,因为修改值因为这通过memcpy在不同的chunk间拷贝entity的componentdata。

System State Components

ISystemStateSharedComponentData,系统状态组件,可以用来跟踪system资源的内部状态,并有机会创建、销毁这些资源,而不需要依赖单独的回调。
重点:当一个entity销毁时,SystemStateComponentData不会被删除。
    DestroyEntity的行为是:
    (1)找到所有引用entity ID的components;
    (2)Delete这些components;
    (3)回收entity ID用来复用;
    但是,如果有SystemStateComponetData,它将不会被删除。
    这就给了system用来清理entity ID相关联的资源或状态的机会。同时,也只有当这些SystemStateComponentData都被删除以后,entity ID才能够再次被复用。
 
设计意图:
    (1)systems需要维护一个基于ComponentData的状态,比如资源分配;
    (2)systems需要能够管理这个状态,尤其是当其它system修改了相关数据的某些值或状态的时候,比如,当compoents.values变更,或有相关联的components添加或删除的时候;
    (3)“No callbakcs”没有回调,是ECS很重要的一个设计元素。
 
用法:
    通常的用法是和用户自定义组件一对一,用来提供用户组建的状态跟踪。
    比如,可以这样同时提供两个组件:
    FooComponent(ComponentData,用户分配)
    FooStateComponent (SystemComponentData,system分配)
检测组件添加:相当于FooComponent.OnAdded()
    当用户add FooComponent,这个时候FooStateCompoent还没有添加。所以system可以Query有FooComponent但是没有FooStateComponent的entities,那么这些查询到的entities,就是新添加FooComponent的entities,在这个时间点上,就可以做一些相当于是OnAdded回调会做的事情,然后FooSystem为entity添加FooStateComponent,这样OnAdded就不会重复执行了。
检测组件移除:相当于FooComponent.OnRemove()
    当用户remove FooComponent,这时候FooStateComponent不会被remove。所以system可以Query有FooStateComponent但没有FooComponent的entities,然后做一些OnRemove()相关的事情,同时FooSystem为entity remove FooStateComponent。
检测Entity销毁:
    和remove component一样的方式。
    注意:DestroyEntity以后,SystemStateComponentData并没有被移除,这时候entity id还不能被复用。
实现接口ISystemStateComponentData: 
    struct FooStateComponent : ISystemStateComponentData
    {
    }
    struct FooStateSharedComponent : ISystemStateSharedComponentData
    {
      public int Value;
    }
    作为一个常用规范,在创建SystemStateComponentData的system外部都应该以ReadOnly方式来访问。

Dynamic Buffers

IBufferElementData && DynamicBuffer<T>提供类似动态数组的数据能力。如果不需要数据是动态长度,可以使用blob asset来代替dynamic buffer。
使用方式:
(1)定义一个struct,实现接口IBufferElementData,里面包含了需要存储在buffer中的元素;
(2)将该IBufferElementData像普通Component一样添加到Eneity(不是添加DynamicBuffer<T>对象);
(3)访问数据的方式和普通Component不一样,需要使用特定的函数返回该buffer的DynamicBuffer实例,然后使用该实例的接口操作数据(类似于array数据的方式);
(4)设置Capacity,小于该值的数据和普通Component一样存储在chunk里面,超过该值的值,会存储在heap上,ECS会自动维护这些内存数据;
    [InternalBufferCapacity(8)]
    public struct FloatBufferElement : IBufferElementData
    {
        // Actual value each buffer element will store.
        public float Value;
        // The following implicit conversions are optional, but can be convenient.
        public static implicit operator float(FloatBufferElement e)
        {
            return e.Value;
        }
        public static implicit operator FloatBufferElement(float e)
        {
            return new FloatBufferElement {Value = e};
        }
    }
    public class DynamicBufferExample : ComponentSystem
    {
        protected override void OnUpdate()
        {
            float sum = 0;
            Entities.ForEach((DynamicBuffer<FloatBufferElement> buffer) =>
            {
                foreach (var element in buffer.Reinterpret<float>())
                {
                    sum += element;
                }
            });
            Debug.Log("Sum of all buffers: " + sum);
        }
    }
给Entity添加Buffer的方式:
(1)通过Entity添加:
    EntityManager.AddBuffer<MyBufferElement>(entity);
(2)通过ArcheType添加:
    Entity e = EntityManager.CreateEntity(typeof(MyBufferElement));
(3)通过EntityCommandBuffer添加
    struct DataSpawnJob : IJobForEachWithEntity<DataToSpawn>
    {
        public EntityCommandBuffer.Concurrent CommandBuffer;
        
        public void Execute(Entity spawnEntity, int index, [ReadOnly] ref DataToSpawn data)
        {
            for (int e = 0; e < data.EntityCount; e++)
            {
                Entity newEntity = CommandBuffer.CreateEntity(index);
                DynamicBuffer<MyBufferElement> buffer =
                    CommandBuffer.AddBuffer<MyBufferElement>(index, newEntity);
                DynamicBuffer<int> intBuffer = buffer.Reinterpret<int>();
                for (int j = 0; j < data.ElementCount; j++)
                {
                    intBuffer.Add(j);
                }
            }
            CommandBuffer.DestroyEntity(index, spawnEntity);
        }
    }
EntityCommanBuffer.AddBuffer<T>(Entity):添加IBufferElementData;
EntityCommandBuffer.SetBuffer<T>(Entity):替换现有IBufferElementData,entity必须已有一个buffer。
只有在command buffer执行以后,这个buffer才能访问到。
 
访问Buffers:
可以使用以下方式来访问buffer:EntityManager,systems,jobs。
(1)EntityManager
    DynamicBuffer<MyBufferElement> dynamicBuffer = EntityManager.GetBuffer<MyBufferElement>(entity);
(2)Component System Entities.ForEach
    public class DynamicBufferSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            var sum = 0;
            Entities.ForEach((DynamicBuffer<MyBufferElement> buffer) =>
            {
                foreach (var integer in buffer.Reinterpret<int>())
                {
                    sum += integer;
                }
            });
            Debug.Log("Sum of all buffers: " + sum);
        }
    }
    访问其他entity的buffer data:
    BufferFromEntity<MyBufferElement> lookup = GetBufferFromEntity<MyBufferElement>();
    var buffer = lookup[entity];
    buffer.Add(17);
    buffer.RemoveAt(0);
(3)Job
    IJobForEach或IJobForEachWithEntity:
    声明一个以BufferElement为模板类型的结构体:
    public struct BuffersByEntity : IJobForEachWithEntity_EB<MyBufferElement>
    但注意job.execute第三个参数为DynamicBuffer:
    public void Execute(Entity entity, int index, DynamicBuffer<MyBufferElement> buffer)
    IChunkJob && BufferAccessor<T>:
    public struct BuffersInChunks : IJobChunk
    {
        //The data type and safety object
        public ArchetypeChunkBufferType<MyBufferElement> BufferType;
        //An array to hold the output, intermediate sums
        public NativeArray<int> sums;
        public void Execute(ArchetypeChunk chunk,
            int chunkIndex,
            int firstEntityIndex)
        {
            //A buffer accessor is a list of all the buffers in the chunk
            BufferAccessor<MyBufferElement> buffers
                = chunk.GetBufferAccessor(BufferType);
            for (int c = 0; c < chunk.Count; c++)
            {
                //An individual dynamic buffer for a specific entity
                DynamicBuffer<MyBufferElement> buffer = buffers[c];
                foreach (MyBufferElement element in buffer)
                {
                    sums[chunkIndex] += element.Value;
                }
            }
        }
    }
Buffer操作:
    DynamicBuffer<int> intBuffer = EntityManager.GetBuffer<MyBufferElement>(entity).Reinterpret<int>();
    int类型的buffer,也可以使用float类型来访问,只要内存大小一致即可。

Chunk Component

块数据组件,这个component属于一个特定的chunk,而不是chunk中的某个entity(也可以理解为属于chunk中所有的entities,但是只有一份实例)。
定义:没有专门的Interface,直接使用IComponentData,也就是通用Component。但是添加删除和操作这个Component的接口使用另外一套基于Chunk的。
ChunkComponent也会影响这个chunk中entities的archetype,所以添加和删除entity.chunkcomponent,也会导致entity移动到不同的chunk中。
 
创建:
注意:不能在job中添加chunkcomponent,也不能使用EntityCommndBuffer来添加。
    // entity直接add
    EntityManager.AddChunkComponentData<ChunkComponentA>(entity);
    // 使用EntityQuery
    EntityQueryDesc ChunksWithoutComponentADesc = new EntityQueryDesc()
    {
        None = new ComponentType[] {ComponentType.ChunkComponent<ChunkComponentA>()}
    };
    ChunksWithoutChunkComponentA = GetEntityQuery(ChunksWithoutComponentADesc);
    EntityManager.AddChunkComponentData<ChunkComponentA>(ChunksWithoutChunkComponentA,ew ChunkComponentA() {Value = 4});
 
 
    // 使用EntityArchetype
    ArchetypeWithChunkComponent = EntityManager.CreateArchetype(
        ComponentType.ChunkComponent(typeof(ChunkComponentA)),
        ComponentType.ReadWrite<GeneralPurposeComponentA>());
    var entity = EntityManager.CreateEntity(ArchetypeWithChunkComponent);
    // 使用components列表
    ComponentType[] compTypes = {ComponentType.ChunkComponent<ChunkComponentA>(),
                                 ComponentType.ReadOnly<GeneralPurposeComponentA>()};
    var entity = EntityManager.CreateEntity(compTypes);
读取:
    // With the ArchetypeChunk instance
    var chunks = ChunksWithChunkComponentA.CreateArchetypeChunkArray(Allocator.TempJob);
    foreach (var chunk in chunks)
    {
        var compValue = EntityManager.GetChunkComponentData<ChunkComponentA>(chunk);
        //..
    }
    chunks.Dispose();
 
 
    // 直接使用entity
    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);
        //...
    });
说明:一次修改一个chunk的chunk component效率会单独修改一个entity的chunk component效率要高,因为不需要移动entity。
 
更新:
    // With the ArchetypeChunk instance
    EntityManager.SetChunkComponentData<ChunkComponentA>(chunk, new ChunkComponentA(){Value = 7});

    // 直接使用entity
    var entityChunk = EntityManager.GetChunk(entity);
    EntityManager.SetChunkComponentData<ChunkComponentA>(entityChunk, new ChunkComponentA(){Value = 8});
    检查更新条件:何时chunk component的数据需要更新?
    ChunkComponent的Version变化时,ECS会自动维护version的变更。
 
在JobComponentSystem中进行读写操作:
在IJobChunk中,使用传入的chunk参数,然后调用其GetChunkComponentData和SetChunkComponentData来进行数据读写:
  [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});
      }
  }
删除:
  EntityManager.RemoveChunkComponent<T>(Entity)
Query chunk component:
    ComponentType.ChunkComponent<T>
    ComponentType.ChunkComponentReadOnly<T>
    // In an EntityQueryDesc
    EntityQueryDesc ChunksWithChunkComponentADesc = new EntityQueryDesc()
    {
        All = new ComponentType[]{ComponentType.ChunkComponent<ChunkComponentA>()}
    };
    // In an EntityQueryBuilder lambda function
    Entities.WithAll(ComponentType.ChunkComponentReadOnly<ChunkCompA>())
            .ForEach((Entity ent) =>
    {
        var chunkComponentA = EntityManager.GetChunkComponentData<ChunkCompA>(ent);
    });
 
 
 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!