はじめに
親子関係のあるオブジェクトを削除する時、従来の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;
}
}