【Unity DOTS】親Entityの削除時に子Entityも一緒に削除する方法

# はじめに

親子関係のあるオブジェクトを削除する時、従来の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`と呼ぶことにします。

<img width="366" alt="スクリーンショット 2019-12-30 午後0.54.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/90dcedfa-8134-e4fe-0a9e-1d88c73a5d5c.png">

これら2つのオブジェクトには親子関係があり、`ParentCube`は`ChildCube`の親オブジェクトである、とします。
<img width="177" alt="スクリーンショット 2019-12-30 午後0.54.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/d51c32f8-1cd1-3edc-8e12-f150f33d3e56.png">

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

実行してEntityDebuggerを確認してみると、
<img width="201" alt="スクリーンショット 2019-12-30 午後1.07.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/e39a05f3-fb16-de0c-3307-16130cfacd99.png">
確かに`ParentCube`も`ChildCube`もEntityに変換されます。

ここからが本題ですが、ここで`ParentCube`を削除しようと思います。

まず、`ParentCube`を識別するためのタグと、その「削除」を行うタイミングを知らせるためのタグを作成します。

```ParentCube.cs
using Unity.Entities;

/// <summary>
/// ParentCubeを識別するためのタグ
/// </summary>
public struct ParentCube : IComponentData
{
}
```

```Destroyable.cs
using Unity.Entities;

/// <summary>
/// 削除するタイミングを知らせるためのタグ
/// </summary>
public struct Destroyable : IComponentData
{
}
```


次のようなスクリプトを作成し、`ParentCube`にアタッチします。

```ParentCubeAuthoring.cs
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`を作成します。
<details><summary>コードを見る</summary><div>

```DestroyCubesSystem.cs
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;
    }
}
```
</div></details>

これで実行してみると、

<img width="416" alt="スクリーンショット 2019-12-30 午後1.46.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/ac6a44ec-5743-0332-c1b8-338e479dc5d2.png">
<img width="210" alt="スクリーンショット 2019-12-30 午後1.46.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164430/a8927b43-29e8-fbb4-27af-7f9b0eeb92ac.png">

確かに`ParentCube`は削除されたのですが、`ChildCube`は削除されません。

さらに、(今回のケースではあまり関係ないですが)`LocalToWorld`のデータが`LocalToParent`のデータに置き換わってしまっているので、予期せぬ場所に子Entityが出現したりして、かなり困ることもあるかもしれません。

# 解決法

子Entityの参照が、親Entityに付いている`Child`というBufferElementDataに入っているので、それを使って子Entityを削除します。


```DestroyCubesSystem.cs
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;
    }
}
```

</div></details>
これで子オブジェクトも削除されるはずです。