UnityのECS(Entity Component System)で雪の結晶を降らせる

# はじめに
UniteTokyo2018でECSに関する講演がありました。

* [【Unite 2018 Tokyo】C# Job SystemとECS（Entity Component System）解説 - SlideShare](https://www.slideshare.net/UnityTechnologiesJapan/unite-2018-tokyoc-job-systemecsentity-component-system)

この中でECSを使って雪を大量に降らせるというデモが行われていたので、今回自分でも実装に挑戦してみました。

![ezgif.com-optimize.gif](https://qiita-image-store.s3.amazonaws.com/0/55086/002faf30-e3df-aca7-f242-52dfea158818.gif)

ECSは今までのGameObject&Monobehaviorとは異なるデータ指向型によるアプローチです。
ECSってなに？という解説はこちらの記事が勉強になります。

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

# 準備
ECSを使うための準備です。

## Unityの準備
Unity2018.1をインストールします。
（今回はUnity2018.1.0f2で検証しています）

## ECSのインストール
次にプロジェクトを作るとAssetsと同じ階層にPackagesというフォルダがありその中のmanifest.jsonを書換えます。

manifest.json

```manifest.json
{
	"dependencies":{
		    "com.unity.incrementalcompiler": "0.0.38", 
        "com.unity.entities": "0.0.12-preview.1"
	},
	"testables": [
        "com.unity.collections",
        "com.unity.entities",
        "com.unity.jobs"
	],
	"registry": "https://staging-packages.unity.com"
}
```

ECSとは関係ないことですが、このPacakagesManagerの仕組みが入って、ExperimentalやBetaなどのお試し機能の導入が随分楽になりました。

## .NETレベル
さらにPlayerSettingsから.NETのバージョンを4.xにします。
<img width="354" alt="スクリーンショット 2018-05-14 3.33.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/55086/a58cd6e1-4e07-de4d-0f80-2706dac0786b.png">


## 雪の結晶モデル
今回はこちらのAssetに含まれる雪の結晶を使いました。

* [FREE Christmas Assets / Low Poly - Asset Store](https://assetstore.unity.com/packages/3d/props/free-christmas-assets-low-poly-13102)


# 実装
以下の2つのスクリプトを用意します。

SnowScrap.cs

```SnowScrap.cs
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.UI;

public class SnowStrap : MonoBehaviour 
{
    public Mesh snowMesh;
    public Material snowMaterial;
    public Text countText;
    public Text fpsText;
    
    public int step = 10; // 1フレームあたりに作る数
    
    // FPS
    private float m_updateInterval = 0.5f;
    private float m_accum;
    private int m_frames;
    private float m_timeleft;
    private float m_fps;
    
    // 雪結晶の数
    private int count = 0;

    private EntityManager entityManager;
    private EntityArchetype snowArchetype;

    // ECSで雪の結晶を作るエントリーポイント
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    private void Start()
    {
        entityManager = World.Active.GetOrCreateManager<EntityManager>();

        snowArchetype = entityManager.CreateArchetype(
            typeof(TransformMatrix),
            typeof(Position),
            typeof(Rotation),
            typeof(MeshInstanceRenderer)
        );
    }

    private void Update()
    {
        if (Input.GetKey(KeyCode.Space))
        {
            for (int i = 0; i < step; i++)
            {
                var snow = entityManager.CreateEntity(snowArchetype);
        
                entityManager.SetSharedComponentData(snow, new MeshInstanceRenderer
                {
                    mesh = snowMesh,
                    material = snowMaterial,
                });
        
                entityManager.SetComponentData(snow, new Position
                {
                    Value = new float3(Random.Range(-20.0f, 20.0f), 20, Random.Range(-20.0f, 20.0f))
                });
                entityManager.SetComponentData(snow, new Rotation
                {
                    Value = Quaternion.Euler(0f, Random.Range(0.0f, 180.0f), 90f)
                });

                count++;
                
            }
            
            countText.text = count.ToString();
            
            // FPS
            m_timeleft -= Time.deltaTime;
            m_accum += Time.timeScale / Time.deltaTime;
            m_frames++;

            if ( 0 < m_timeleft ) return;

            m_fps = m_accum / m_frames;
            m_timeleft = m_updateInterval;
            m_accum = 0;
            m_frames = 0;

            fpsText.text = "FPS: " + m_fps.ToString("f2");
        }
    }
}
```

SnowMovementSystem.cs

```SnowMovementSystem.cs
using Unity.Entities;
using Unity.Transforms;

public class SnowMovementSystem : ComponentSystem {
    
    // 落下速度
    private float delta = 0.1f;

    public struct SnowGroup
    {
        public ComponentDataArray<Position> snowPostion;
        public int Length;
    }

    [Inject] private SnowGroup _snowGroup;
    
    protected override void OnUpdate()
    {
        for (int i = 0; i < _snowGroup.Length; i++)
        {
            var newPos = _snowGroup.snowPostion[i];
            newPos.Value.y -= delta;
            _snowGroup.snowPostion[i] = newPos;
        }
    }
}

```

本来は雪の結晶をレンダリングするシステムと雪の結晶を動かすシステムの2つのシステムを作る必要がありますが、レンダリングに関するシステムはUnity側で用意してくれている MeshInstanceRenderSystemというものがあるのでそれを使っています。  
ComponetDataもPositionなどあらかじめUnity側で用意してくれているので自分では作っていません。

<img width="512" alt="20180513012513.png (174.4 kB)" src="https://img.esa.io/uploads/production/attachments/7618/2018/05/13/11592/2e11de53-f329-45d8-9c56-29eb95e9ca13.png">

空のゲームオブジェクトを作成してSnowScrap.csをコンポーネントとして貼り付けます。
publicフィールドのプロパティを割り当てます。
ここで気をつけなければいけないのがMaterialの設定です。

<img width="203" alt="20180513012942.png" src="https://qiita-image-store.s3.amazonaws.com/0/55086/2f9a462b-a85b-fb46-1928-7fa7cf8d63ec.png">


Enable GPU Instancingにチェックを入れなければいけません。
MeshInstanceRenderSystemの内部でGPU Instancing使っているようです。

最後に確認です。
EditorのRunボタンを押してシーンをスタートさせます。
Unity -> Window -> EntityDebuggerを開きます。
ECSのEntityはヒエラルキーには表示されないのでこちらを使って確認します。

![20180513014716.png (67.7 kB)](https://img.esa.io/uploads/production/attachments/7618/2018/05/13/11592/68792c60-3e61-486d-80f1-11301fe15abf.png)

SnowMovementSystemとMeshInstanceRenderSystemがあるのが分かります。
システムをどうさせるのに必要なComponetDataやEntityの詳細(transformなど)を確認することができます。




# サンプルプロジェクト
サンプルプロジェクトGithubに上げました。

* https://github.com/yuri-hattori/EcsSnowProject


# まとめ
ECS楽しい、凄い！

これくらい簡単なものであればシステムやデータコンポーネントの設計でそれほど迷うことはありませんが、もう少し大きめのことをやろうとすると設計の難易度は格段に上がりそうで慣れが必要になってきそうです。
レンダリングのようにUnity側で用意してくれるシステムが充実してくれることにも期待です。

# 参考
英語のブログ記事ですが役に立ちました。

* [Unity - ECS Tutorial (Entity Component System) ( Blog + Video ) | WDTutorials](https://www.wdtutorials.com/unity/ecs-tutorial-entity-component-system/#.Wvjwx2jRCM3)

本家のECSサンプルです。こちらも参考になります。

* https://github.com/Unity-Technologies/EntityComponentSystemSamples

FPSの求め方はこちらを参考にしています。

* [【Unity】ECS を利用して 10,000 体のスプライトを描画するサンプル「ecs-instanced-sprite-renderer」紹介 - コガネブログ](http://baba-s.hatenablog.com/entry/2018/04/17/090000)