系统状态组件

您可以使用SystemStateComponentData来跟踪系统内部的资源,并根据需要创建和销毁这些资源,而无需依赖单个回调。

SystemStateComponentDataSystemStateSharedComponentData类似于ComponentDataSharedComponentData,但ECS不会SystemStateComponentData在实体销毁时删除

当一个实体被销毁时,ECS通常会:

  1. 查找引用特定实体 ID 的所有组件。
  2. 删除那些组件。
  3. 回收实体 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也以与组件相同的方式进行控制(使用privatepublicinternal) 但是,作为一般规则, 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.)
    }
}

 

posted @ 2021-09-24 00:32  alps_01  阅读(131)  评论(0编辑  收藏  举报