Looking up data(查阅数据)
访问和修改ECS数据的最有效方式是使用一个具有实体查询和作业的系统。这提供了对CPU资源的最佳利用,同时内存缓存失误最少。事实上,你的数据设计的目标之一应该是使用最有效、最快的路径来执行大部分的数据转换。然而,有时你需要在程序中的一个任意点访问一个任意实体的任意组件。
给定一个实体对象,你可以在其IComponentData和动态缓冲区中查找数据。方法不同,取决于你的代码是在系统中使用Entities.ForEach或使用[IJobChunk]作业执行,还是在主线程的其他地方执行。
Looking up entity data in a system(在一个系统中查询实体数据)
使用GetComponent(Entity)从系统的Entities.ForEach或[Job.WithCode]函数中查找存储在任意实体的一个组件中的数据。
例如,如果你有Target组件,并有一个Entity字段来识别目标实体,你可以使用下面的代码将跟踪实体向其目标旋转。
public partial class TrackingSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = this.Time.DeltaTime;
Entities
.ForEach((ref Rotation orientation,
in LocalToWorld transform,
in Target target) =>
{
// Check to make sure the target Entity still exists and has
// the needed component
if (!HasComponent<LocalToWorld>(target.entity))
return;
// Look up the entity data
LocalToWorld targetTransform
= GetComponent<LocalToWorld>(target.entity);
float3 targetPosition = targetTransform.Position;
// Calculate the rotation
float3 displacement = targetPosition - transform.Position;
float3 upReference = new float3(0, 1, 0);
quaternion lookRotation =
quaternion.LookRotationSafe(displacement, upReference);
orientation.Value =
math.slerp(orientation.Value, lookRotation, deltaTime);
})
.ScheduleParallel();
}
}
访问存储在动态缓冲区的数据需要一个额外的步骤。你必须在OnUpdate()方法中声明一个BufferFromEntity类型的局部变量。然后你可以在你的lambda函数中 "捕获 "这个局部变量。
public struct BufferData : IBufferElementData
{
public float Value;
}
public partial class BufferLookupSystem : SystemBase
{
protected override void OnUpdate()
{
BufferFromEntity<BufferData> buffersOfAllEntities
= this.GetBufferFromEntity<BufferData>(true);
Entities
.ForEach((ref Rotation orientation,
in LocalToWorld transform,
in Target target) =>
{
// Check to make sure the target Entity with this buffer type still exists
if (!buffersOfAllEntities.HasComponent(target.entity))
return;
// Get a reference to the buffer
DynamicBuffer<BufferData> bufferOfOneEntity =
buffersOfAllEntities[target.entity];
// Use the data in the buffer
float avg = 0;
for (var i = 0; i < bufferOfOneEntity.Length; i++)
{
avg += bufferOfOneEntity[i].Value;
}
if (bufferOfOneEntity.Length > 0)
avg /= bufferOfOneEntity.Length;
})
.ScheduleParallel();
}
}
Looking up entity data in IJobEntityBatch(在IJobEntityBatch中查询实体数据)
要随机访问IJobEntityBatch或其他Job结构中的组件数据,请使用以下类型之一,以获得类似数组的组件接口,按Entity对象索引:
- ComponentDataFromEntity
- BufferFromEntity
声明一个ComponentDataFromEntity或BufferFromEntity类型的字段,并在调度作业前设置该字段的值。
例如,如果你有Target组件,并有一个Entity字段来识别目标实体,你可以在作业结构中添加以下字段来查询目标的世界位置。
[ReadOnly]
public ComponentDataFromEntity<LocalToWorld> EntityPositions;
请注意,这个声明使用了ReadOnly属性。你应该始终将ComponentDataFromEntity对象声明为只读,除非你对你访问的组件进行了写入。
你可以在安排工作时设置这个字段,如下所示:
var job = new ChaserSystemJob();
job.EntityPositions = this.GetComponentDataFromEntity<LocalToWorld>(true);
在作业的Execute()函数中,你可以使用一个Entity对象来查询一个组件的值:
float3 targetPosition = EntityPositions[targetEntity].Position;
下面这个完整的例子显示了一个系统,该系统将有一个包含其目标实体对象的目标字段的实体向目标的当前位置移动:
public class MoveTowardsEntitySystem : SystemBase
{
private EntityQuery query;
[BurstCompile]
private struct MoveTowardsJob : IJobEntityBatch
{
// Read-write data in the current chunk
public ComponentTypeHandle<Translation> PositionTypeHandleAccessor;
// Read-only data in the current chunk
[ReadOnly]
public ComponentTypeHandle<Target> TargetTypeHandleAccessor;
// Read-only data stored (potentially) in other chunks
[ReadOnly]
public ComponentDataFromEntity<LocalToWorld> EntityPositions;
// Non-entity data
public float deltaTime;
public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
{
// Get arrays of the components in chunk
NativeArray<Translation> positions
= batchInChunk.GetNativeArray<Translation>(PositionTypeHandleAccessor);
NativeArray<Target> targets
= batchInChunk.GetNativeArray<Target>(TargetTypeHandleAccessor);
for (int i = 0; i < positions.Length; i++)
{
// Get the target Entity object
Entity targetEntity = targets[i].entity;
// Check that the target still exists
if (!EntityPositions.HasComponent(targetEntity))
continue;
// Update translation to move the chasing enitity toward the target
float3 targetPosition = EntityPositions[targetEntity].Position;
float3 chaserPosition = positions[i].Value;
float3 displacement = targetPosition - chaserPosition;
positions[i] = new Translation
{
Value = chaserPosition + displacement * deltaTime
};
}
}
}
protected override void OnCreate()
{
// Select all entities that have Translation and Target Componentx
query = this.GetEntityQuery
(
typeof(Translation),
ComponentType.ReadOnly<Target>()
);
}
protected override void OnUpdate()
{
// Create the job
var job = new MoveTowardsJob();
// Set the chunk data accessors
job.PositionTypeHandleAccessor =
this.GetComponentTypeHandle<Translation>(false);
job.TargetTypeHandleAccessor =
this.GetComponentTypeHandle<Target>(true);
// Set the component data lookup field
job.EntityPositions = this.GetComponentDataFromEntity<LocalToWorld>(true);
// Set non-ECS data fields
job.deltaTime = this.Time.DeltaTime;
// Schedule the job using Dependency property
this.Dependency = job.ScheduleParallel(query, 1, this.Dependency);
}
}
Data access errors(数据访问错误)
如果你要查询的数据与你在作业中直接读写的数据重叠,那么随机访问会导致竞争条件和微妙的bug。当你确定你在作业中直接读写的特定实体数据和你随机读写的特定实体数据之间没有重叠,那么你可以用NativeDisableParallelForRestriction属性来标记访问器对象。