はじめに
親子関係のあるオブジェクトを削除する時、従来のUnityであれば親オブジェクトを削除すれば子オブジェクトも併せて一緒に削除されます。
しかし、ECSでは親Entityを削除しても子Entityは削除されず、データが書き変わって残り続けてしまいます。
そのような問題を解決する方法をこの記事に書きました。
実行環境
- Unity 2019.3.0f3
- Entities 0.4.0 preview.10
- Hybrid Renderer 0.3.1 preview.10
注意
- 本記事ではpreview packageを多く使用しています。今の実装と本記事の実装が大幅に異なる可能性があるのでご注意下さい。
- 本記事ではGameObject/Component を使用したHybrid ECSを前提としています。
問題
下図のように2つのオブジェクトがある場合を考えます。
白くて大きい立方体のオブジェクトをParentCube、
黒くて小さい立方体のオブジェクトをChildCubeと呼ぶことにします。
これら2つのオブジェクトには親子関係があり、ParentCubeはChildCubeの親オブジェクトである、とします。

ここで、これらのオブジェクトをEntity化することを考え、ParentCubeにのみConvertToEntityをAddします。
実行してEntityDebuggerを確認してみると、

確かにParentCubeもChildCubeもEntityに変換されます。
ここからが本題ですが、ここでParentCubeを削除しようと思います。
まず、ParentCubeを識別するためのタグと、その「削除」を行うタイミングを知らせるためのタグを作成します。
using Unity.Entities;
/// <summary>
/// ParentCubeを識別するためのタグ
/// </summary>
public struct ParentCube : IComponentData
{
}
using Unity.Entities;
/// <summary>
/// 削除するタイミングを知らせるためのタグ
/// </summary>
public struct Destroyable : IComponentData
{
}
次のようなスクリプトを作成し、ParentCubeにアタッチします。
using UnityEngine;
using Unity.Entities;
public class ParentCubeAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, new ParentCube());
}
}
そして、ParentCubeを削除する(そしてそれによりChildCubeも削除されることを期待する)、DestroyCubesSystemを作成します。
コードを見る
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
public class DestroyCubesSystem : JobComponentSystem
{
private EntityCommandBufferSystem _bufferSystem;
protected override void OnCreate()
{
_bufferSystem = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
}
[BurstCompile]
private struct DestroyCubesJob : IJobForEachWithEntity<ParentCube, Destroyable>
{
public EntityCommandBuffer.Concurrent CommandBuffer;
public void Execute(Entity entity, int index, [ReadOnly] ref ParentCube c0, [ReadOnly] ref Destroyable c1)
{
CommandBuffer.DestroyEntity(index, entity);
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var jobHandle = new DestroyCubesJob
{
CommandBuffer = _bufferSystem.CreateCommandBuffer().ToConcurrent(),
}.Schedule(this, inputDeps);
return jobHandle;
}
}
これで実行してみると、
確かにParentCubeは削除されたのですが、ChildCubeは削除されません。
さらに、(今回のケースではあまり関係ないですが)LocalToWorldのデータがLocalToParentのデータに置き換わってしまっているので、予期せぬ場所に子Entityが出現したりして、かなり困ることもあるかもしれません。
解決法
子Entityの参照が、親Entityに付いているChildというBufferElementDataに入っているので、それを使って子Entityを削除します。
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
public class DestroyCubesSystem : JobComponentSystem
{
private EntityCommandBufferSystem _bufferSystem;
protected override void OnCreate()
{
_bufferSystem = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
}
[BurstCompile]
private struct DestroyCubesJob : IJobForEachWithEntity<ParentCube, Destroyable>
{
public EntityCommandBuffer.Concurrent CommandBuffer;
// 追加
[ReadOnly] public BufferFromEntity<Child> ChildBufferFromEntity;
public void Execute(Entity entity, int index, [ReadOnly] ref ParentCube c0, [ReadOnly] ref Destroyable c1)
{
// 追加
var childEntityBuffer = ChildBufferFromEntity[entity];
// 追加
for (var i = 0; i < childEntityBuffer.Length; i++)
{
CommandBuffer.DestroyEntity(index, childEntityBuffer[i].Value);
}
CommandBuffer.DestroyEntity(index, entity);
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var jobHandle = new DestroyCubesJob
{
CommandBuffer = _bufferSystem.CreateCommandBuffer().ToConcurrent(),
// 追加
ChildBufferFromEntity = GetBufferFromEntity<Child>()
}.Schedule(this, inputDeps);
return jobHandle;
}
}