修羅のためのUnity ECS学習（1）(2019年04月の時点、Unity2019.1.0b5)

## 諸注意
* **2019.1.0b5以降がベースになります。Unity2018をお使いの方は参考にならないと思います。(以下の公式サンプルが2019.1.0b5使ってます。)**
* **記事を読む前に、 https://github.com/Unity-Technologies/EntityComponentSystemSamples のサンプルを必ず目を通してください。** Unity ECSを学ぶにはソースコードを読むのが一番早いです。 **サンプルに目を通さずに、(この記事も含めて)ネット上にある記事のコードを参考にすると、新旧のコードの書き方の違いに悩まされます。**
* **この記事は2019.04.06 に書かれました。** 記事に書かれている内容は **今後の開発によって非推奨(deprecated)になる可能性があります。**
* ("ECS"タグを使っていないのは、AWSのECSとかぶっているためです。)

## 記事の内容
* 2019年4月時点のECSのサンプルコードを雑に紐解く
* どんなコードが使われているかを紹介(HelloCube_02の内容がメイン)
* Spawnerについては次回の予定(HelloCube_06の内容を予定)

## はじめに
4月を開始してぐらいから、UnityのEntityComponentSystem(ECS)を改めて勉強するために、いろいろネット上の記事を調べていましたが、2018年から現在までにかなり処理系統が変更されているようで、今どんなコードが使えて、どんなコードが使えないのかを整理するため、この記事を書きました。

## そもそもUnity ECSとは何か
これについてはテラシュールブログ様の記事を読むのが良いです。

