Unity ECS小知识2 - 动态缓冲组件(Dynamic Buffer Components)

本文详细介绍了Unity中的ECS系统中动态缓冲组件DynamicBuffer的使用方法,包括其声明、指针类型、使用场景和操作,以及在并行处理和EntityCommandBuffer中的应用。动态缓冲组件允许高效地处理动态大小的数据数组,对于需要动态增删元素的场景非常有用。
摘要由CSDN通过智能技术生成

ECS的组件(Component)是常用的一类,他的固定大小造就了ECS系统能够高速的运转,但是我们有时是需要动态可变数组的。今天我们来看看动态缓冲组件(Dynamic Buffer Components)

声明方式

[InternalBufferCapacity(16)]
public struct MyElement : IBufferElementData
{
    public int Value;
}

InternalBufferCapacity,让我们定义一般需要16个元素,如果省略默认128字节元素。(看官方文档解释是128个字节,取决于结构体有几个字节,没理解错的话,如果没有声明InternalBufferCapacity,这里是128/4=32个Capactiy。

这里区别于普通组件,继承的是IBufferElementData。

结构是非托管的,因此受到与结构IBufferElementData相同的约束IComponentData。DynamicBuffer结构仅代表一个单独的动态缓冲区,而不是组件类型。

指针类型

每个缓冲区存储了2个有用的指针类型:Length和Capacity。

Length

Length是逻辑长度,从0开始的,你可以通过Add添加,那么Length会增加1。

Capacity

Capacity是缓冲区的实际重量,单独设置他会调整缓冲区大小,如果你的数据超出了缓冲区大小,那么所有数据会被复制到一个更大数组中,指针会设置到该数组。

使用方法

//创建一个动态缓冲区的实体。
EntityManager.CreateEntity(typeof(MyElement));    

//给实体e添加一个动态数组。
EntityManager.AddComponent<MyElement>(e);    

//移除实体e的动态数组MyElement。
EntityManager.RemoveComponent<MyElement>(e);

//匹配实体的动态数组MyElement的查询。
EntityQuery query = GetEntityQuery(typeof(MyElement));

//获取实体e的动态数组
DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);

//读取和写入长度5的数据,如果超出抛出异常。
int x = myBuff[5].Value;
myBuff[5] = new MyElement { Value = x + 1 };

//在当前Length处增加一个新的数值,并增加Length,如果超过了Capacity大小,则缓冲区大小调整为Capacity的两倍。
myBuff.Add(new MyElement { Value = 100 });

//设置了可用索引为10(0-9)
myBuff.Length = 10; 

//设置数组大小,如果小于Length则抛出安全检查异常。
myBuff.Capacity = 20;

在ForEach中使用

	Entities.ForEach((in DynamicBuffer<MyElement> myBuff) => {
    for (int i = 0; i < myBuff.Length; i++)
    {
        // ... read myBuff[i]
    }
}).Schedule();

如果要修改就改成ref。

	Entities.ForEach((ref DynamicBuffer<MyElement> myBuff) => {
    for (int i = 0; i < myBuff.Length; i++)
    {
        myBuff.Add(new MyElement(){ Value = 1});
    }
}).Schedule();

结构更改使 DynamicBuffer 无效

DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);

//这里创建了新的Entity,这个结构改变使之前获得的myBuff 无效。
EntityManager.CreateEntity();

//进行读写操作这里可能引发异常。
var x = myBuff[0];   // Exception!

// 应当在这里重新获得myBuff
myBuff = EntityManager.GetBuffer<MyElement>(e);
var y = myBuff[0];   // OK

通过 BufferFromEntity 随机查找缓冲区

如果一个实体的所有实体都Entities.ForEach需要相同的缓冲区,则可以将该缓冲区捕获为主线程上的局部变量:

var myBuff = EntityManager.GetBuffer<MyElement>(someEntity);  

Entities.ForEach((in SomeComp someComp) => {    
    // ... use myBuff
}).Schedule();

并行记录

如果使用ScheduleParallel,请注意不能并行写入缓冲区。但是,您可以使用 EntityCommandBuffer.ParallelWriter来并行记录更改。

OnUpdate中在ForEach中访问多个缓冲区

如果Entities.ForEach需要在其代码中查找一个或多个缓冲区,则需要一个BufferFromEntity结构,它提供按实体对缓冲区的随机查找。

// 在SystemBase类中的OnUpdate中
BufferFromEntity<MyElement> lookup = GetBufferFromEntity<MyElement>();

Entities.ForEach((in SomeComp someComp) => {
    // EntityManager不能再作业中使用,我所我们使用, 所以我们可以用这种方式
    DynamicBuffer<MyElement> myBuff = lookup[someComp.OtherEntity];

    // ... use myBuff
}).Schedule();

使用动态缓冲区修改


EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);

//移除e的MyElement
ecb.RemoveComponent<MyElement>(e);

//记录向现有实体添加MyElement动态缓冲区的命令。
//返回的DynamicBuffer的数据存储在EntityCommandBuffer中,
//因此对返回缓冲区的更改也会被记录下来。
DynamicBuffer<MyElement> myBuff = ecb.AddBuffer<MyElement>(e); 

//实体将有一个MyElement缓冲区
//长度20和这些记录的值。
myBuff.Length = 20;
myBuff[0] = new MyElement { Value = 5 };
myBuff[3] = new MyElement { Value = -9 };

// SetBuffer类似于AddBuffer,但是安全检查将在回放时抛出异常,因为实体还没有MyElement缓冲区。Add过才能Set。
DynamicBuffer<MyElement> otherBuf = ecb.SetBuffer<MyElement>(otherEntity);

//记录一个要追加到缓冲区的MyElement值。抛出异常
//实体还没有MyElement缓冲区。
ecb.AppendToBuffer<MyElement>(otherEntity, new MyElement { Value = 12 });

获取块(Chunk)的所有缓冲区

// ... 假设一个带有MyElement动态缓冲区的块

// 从SystemBase获取一个表示动态缓冲区类型MyElement的BufferTypeHandle
BufferTypeHandle<MyElement> myElementHandle = GetBufferTypeHandle<MyElement>();

// 从块中获取 BufferAccessor .
BufferAccessor<MyElement> buffers = chunk.GetBufferAccessor(myElementHandle);

//遍历区块中每个实体的所有MyElement缓冲区。
for (int i = 0; i < chunk.Count; i++)
{
    DynamicBuffer<MyElement> buffer = buffers[i];

    //遍历缓冲区中的所有元素。
    for (int i = 0; i < buffer.Length; i++)
    {
        // ...
    }
}

重新解释缓冲区

ADynamicBuffer可以被“重新解释”,这样你就可以得到另一个DynamicBuffer, where T并且U具有相同的大小。这种重新解释对相同的内存进行了别名,因此更改i一个索引处的值会更改i另一个索引处的值:

DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);

//只要每个MyElement结构体是四个字节就有效。
DynamicBuffer<int> intBuffer = myBuff.Reinterpret<int>();

intBuffer[2] = 6;  // 相同的效果: myBuff[2] = new MyElement { Value = 6 };

// MyElement值与int值6具有相同的四个字节。
MyElement myElement = myBuff[2];
Debug.Log(myElement.Value);    // 6

该Reinterpret方法仅强制原始类型和新类型具有相同的大小。例如,您可以将 a 重新解释uint为 a,float因为这两种类型都是 32 位的。您有责任决定重新解释是否对您的目的有意义。

本文基本和官方文档一致,增加了一些个人的翻译和看法。
看不懂的可以直接看原文。

引用:
官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值