【Unity】ECSの「Disabled ComponentData」と「Prefab ComponentData」について解説してみる

更新自体は大分前ですが「[entities@0.0.12-preview.12](https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/ReleaseNotes.md#0011)」から表題にある`Disabled ComponentData`と`Preafab ComponentData`と言う機能が追加されていたみたいです。

※ReleaseNotesより一部引用
> ・Global Disabled component. Any component data associated with same entity referenced by Disabled component will be ignored by all system updates.
> ・Global Prefab component. Same behavior as Disabled component, except when an Entity associated with a Prefab component is Instantiated, the Prefab component is not present in the created archetype.

試しに検証してみた所、意外と便利な感じがしたので概要や使い方などをメモ。

## ▼ 実装/動作環境

- **Unity version**
    - Unity2018.3.0b5
- **依存パッケージ**
    - com.unity.entities@0.0.12-preview.18
        - **※ECSはPreviewの為か、バージョンによっては破壊的な変更が入っているのでバージョン間に於ける互換性が保証されておりません。その点ご注意下さい。**



# ▽ Disabled ComponentData

先ずは`Disabled ComponentData`について解説していきます。
簡単に要約すると以下の機能を持つEntityを生成できます。

- EntityがComponentを所持している間は全てのComponentSystemで一切動作しない。
    - ※EntityDebuggerにも表示されない。
- **RemoveComponentでDisabledを外すとEntityは再び動作する。**

特徴としては上述の通り`Disabled`の有無でEntityの停止/再開を切り替えられる点になるかと思われます。
ちなみにDisabledで停止してもEntityが持つデータの状態は維持されるので、GameObjectのSetActiveに近い形の運用が出来るかと思われます。


## ▼ 実装例

以下のコードはサンプルコードのコードです。
実行すると↓の様に5つの回転するCubeが横並びに生成されます。  

![sample2.gif](https://qiita-image-store.s3.amazonaws.com/0/80207/2ddd3014-c2cd-1d60-71d0-aa44222b1778.gif)

画面下にあるボタンは`Disabled ComponentData`が「付いているならRemoveComponent」「付いていないならAddComponentData」を行うボタンで、配置は上に並ぶCubeの並び順と連動しており、例えば1のボタンを押下すると一番左側のCubeの`Disabled`の設定を切り替えることが出来ます。
切り替え後の挙動に注目すると回転情報はそのまま維持されているように見受けられるので、「データを保持してEntityを削除→再生する」と言った手順よりは気軽に使えるかもしれません。

※コード中で`Prefab ComponentData`が用いられておりますが、こちらについては後述。

● DisabledTest.cs

```csharp
namespace MainContents
{
    using UnityEngine;
    using UnityEngine.UI;

    using Unity.Entities;
    using Unity.Collections;
    using Unity.Mathematics;
    using Unity.Transforms;
    using Unity.Rendering;
    using Unity.Jobs;
    using Unity.Burst;

    using MainContents.ECS;

    public sealed class DisabledTest : MonoBehaviour
    {
        const int MaxObjectNum = 5;
        [SerializeField] MeshInstanceRenderer _meshInstanceRenderer;

        EntityManager _entityManager = null;
        Entity[] _entities = new Entity[MaxObjectNum];

        void Start()
        {
            World.DisposeAllWorlds();

            World.Active = new World("Sample World");
            var entityManager = World.Active.CreateManager<EntityManager>();
            World.Active.CreateManager(typeof(EndFrameTransformSystem));
            World.Active.CreateManager(typeof(RenderingSystemBootstrap));
            World.Active.CreateManager(typeof(AnimationSytstem));   // Entityを動かすだけのシステム
            ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.Active);

            var archetype = entityManager.CreateArchetype(
                ComponentType.Create<Prefab>(),
                ComponentType.Create<Position>(),
                ComponentType.Create<Rotation>(),
                ComponentType.Create<LocalToWorld>(),
                ComponentType.Create<MeshInstanceRenderer>());

            float posX = -12f;

            // Prefab Entityの生成(これは動かない。そもそもEntityDebuggerにすら表示されていない?)
            // →先にSharedの設定と言った共通設定を済ませておく
            var prefabEntity = entityManager.CreateEntity(archetype);
            entityManager.SetSharedComponentData(prefabEntity, this._meshInstanceRenderer);
            for (int i = 0; i < MaxObjectNum; ++i)
            {
                // Entityの量産
                // ※Entity Debuggerを見た感じだとPrefab ComponentDataが含まれていない
                var entity = entityManager.Instantiate(prefabEntity);
                entityManager.SetComponentData(entity, new Position { Value = new float3(posX += 4f, 0f, 0f) });
                entityManager.SetComponentData(entity, new Rotation { Value = UnityEngine.Random.rotation });
                this._entities[i] = entity;
            }
            this._entityManager = entityManager;
        }

        void OnDestroy()
        {
            World.DisposeAllWorlds();
        }

        public void OnClick(int index)
        {
            // Entityのindexに該当するボタンが押されたらDisabled ComponentDataの付加状況に応じて以下の対応
            // ・付いていたらRemoveComponentで外して再起動
            // ・付いていないならAddComponentDataで付けて停止

            // ※Disabledが付いている間は表示含めて一切のComponentDataの更新は止まるが、
            // データ自体は保持されるので「動かしたくないけどデータは取っておきたい」的な状況には使えるかもしれない。
            var entity = this._entities[index];
            if (this._entityManager.HasComponent<Disabled>(entity))
            {
                this._entityManager.RemoveComponent<Disabled>(entity);
            }
            else
            {
                this._entityManager.AddComponentData(entity, new Disabled());
            }
        }
    }
}
```

● AnimationSystem.cs

```csharp
namespace MainContents.ECS
{
    using UnityEngine;

    using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    using Unity.Jobs;
    using Unity.Burst;

    public sealed class AnimationSytstem : JobComponentSystem
    {
        [BurstCompile]
        public struct MyJob : IJobProcessComponentData<Rotation>
        {
            float _deltaTime;
            public MyJob(float deltaTime) => this._deltaTime = deltaTime;
            public void Execute(ref Rotation rot)
            {
                rot.Value = math.mul(
                    math.normalize(rot.Value),
                    quaternion.AxisAngle(math.up(), 2f * this._deltaTime));
            }
        }
        protected override JobHandle OnUpdate(JobHandle inputDeps) => new MyJob(Time.deltaTime).Schedule(this, inputDeps);
    }
}
```





# ▽ Prefab ComponentData

次に`Prefab CompobentData`について解説して行きたいと思います。
簡単に要約すると以下の機能を持つEntityを生成できます。

- `上述のDisabled`と同じくEntityがComponentを所持している間は全てのComponentSystemで一切動作しない。
    - ※EntityDebuggerにも表示されない。
- **`Disabled`と同じくRemoveComponentでDisabledを外すとEntityは再び動作する。**
- **「`Prefab`を持つEntity」を元にInstantiateしたEntityには`Prefab`が外れた状態で複製される。**

特徴としては複製した際の挙動になるかと思われます。
その為に「ゲーム中では動作しない初期化(描画情報の設定など)だけを済ませた原型Entity 」を予め生成しておき、後は生成が必要なタイミングでこちらを元にEntityManager.Instantiateで複製すると言った使いみち等が考えられるかと思われます。
※挙動的に原型を元に複製を行うのでPrefabと言う名称なのかもしれません。


## ▼ 実装例

以下のコードはサンプルコードのコードです。
実行すると↓の様に5つの回転するCubeが横並びに生成されます。

![sample1.gif](https://qiita-image-store.s3.amazonaws.com/0/80207/8d992fd8-39ee-b826-9aea-92bc4121031b.gif)

実はこちらで生成しているEntityとしてはPrefabEntityを含めると6つあり、「ENABLE_PREFAB_ENTITY」の定義を外すとPrefabを使用しない状態で生成するので存在を確認することが出来ます。

サンプルの例としては最初に必要な共通設定(描画情報)を済ませておき、後はこれをベースにEntityを生成し、固有に持つ位置/回転情報と言ったものをSetComponentDataしてます。

● PrefabTest.cs

```csharp
#define ENABLE_PREFAB_ENTITY

namespace MainContents
{
    using UnityEngine;

    using Unity.Entities;
    using Unity.Collections;
    using Unity.Mathematics;
    using Unity.Transforms;
    using Unity.Rendering;
    using Unity.Jobs;
    using Unity.Burst;

    using MainContents.ECS;

    public sealed class PrefabTest : MonoBehaviour
    {
        const int MaxObjectNum = 5;
        [SerializeField] MeshInstanceRenderer _meshInstanceRenderer;

        void Start()
        {
            World.DisposeAllWorlds();

            World.Active = new World("Sample World");
            var entityManager = World.Active.CreateManager<EntityManager>();
            World.Active.CreateManager(typeof(EndFrameTransformSystem));
            World.Active.CreateManager(typeof(RenderingSystemBootstrap));
            World.Active.CreateManager(typeof(AnimationSytstem));   // Entityを動かすだけのシステム
            ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.Active);

            var archetype = entityManager.CreateArchetype(
                // "ENABLE_PREFAB_ENTITY"未定義時はPrefabEntityでは無く普通のEntityとして動作
#if ENABLE_PREFAB_ENTITY
                ComponentType.Create<Prefab>(),
#endif
                ComponentType.Create<Position>(),
                ComponentType.Create<Rotation>(),
                ComponentType.Create<LocalToWorld>(),
                ComponentType.Create<MeshInstanceRenderer>());

            float posX = -9f;

            // Prefab Entityの生成(これは動かない。そもそもEntityDebuggerにすら表示されていない?)
            // →先にSharedの設定と言った共通設定を済ませておく
            var prefabEntity = entityManager.CreateEntity(archetype);
            entityManager.SetSharedComponentData(prefabEntity, this._meshInstanceRenderer);
#if !ENABLE_PREFAB_ENTITY
            entityManager.SetComponentData(prefabEntity, new Position { Value = new float3(posX, 0f, 0f) });
            entityManager.SetComponentData(prefabEntity, new Rotation { Value = UnityEngine.Random.rotation });
#endif

            for (int i = 0; i < MaxObjectNum; ++i)
            {
                // Entityの量産
                // ※Entity Debuggerを見た感じだとPrefab ComponentDataが含まれていない
                var entity = entityManager.Instantiate(prefabEntity);
                entityManager.SetComponentData(entity, new Position { Value = new float3(posX += 3.5f, 0f, 0f) });
                entityManager.SetComponentData(entity, new Rotation { Value = UnityEngine.Random.rotation });
            }
        }

        void OnDestroy()
        {
            World.DisposeAllWorlds();
        }
    }
}
```


### ~~※補足 : RemoveComponent時の挙動について~~

※追記 : こちらの記載は検証のミスによる認識違いでした。。お騒がせしました..。

~~試しにPrefabEntityに対してRemoveComponentで`Prefab ComponentData`を外してみた所、EntityDebuggerには表示されるものの正常に動作していない(?)様に見受けられました。(ひょっとしたら解決策はあるかもしれませんが未検証)~~

~~全体的な挙動はまだ見えておりませんが、基本的には一度PrefabEntityとして生成したものはベース以外には使わない方向で考えてみるのも有りかもしれません。~~