[【Unity】Unity 2018のEntity Component System（通称ECS）について（１）- テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2018/03/25/180203)
[【Unity】Unity 2018のEntity Component System（通称ECS）について（２）- テラシュールブログ]
(http://tsubakit1.hateblo.jp/entry/2018/04/02/233106)

すごく雑に言うと、**「Monobehaviourを継承したクラスの`Update()`内で数千数万のGameObjectやComponentを直接操作するのはやめろ。代わりにメインスレッド外でGameObjectやComponentっぽいものを扱えるようにしたのでこっち(ECS)を使え」** という感じです。

## サンプルで登場するクラスやコードたち

各要素の単位はコンポーネントと呼ばれているようなので、ここからはコンポーネントと呼びます。(`UnityEngine.Component`とは異なるので注意です。)

* **IComponentData**   
自分で定義できるコンポーネントです。Transform以外のコンポーネントを用意するときに使用します。 **位置情報(Translation)回転(Rotation)などのTransformに関連するコンポーネントは`Unity.Transforms`内で既に用意されているので、Transform関係の処理は自分で定義しなくて良いです。** 以下は公式サンプルのソースです。
(共有データっぽいのにIComponentDataが使用されています。ISharedComponentDataどこに行った)

```c#:RotationSpeed.cs
using System;
using Unity.Entities;

namespace Samples.HelloCube_02
{
    [Serializable]
    public struct RotationSpeed : IComponentData
    {
        public float RadiansPerSecond;
    }
}
```

* **ComponentSystem(ForEach使用時), JobComponentSystem,(JobSystem使用時)**   
コンポーネントに対する処理を定義するためのクラスです。`OnUpdate()` 内に具体的な処理を記述します。
以下は公式サンプルのソースのうち、ジョブの定義に**IJobProcessComponentData**を使ったものです。 (※0.0.12-preview.30から **IJobForEach**にリネームされるようです)   
**【1.`OnUpdate()`内でジョブを初期化】、【2.ジョブをスケジュール】、【3.別スレッドでジョブ実行】**というループの処理になっているようです。(ループ実行は **IJobProcessComponentData** のみかもしれません。)   
それぞれの元のソースコードは以下のGitHubのコードを参照してください。(以下の引用ではBurstCompileのアトリビュートを外しています。)　**ForEachは結局メインスレッド内で計算処理をしているため、処理を効率化したい場合は使わないほうがよさそうです。**   

[**【HelloCube_01_ForEach/RotationSpeedSystem.cs】**](https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Samples/Assets/HelloECS/HelloCube_01_ForEach/RotationSpeedSystem.cs)
[**【HelloCube_02_IJobProcessComponentData/RotationSpeedSystem.cs】**](
https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Samples/Assets/HelloECS/HelloCube_02_IJobForEach/RotationSpeedSystem.cs)
[**【HelloCube_03_IJobChunk/RotationSpeedSystem.cs】**](
https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Samples/Assets/HelloECS/HelloCube_03_IJobChunk/RotationSpeedSystem.cs)

```c#:HelloCube_02_IJobProcessComponentData/RotationSpeedSystem.cs
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace Samples.HelloCube_02
{
    public class RotationSpeedSystem : JobComponentSystem
    {
        struct RotationSpeedJob : IJobProcessComponentData<Rotation, RotationSpeed>
        {
            public float DeltaTime;
    
            public void Execute(
                ref Rotation rotation,
                [ReadOnly] ref RotationSpeed rotSpeed
            )
            {
                rotation.Value = 
                    math.mul(
                        math.normalize(rotation.Value),
                        quaternion.AxisAngle(
                            math.up(),
                            rotSpeed.RadiansPerSecond * DeltaTime
                        )
                    );
            }
        }
    
        protected override JobHandle OnUpdate(JobHandle inputDependencies)
        {
            var job = new RotationSpeedJob()
            {
                DeltaTime = Time.deltaTime
            };
    
            return job.Schedule(this, inputDependencies);
        }
    }
}
```

* **IConvertGameObjectToEntity**   
**ECSのコンポーネントは直接作成するのではなく、エンティティ(Entity)という実体の中のコンポーネントとして登録します。(GameObjectと似た構造？)**   
EntityはEntityManagerから直接作成する方法と、GameObjectやPrefabから変換して用意する方法(Hybrid)があります。**IConvertGameObjectToEntity**は後者の方法です。(ほかにもやり方があれば、誰か教えてください。)   
変換対象のGameObjectに**IConvertGameObjectToEntity**を継承したスクリプトを追加します。 **注意点として、GameObjectからEntityに変換する場合、そのGameObjectに別途ConvertToEntityのスクリプトを手動で追加する必要があります。**(Prefabから作成する場合も同様に、Entityを作成しているGameObjectにConvertToEntityのスクリプトを追加しておく必要があります。Prefab内にはConvertToEntityは必要ありません。その他、別のシーンのGameObjectから作成する方法もありますが、割愛します。)   
**ゲーム実行時にEntityに変換されます。変換後は対象のGameObjectは通常の管理下から外れ、Hierarchyから見えなくなるようです。**

```c#:HelloCube_02_IJobProcessComponentData/RotationSpeedProxy.cs
using System;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

namespace Samples.HelloCube_02
{
    [RequiresEntityConversion]
    public class RotationSpeedProxy : MonoBehaviour, IConvertGameObjectToEntity
    {
        public float DegreesPerSecond = 360;
        
        public void Convert(
            Entity entity,
            EntityManager dstManager,
            GameObjectConversionSystem conversionSystem
         )
         {
            var data = new RotationSpeed
            {
                RadiansPerSecond = math.radians(DegreesPerSecond)
            };
            dstManager.AddComponentData(entity, data);
        }
    }
}
```

![rotationcube1.jpg](https://qiita-image-store.s3.amazonaws.com/0/58376/c0fac095-f6e7-9c42-4210-02d3ff7a34f7.jpeg)

サンプルを実行すると、キューブが回転します。

![rotationcube2.jpg](https://qiita-image-store.s3.amazonaws.com/0/58376/306fd88f-9470-4a96-fa40-987bcb345562.jpeg)

## (次回予定)Spawnerについて

上記のコードで最低限ECSを体験することができますが、実際はPrefabからEntityを作成するためのSpawnerクラスを作成するほうが便利に使えるようです。
(GameObjectをあらかじめ数千個用意するのは現実的ではないので・・・)
動作的に理解できてない箇所もあるので、次回があればSpawnerクラスについて紹介したいと思います。