Unity ECS で動的に親子関係を作る
始めに
プログラムから親子関係を作るのにてこずったので記録を残す。
- Unity 2022.3.27f1
- com.unity.entities 1.0.16
- com.unity.brust 1.8.13
参考
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 も取り扱いが面倒な代物。主題ではないのでコードだけ載せる。
(2024/05/29 14:39:00 追記)以下コードはAndroidビルドでエラーになる。別記事20240529-01に対処方法を記載した。
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 に変わる際の破壊的変更により 更新されていないドキュメントが検索で見つかるから。 ここに書くことも近いうちに変わってしまうかもしれないが、それでも誰かの役に立つ事を期待する。