ECSの簡単な実装と変更点について

## はじめに
ECSの登場から２年半、qiitaにも情報が集積して手を付けやすくなりましたね。
今では情報の流れもゆっくりになり、ECSの更新が続き過去のコードにも変更が必要になりました。
そこで本記事では今のECSで簡単なコードを紹介して、過去のコードからの変更点をお見せできればと思います。

### 環境
Unityバージョン:2020.1.12f1
Entities: 0.16.0-preview 21
Hybrid Renderer : 0.10.0-preview 21

##インストール
Unity2020.1から一部のパッケージがPackageManagerに[表示されなくなりました]
(https://helpdesk.unity3d.co.jp/hc/ja/articles/900002171066-Unity-2020-1-%E3%81%8B%E3%82%89%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3%E3%81%A7%E7%99%BA%E8%A6%8B%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%8F%E3%81%AA%E3%81%A3%E3%81%9F%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95)。

EntitiesやHybridRendererも表示されないので、こちらは手動でインストールする必要があります。

まずはPackageManagerを開き、「+」-「Add package from git URL...」で"com.unity.entities"を指定します。
同様に"com.unity.rendering.hybrid"を指定してHybridRendererをインストールします。

これでECSを使う準備ができました。

## まずはシンプルな実装
まずはMonoBehaviourからEntityを生成するときの変更部分を見ていきましょう

  
ECSを扱うにはEntityManagerが外せないのですが、以前はWorld.Activeから取得していました。(独自Worldを除く)
現在はActiveが無くなり、DefaultGameObjectInjectionWorldに代わっています。


```:sample.cs
        // 以前
        var em = World.Active.EntityManager;

        // いま
        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
```


Hybrid Rendererを使うときに、RenderBoundsが必要になりました。
RenderBoundsはAABBを持つクラスですが、SetComponentDataなどでパラメータを設定する必要はありません。
RenderBoundsが無い場合はエラーにはなりませんが描画されなくなりますので注意しましょう。

```:sample.cs
        // 以前
        var archetype = em.CreateArchetype(
                                typeof(Translation),
                                typeof(LocalToWorld),
                                typeof(RenderMesh));

        // いま
        var archetype = em.CreateArchetype(
                                typeof(Translation),
                                typeof(LocalToWorld),
                                typeof(RenderMesh),
                                typeof(RenderBounds));
```


## ジョブの実行
少し前のECSではIJobForEachやIJobForEachWithEntityを使っていましたが、こちらは非推奨となり今後は削除されることになりました。
代わりにEntities.ForEachかIJobChunkを使用する必要があります。(ほとんどのパターンでEntities.ForEachの方が使いやすいかと思います)

### Entities.ForEachの場合
下記はEntities.ForEachを使用して、３秒おきにEntityを生成するコードです。

```:SpawnerForEach.cs
public struct SpawnerComponent : IComponentData
{
    public float timer;
}
public class SpawnerForEach : JobComponentSystem
{
    private EntityArchetype m_spawnArchetype;
    private BeginSimulationEntityCommandBufferSystem m_commandBufferSystem;

    protected override void OnCreate()
    {
        // この辺りは以前と同じ
        m_commandBufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();

        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
        m_spawnArchetype = em.CreateArchetype(typeof(Translation), typeof(LocalToWorld), typeof(RenderMesh), typeof(RenderBounds));
    }

    protected override JobHandle OnUpdate( JobHandle inputDeps)
    {
        float dt = Time.DeltaTime;

        // ジョブ内で使用できるようにAsParallelWriter()を呼ぶ
        var commandBuffer = m_commandBufferSystem.CreateCommandBuffer().AsParallelWriter();
        var archeType = m_spawnArchetype;

        // BurstCompileを有効にしてCustom_ForEachという名前でジョブを実行
        inputDeps = Entities.
                        WithName("Custom_ForEach").
                        WithBurst().
                        ForEach((ref Translation pos, ref SpawnerComponent spawner) =>
        {
            spawner.timer += dt;
            if (spawner.timer >= 3.0f)
            {
                spawner.timer = 0.0f;

                // Entityを生成.
                var e = commandBuffer.CreateEntity(0, archeType);
                commandBuffer.SetComponent<Translation>(0, e, new Translation
                {
                    Value = new float3(0, 0, 0),
                });
            }
        }).Schedule( inputDeps);

        return inputDeps;
    }
}
```

EntityCommandBuffer.ToConcurrent()は非推奨になり代わりにAsParallelWriter()になりました。
余談ですが、WithName()に半角スペースが入っているとエラーになるので"_"などで代用しましょう。


## IJobChunkの場合
先ほどの３秒ごとにEntityを生成するジョブをIJobChunkで書くと以下のようになります。

```:SpawnerJobChunk.cs
public class Spawner : JobComponentSystem
{
    private EntityQuery m_query;
    private EntityArchetype m_spawnArchetype;
    private BeginSimulationEntityCommandBufferSystem m_commandBufferSystem;

    [BurstCompile]
    struct SpawnJob : IJobChunk
    {
        public float dt;

        [ReadOnly]public ComponentTypeHandle<Translation> transHandle;
        public ComponentTypeHandle<SpawnerComponent> spawnerHandle;

        public EntityArchetype spawnArchetype;
        public EntityCommandBuffer.ParallelWriter commandBuffer;

        public void Execute( ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
            var translations = chunk.GetNativeArray(transHandle);
            var spawners = chunk.GetNativeArray(spawnerHandle);

            for( int i = 0; i < chunk.Count; ++i)
            {
                var translation = translations[i];
                var spawner = spawners[i];

                spawner.timer += dt;
                if( spawner.timer >= 3.0f)
                {
                    spawner.timer = 0.0f;

                    var e = commandBuffer.CreateEntity(0,spawnArchetype);
                    commandBuffer.SetComponent<Translation>(0, e, new Translation
                    {
                        Value = new float3(0, 0, 0),
                    });
                }

                spawners[i] = spawner;
            }
        }
    }

    protected override void OnCreate()
    {
        // この辺りは変わらず
        m_query = GetEntityQuery( ComponentType.ReadOnly<Translation>(), ComponentType.ReadOnly<SpawnerComponent>());
        m_commandBufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();

        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
        m_spawnArchetype = em.CreateArchetype(typeof(Translation), typeof(LocalToWorld), typeof(RenderMesh), typeof(RenderBounds));
    }

    protected override JobHandle OnUpdate( JobHandle inputDeps)
    {
        var cmdBuffer = m_commandBufferSystem.CreateCommandBuffer().AsParallelWriter();

        var job = new SpawnJob
        {
            transHandle = GetComponentTypeHandle<Translation>(),
            spawnerHandle = GetComponentTypeHandle<SpawnerComponent>(),
            spawnArchetype = m_spawnArchetype,
            commandBuffer = cmdBuffer,
            dt = Time.DeltaTime,
        };

        inputDeps = job.Schedule(m_query, inputDeps);
        return inputDeps;
    }
}
```

ArchetypeChunkComponentTypeがリネームされ、ComponentTypeHandleになりました。



簡単な実装ですので変更点は少ないですが以上となります。