系统状态组件
您可以使用SystemStateComponentData来跟踪系统内部的资源,并根据需要创建和销毁这些资源,而无需依赖单个回调。
SystemStateComponentData
和SystemStateSharedComponentData类似于ComponentData
和SharedComponentData
,但ECS不会SystemStateComponentData
在实体销毁时删除。
当一个实体被销毁时,ECS通常会:
- 查找引用特定实体 ID 的所有组件。
- 删除那些组件。
- 回收实体 ID 以供重用。
但是,如果SystemStateComponentData
存在,ECS 不会回收 ID。这使系统有机会清理与实体 ID 关联的任何资源或状态。ECS 仅重用实体 ID 一次SystemStateComponentData
删除。
何时使用系统状态组件
系统可能需要基于ComponentData
. 例如,可能会分配资源。
系统还需要能够将状态作为值进行管理,而其他系统可能会更改状态。例如,当组件中的值发生变化时,或者当相关组件被添加或删除时。
“无回调”是 ECS 设计规则的一个重要元素。
一般使用 SystemStateComponentData
预期镜像用户组件,提供内部状态。
例如,给定:
- FooComponent (
ComponentData
, 用户分配) - FooStateComponent (
SystemComponentData
, 系统分配)
检测何时添加组件
创建组件时,系统状态组件不存在。系统更新对没有系统状态组件的组件的查询,并且可以推断它们已被添加。此时,系统会添加系统状态组件和任何所需的内部状态。
检测组件何时被移除
当您移除一个组件时,系统状态组件仍然存在。系统更新对没有组件的系统状态组件的查询,并且可以推断它们已被移除。此时,系统移除系统状态组件并修复任何需要的内部状态。
检测实体何时被销毁
DestroyEntity
是一个速记实用程序:
- 查找引用给定实体 ID 的组件。
- 删除找到的组件。
- 回收实体 ID。
但是,SystemStateComponentData
在删除DestroyEntity
最后一个组件之前,不会删除,并且不会回收实体 ID。这使系统有机会以与删除组件完全相同的方式清理内部状态。
系统状态组件
ASystemStateComponentData
类似于 a ComponentData
。
struct FooStateComponent : ISystemStateComponentData
{
}
a 的可见性SystemStateComponentData
也以与组件相同的方式进行控制(使用private
, public
, internal
) 但是,作为一般规则, aSystemStateComponentData
将在ReadOnly
创建它的系统之外。
系统状态共享组件
ASystemStateSharedComponentData
类似于 a SharedComponentData
。
struct FooStateSharedComponent : ISystemStateSharedComponentData
{
public int Value;
}
使用状态组件的示例系统
以下示例显示了一个简化的系统,该系统说明了如何使用系统状态组件管理实体。该示例定义了一个通用 IComponentData 实例和一个系统状态 ISystemStateComponentData 实例。它还基于这些实体定义了三个查询:
m_newEntities
选择具有通用但不具有系统状态组件的实体。此查询查找系统以前未见过的新实体。系统使用添加系统状态组件的新实体查询运行作业。m_activeEntities
选择具有通用和系统状态组件的实体。在实际应用中,其他系统可能是处理或销毁实体的系统。m_destroyedEntities
选择具有系统状态但不具有通用组件的实体。由于系统状态组件本身从未添加到实体中,因此该查询选择的实体必须已被该系统或其他系统删除。系统重用销毁的实体查询来运行作业并从实体中删除系统状态组件,这允许 ECS 代码回收实体标识符。
笔记
这个简化的例子不维护系统内的任何状态。系统状态组件的一个目的是跟踪何时需要分配或清理持久资源。
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
public struct GeneralPurposeComponentA : IComponentData
{
public int Lifetime;
}
public struct StateComponentB : ISystemStateComponentData
{
public int State;
}
public partial class StatefulSystem : SystemBase
{
private EntityCommandBufferSystem ecbSource;
protected override void OnCreate()
{
ecbSource = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
// Create some test entities
// This runs on the main thread, but it is still faster to use a command buffer
EntityCommandBuffer creationBuffer = new EntityCommandBuffer(Allocator.Temp);
EntityArchetype archetype = EntityManager.CreateArchetype(typeof(GeneralPurposeComponentA));
for (int i = 0; i < 10000; i++)
{
Entity newEntity = creationBuffer.CreateEntity(archetype);
creationBuffer.SetComponent<GeneralPurposeComponentA>
(
newEntity,
new GeneralPurposeComponentA() { Lifetime = i }
);
}
//Execute the command buffer
creationBuffer.Playback(EntityManager);
}
protected override void OnUpdate()
{
EntityCommandBuffer.ParallelWriter parallelWriterECB = ecbSource.CreateCommandBuffer().AsParallelWriter();
// Entities with GeneralPurposeComponentA but not StateComponentB
Entities
.WithNone<StateComponentB>()
.ForEach(
(Entity entity, int entityInQueryIndex, in GeneralPurposeComponentA gpA) =>
{
// Add an ISystemStateComponentData instance
parallelWriterECB.AddComponent<StateComponentB>
(
entityInQueryIndex,
entity,
new StateComponentB() { State = 1 }
);
})
.ScheduleParallel();
ecbSource.AddJobHandleForProducer(this.Dependency);
// Create new command buffer
parallelWriterECB = ecbSource.CreateCommandBuffer().AsParallelWriter();
// Entities with both GeneralPurposeComponentA and StateComponentB
Entities
.WithAll<StateComponentB>()
.ForEach(
(Entity entity,
int entityInQueryIndex,
ref GeneralPurposeComponentA gpA) =>
{
// Process entity, in this case by decrementing the Lifetime count
gpA.Lifetime--;
// If out of time, destroy the entity
if (gpA.Lifetime <= 0)
{
parallelWriterECB.DestroyEntity(entityInQueryIndex, entity);
}
})
.ScheduleParallel();
ecbSource.AddJobHandleForProducer(this.Dependency);
// Create new command buffer
parallelWriterECB = ecbSource.CreateCommandBuffer().AsParallelWriter();
// Entities with StateComponentB but not GeneralPurposeComponentA
Entities
.WithAll<StateComponentB>()
.WithNone<GeneralPurposeComponentA>()
.ForEach(
(Entity entity, int entityInQueryIndex) =>
{
// This system is responsible for removing any ISystemStateComponentData instances it adds
// Otherwise, the entity is never truly destroyed.
parallelWriterECB.RemoveComponent<StateComponentB>(entityInQueryIndex, entity);
})
.ScheduleParallel();
ecbSource.AddJobHandleForProducer(this.Dependency);
}
protected override void OnDestroy()
{
// Implement OnDestroy to cleanup any resources allocated by this system.
// (This simplified example does not allocate any resources, so there is nothing to clean up.)
}
}