Unity ECS で動的に親子関係を作る

始めに

プログラムから親子関係を作るのにてこずったので記録を残す。

参考

Unityで最近のDOTSを触ってみた。前編:実装Tips https://gaprot.jp/2023/10/23/unity-dots-part-1/

公式サンプル https://github.com/Unity-Technologies/EntityComponentSystemSamples

特に以下 https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/EntitiesSamples/Assets/HelloCube/6.%20Reparenting https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/EntitiesSamples/Assets/Baking/PrefabReference

内容

EntityCommandBuffer.CreateEntity を使ってエンティティを作り、 それに対して

ecb.AddComponent(entity, new Unity.Transforms.Parent()
{
    Value = parentEntity,
});

として親を設定しようとするとエラーになる。

ArgumentException: System.ArgumentException: System.String Unity.Entities.EntityComponentStore::AppendRemovedComponentRecordError(Unity.Entities.Entity,Unity.Entities.ComponentType)

解決策は Prefab を作り EntityCommandBuffer.Instantiate を使ってエンティティを作る事。 Prefab はカラで良い。

なお Prefab も取り扱いが面倒な代物。主題ではないのでコードだけ載せる。

public class MyPrefab : MonoBehaviour
{
    public GameObject Prefab;

    class Baker : Baker<MyPrefab>
    {
        public override void Bake(MyPrefab myPrefab)
        {
            var prefabReference = new Unity.Entities.Serialization.EntityPrefabReference(myPrefab.Prefab);

            var entity = GetEntity(TransformUsageFlags.None);
            AddComponent(entity, new InitializeComponentData
            {
            });
            AddComponent(entity, new RequestEntityPrefabLoaded
            {
                Prefab = prefabReference
            });
        }
    }

    public struct InitializeComponentData : IComponentData
    {
    }
    public struct ComponentData : IComponentData
    {
        public Entity PrefabEntity;
    }
}

public partial struct MyPrefabInitializeSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<MyPrefab.InitializeComponentData>();
        state.RequireForUpdate<PrefabLoadResult>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var entity = SystemAPI.GetSingletonEntity<MyPrefab.InitializeComponentData>();
        if (!SystemAPI.HasComponent<PrefabLoadResult>(entity)) return;

        state.Enabled = false;

        var prefabLoadResult = SystemAPI.GetComponent<PrefabLoadResult>(entity);

        using var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);

        ecb.AddComponent(entity, new MyPrefab.ComponentData()
        {
            PrefabEntity = prefabLoadResult.PrefabRoot
        });
        ecb.RemoveComponent<MyPrefab.InitializeComponentData>(entity);
        ecb.RemoveComponent<PrefabLoadResult>(entity);

        ecb.Playback(state.EntityManager);
    }
}

public class MyClass : MonoBehaviour
{
    class Baker : Baker<MyClass>
    {
        public override void Bake(MyClass cd)
        {
            var entity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.NonUniformScale);
            AddComponent(entity, new ComponentData
            {
            });

            AddComponent(entity, new InitializeComponentData()
            {
            });
        }
    }

    public struct ComponentData : IComponentData
    {
    }

    public struct InitializeComponentData : IComponentData
    {
    }
}

public partial struct MyClassInitializeSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<MyClass.ComponentData>();
        state.RequireForUpdate<MyClass.InitializeComponentData>();
        state.RequireForUpdate<MyPrefab.ComponentData>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var myPrefabEntity = SystemAPI.GetSingletonEntity<MyPrefab.ComponentData>();
        var myPrefab = SystemAPI.GetComponent<MyPrefab.ComponentData>(myPrefabEntity);

        using var ecb = new EntityCommandBuffer(Allocator.Temp);

        foreach (var (myClassCD, myClassInitializeCD, entity) in SystemAPI.Query<
            RefRW<MyClass.ComponentData>,
            RefRO<MyClass.InitializeComponentData>>()
            .WithEntityAccess())
        {
            var childEntity = ecb.Instantiate(myPrefab.PrefabEntity);
            ecb.AddComponent(childEntity, new Unity.Transforms.Parent()
            {
                Value = entity,
            });
            //ecb.AddComponent(childEntity, );

            ecb.RemoveComponent<MyClass.InitializeComponentData>(entity);
        }

        ecb.Playback(state.EntityManager);
    }
}

最後に

ECS は難しい。その理由の一つとして Entities バージョン 0.X から 1.X に変わる際の破壊的変更により 更新されていないドキュメントが検索で見つかるから。 ここに書くことも近いうちに変わってしまうかもしれないが、それでも誰かの役に立つ事を期待する。

top