• ECS:Components


    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 Components

    ISharedComponentData:value is shared by all entities in the same chunk.
    SharedComponent的值会影响Entity存储的Chunk,但不影响其ArcheType。
    优先使用SharedComponent,但是不能过度使用,因为每一个不同的值都会导致重新分配一个chunk。
    尽量不要给ShardComponent添加不必要的数据,因为可能会导致更多的状态膨胀。
    一些重要的点:
        (1)同一个ArcheType里面,拥有相同值的ShardedComponentData的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)使用EntityManager.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 Components

    块数据组件,这个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);
        });
     
     
     
  • 相关阅读:
    React 生命周期及setState原理分析
    React Vue Angular 对比
    盒模型(一)
    CSS尺寸 rem与em原理与区别(二)
    HTTP 状态码
    React渲染机制
    HTTP三次握手四次挥手
    Java常见算法
    SharedPreferences存储数据
    解决ListView滑动上下出现阴影
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/12315547.html
Copyright © 2020-2023  润新知