使用块组件将数据与特定块相关联。
块组件包含适用于特定块中所有实体的数据。例如,如果您有表示按邻近度组织的 3D 对象的实体块,您可以使用块组件为它们存储一个集合边界框。块组件使用接口类型IComponentData。
添加和设置块组件的值
尽管块组件可以具有对单个块唯一的值,但它们仍然是块中实体原型的一部分。因此,如果您从实体中删除块组件,ECS 会将该实体移动到不同的块(可能是新块)。同样,如果您向实体添加块组件,ECS 会将该实体移动到不同的块,因为它的原型发生了变化;添加块组件不会影响原始块中的剩余实体。
如果您使用块中的实体来更改块组件的值,则会更改该块中所有实体共有的块组件的值。如果您更改实体的原型,使其移动到具有相同类型块组件的新块中,则目标块中的现有值不受影响。
笔记
如果实体被移动到新创建的块,则 ECS 会为该块创建一个新的块组件并为其分配默认值。
使用块组件和通用组件的主要区别在于您使用不同的函数来添加、设置和删除它们。
相关API
声明一个块组件
块组件使用接口类型IComponentData。
public struct ChunkComponentA : IComponentData
{
public float Value;
}
创建块组件
要直接添加块组件,请使用目标块中的实体,或使用选择一组目标块的实体查询。您不能在作业中添加块组件,也不能使用EntityCommandBuffer添加它们。
您还可以将区块组件作为ECS 用于创建实体的EntityArchetype或 [ComponentType] 对象列表的一部分。ECS 为每个块创建块组件并存储具有该原型的实体。
将ComponentType.ChunkComponent<T>或 [ComponentType.ChunkComponentReadOnly<T>] 与这些方法一起使用。否则,ECS 会将组件视为通用组件而不是块组件。
块中有一个实体
给定目标块中的实体,您可以使用EntityManager.AddChunkComponentData<T>()函数将块组件添加到块中:
EntityManager.AddChunkComponentData<ChunkComponentA>(entity);
使用此方法时,您无法立即为块组件设置值。
给定一个实体查询,该查询选择了要向其添加块组件的所有块,您可以使用EntityManager.AddChunkComponentData<T>()函数来添加和设置组件:
EntityQueryDesc ChunksWithoutComponentADesc
= new EntityQueryDesc()
{
None = new ComponentType[]{
ComponentType.ChunkComponent<ChunkComponentA>()
}
};
EntityQuery ChunksWithoutChunkComponentA
= GetEntityQuery(ChunksWithoutComponentADesc);
EntityManager.AddChunkComponentData<ChunkComponentA>(
ChunksWithoutChunkComponentA,
new ChunkComponentA() { Value = 4 });
使用此方法时,您可以为所有新块组件设置相同的初始值。
当您使用原型或组件类型列表创建实体时,请在原型中包含块组件类型:
EntityArchetype ArchetypeWithChunkComponent
= EntityManager.CreateArchetype(
ComponentType.ChunkComponent(typeof(ChunkComponentA)),
ComponentType.ReadWrite<GeneralPurposeComponentA>());
Entity newEntity
= EntityManager.CreateEntity(ArchetypeWithChunkComponent);
或组件类型列表:
ComponentType[] compTypes = {
ComponentType.ChunkComponent<ChunkComponentA>(),
ComponentType.ReadOnly<GeneralPurposeComponentA>()
};
Entity entity = EntityManager.CreateEntity(compTypes);
当您使用这些方法时,ECS 作为实体构建的一部分创建的新块的块组件将接收默认结构值。ECS 不会更改现有块中的块组件。请参阅更新块组件以了解如何在给定对实体的引用的情况下设置块组件值。
读取块组件
要读取块组件,您可以使用表示块的ArchetypeChunk对象,或使用目标块中的实体。
使用 ArchetypeChunk 实例
给定一个块,您可以使用EntityManager.GetChunkComponentData<T>函数读取其块组件。以下代码遍历与查询匹配的所有块并访问类型为 的块组件ChunkComponentA
:
NativeArray<ArchetypeChunk> chunks
= ChunksWithChunkComponentA.CreateArchetypeChunkArray(
Allocator.TempJob);
foreach (var chunk in chunks)
{
var compValue =
EntityManager.GetChunkComponentData<ChunkComponentA>(chunk);
//..
}
chunks.Dispose();
块中有一个实体
给定一个实体,您可以使用EntityManager.GetChunkComponentData<T>访问包含该实体的块中的块组件:
if (EntityManager.HasChunkComponent<ChunkComponentA>(entity))
{
ChunkComponentA chunkComponentValue =
EntityManager.GetChunkComponentData<ChunkComponentA>(entity);
}
更新块组件
你可以更新一个块组件给它所属块的引用。在IJobChunk
作业中,您可以调用ArchetypeChunk.SetChunkComponentData。在主线程上,您可以使用 EntityManager 版本:EntityManager.SetChunkComponentData。
笔记
您不能使用 SystemBase Entities.ForEach 访问块组件,因为您无权访问ArchetypeChunk
对象或 EntityManager。
使用 ArchetypeChunk 实例
要更新作业中的块组件,请参阅在系统中读取和写入。
要更新主线程上的块组件,请使用 EntityManager:
EntityManager.SetChunkComponentData<ChunkComponentA>(
chunk, new ChunkComponentA() { Value = 7 });
使用实体实例
如果块中有实体而不是块引用本身,则还可以使用 EntityManger 获取包含实体的块:
笔记
如果只想读取块组件而不写入,则应在定义实体查询时使用ComponentType.ChunkComponentReadOnly以避免创建不必要的作业调度约束。
删除块组件
使用EntityManager.RemoveChunkComponent函数删除块组件。您可以删除目标块中给定实体的块组件,也可以从实体查询选择的所有块中删除给定类型的所有块组件。
如果您从单个实体中移除块组件,该实体将移动到不同的块,因为实体的原型发生了变化。只要块中还有其他实体,块就会保持不变的块组件。
在查询中使用块组件
要在实体查询中使用块组件,您必须使用ComponentType.ChunkComponent<T>或 [ComponentType.ChunkComponentReadOnly<T>] 函数来指定类型。否则,ECS 会将组件视为通用组件而不是 Chunk 组件。
您可以使用以下查询描述创建一个实体查询,该查询选择所有块以及这些块中具有类型为ChunkComponentA的块组件的实体:
EntityQueryDesc ChunksWithChunkComponentADesc
= new EntityQueryDesc()
{
All = new ComponentType[] {
ComponentType.ChunkComponent<ChunkComponentA>()
}
};
迭代块以设置块组件
要迭代要为其设置块组件的所有块,您可以创建一个实体查询来选择正确的块,然后使用 EntityQuery 对象获取 ArchetypeChunk 实例的列表作为本机数组。ArchetypeChunk 对象允许您向块组件写入新值。
public class ChunkComponentExamples : SystemBase
{
private EntityQuery ChunksWithChunkComponentA;
protected override void OnCreate()
{
EntityQueryDesc ChunksWithComponentADesc = new EntityQueryDesc()
{
All = new ComponentType[] {
ComponentType.ChunkComponent<ChunkComponentA>()
}
};
ChunksWithChunkComponentA
= GetEntityQuery(ChunksWithComponentADesc);
}
[BurstCompile]
struct ChunkComponentCheckerJob : IJobEntityBatch
{
public ComponentTypeHandle<ChunkComponentA> ChunkComponentATypeHandle;
public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
{
var compValue
= batchInChunk.GetChunkComponentData(ChunkComponentATypeHandle);
//...
var squared = compValue.Value * compValue.Value;
batchInChunk.SetChunkComponentData(ChunkComponentATypeHandle,
new ChunkComponentA() { Value = squared });
}
}
protected override void OnUpdate()
{
var job = new ChunkComponentCheckerJob()
{
ChunkComponentATypeHandle
= GetComponentTypeHandle<ChunkComponentA>()
};
this.Dependency
= job.ScheduleParallel(ChunksWithChunkComponentA, 1,
this.Dependency);
}
}
请注意,如果您需要读取块中的组件以确定块组件的正确值,则应使用IJobEntityBatch。例如,以下代码计算包含具有 LocalToWorld 组件的实体的所有块的轴对齐边界框:
public struct ChunkAABB : IComponentData
{
public AABB Value;
}
[UpdateInGroup(typeof(PresentationSystemGroup))]
[UpdateBefore(typeof(UpdateAABBSystem))]
public class AddAABBSystem : SystemBase
{
EntityQuery queryWithoutChunkComponent;
protected override void OnCreate()
{
queryWithoutChunkComponent
= GetEntityQuery(new EntityQueryDesc()
{
All = new ComponentType[] {
ComponentType.ReadOnly<LocalToWorld>()
},
None = new ComponentType[]{
ComponentType.ChunkComponent<ChunkAABB>()
}
});
}
protected override void OnUpdate()
{
// This is a structural change and a sync point
EntityManager.AddChunkComponentData<ChunkAABB>(
queryWithoutChunkComponent,
new ChunkAABB()
);
}
}
[UpdateInGroup(typeof(PresentationSystemGroup))]
public class UpdateAABBSystem : SystemBase
{
EntityQuery queryWithChunkComponent;
protected override void OnCreate()
{
queryWithChunkComponent
= GetEntityQuery(new EntityQueryDesc()
{
All = new ComponentType[]
{
ComponentType.ReadOnly<LocalToWorld>(),
ComponentType.ChunkComponent<ChunkAABB>()
}
});
}
[BurstCompile]
struct AABBJob : IJobEntityBatch
{
[ReadOnly]
public ComponentTypeHandle<LocalToWorld> LocalToWorldTypeHandleInfo;
public ComponentTypeHandle<ChunkAABB> ChunkAabbTypeHandleInfo;
public uint L2WChangeVersion;
public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
{
bool chunkHasChanges
= batchInChunk.DidChange(LocalToWorldTypeHandleInfo,
L2WChangeVersion);
if (!chunkHasChanges)
return; // early out if the chunk transforms haven't changed
NativeArray<LocalToWorld> transforms
= batchInChunk.GetNativeArray<LocalToWorld>(LocalToWorldTypeHandleInfo);
UnityEngine.Bounds bounds = new UnityEngine.Bounds();
bounds.center = transforms[0].Position;
for (int i = 1; i < transforms.Length; i++)
{
bounds.Encapsulate(transforms[i].Position);
}
batchInChunk.SetChunkComponentData(
ChunkAabbTypeHandleInfo,
new ChunkAABB() { Value = bounds.ToAABB() });
}
}
protected override void OnUpdate()
{
var job = new AABBJob()
{
LocalToWorldTypeHandleInfo
= GetComponentTypeHandle<LocalToWorld>(true),
ChunkAabbTypeHandleInfo
= GetComponentTypeHandle<ChunkAABB>(false),
L2WChangeVersion = this.LastSystemVersion
};
this.Dependency
= job.ScheduleParallel(queryWithChunkComponent, 1, this.Dependency);
}
}