【Unity】EntityとGameObjectを連携したい

# 自己紹介
明後日(5/26)にゲームジャムを控え、大興奮が止まらない大学生
## 記事の参考動画

https://t.co/UoZ28c0b4N

# DOTS使ってますか？
Unity DOTS楽しいですよね。この前、Ver.1.0.8がリリースされてついにExperimentalが外れましたね。
一方で、機能が足りないところはしばしば…
## 今回は
今回は、機能が足りないところを既存のGameObject関連の機能と連携させてみたいと思います。
簡易的なゲームにも必須なUI（Unity UI）を連携させますが、ほとんど同じです。
>タイトルは分かりやすく、EntityとGameObjectの連携と書きましたが、正確にはMonoBehaviourとECSの連携といったほうが正確かもしれません。

# MonoBehaviour → ECS
これは非常に簡単に行えます。
## MonoBehaviour側
```C#:Button.cs
public class Button : MonoBehaviour
{
    private EntityManager entityManager;
    private Entity entity;
    private async void OnEnable()
    {
        entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

        await UniTask.Delay(100);
        //Entityを取得
        entity = entityManager.CreateEntityQuery(typeof(Spawner)).GetSingletonEntity();
    }

    public void OnButtonClick()
    {
        entityManager.AddComponent(entity, typeof(CharacterSpawnTag));
    }
}
```
この`OnButtonClick`をuGUIから呼び出せば、EntityにComponentを追加しそれをSystemで拾ってあげれば…連携完了！

一方で、この方法ではMonoBehaviourから数値などを含むコンポーネントを追加できない問題があります。
この`AddComponent`は`Baker<T>`を継承したクラスで利用できるものとは異なるため、数値などを渡す方法は異なります。その方法は`AddComponent`したのち、`SetComponent`メソッドで変更できます。
なぜ、直接的に指定できないのかは不明です。もしかしたら、直接指定できるのかもしれませんが、私が調べた限りは無理でした。
### スクリプトの詳細解説
まずは7行目、Entityを管理するEntityManagerを取得します。
これを利用してEntityにコンポーネントを張り付けていきます。
```C#
entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
```
次にEntityManagerから対象となるEntityを取得します。
今回は`Spawner`というコンポーネントがついているEntityを１つ取得しています。
この`GetSingletonEntity`メソッドでは対象となりうるEntityが複数個あるとエラーとなりますので気を付けてください。

その前に、`UniTask`という外部のパッケージで少し遅延を発生させています。
これは、ECSの初期化が完了してからEntityを取得しなければエラーとなるからです。
```C#
   await UniTask.Delay(100);
   //Entityを取得
   entity = entityManager.CreateEntityQuery(typeof(Spawner)).GetSingletonEntity();
```
最後に、`OnButtonClick`からAddComponentで取得したEntityにコンポーネントをつけます。
今回は`CharacterSpawnTag`というコンポーネントデータを作成しEntityに張り付けています。
## ECSの設定
```C#:CharacterSpawnSystem.cs
[BurstCompile]
public partial struct CharacterSpawnSystem : ISystem
{
    private void OnCreate(ref SystemState state)
    {
        
    }
    private void OnDestroy(ref SystemState state) 
    {

    }
    public void OnUpdate(ref SystemState state)
    {
        var ecb = new EntityCommandBuffer(Allocator.Temp);
        foreach(var (_, characterObj, entity) in SystemAPI.Query<CharacterSpawnTag, CharacterGameObjectsData>().WithEntityAccess())
        {
            //何か処理を書く
            ecb.RemoveComponent<CharacterSpawnTag>(entity);
        }
    }
}

```
こちらがECS側のスクリプト（一部略）です。
内容は、`EntityCommandBuffer`を取得しそのEntityを使って何かを行い、その後MonoBehaviour側で指定したコンポーネントを外せば一度だけ処理されるということになりました。
今回はボタンを利用しましたが、それ以外の`UnityEvent`とも連携出来ることになります。
# ECS → MonoBehaviour
こちらは、MonoBehaviourの`Action`をECSから呼び出すというイメージです。
## ECS側の設定
```C#:ECSToMono.cs
using System;
using Unity.Entities;

public partial class ECSToMono : SystemBase
{
    public Action<int> action;
    protected override void OnUpdate()
    {
        action?.Invoke(100);
    }
}
```
このように`Action`を定義し、ECS側で発火（`Ivoke`）させます。今回はOnUpdateの中で発火していますが、このようなことはあまりないかもしれません。
また、Actionのジェネリクスは指定するメソッドの引数です。

次はMonoBehaviour側のスクリプトです。
```C#:ECSToMonoReceiver.cs
using Unity.Entities;
using UnityEngine;

public class ECSToMonoReceiver : MonoBehaviour
{
    private void OnEnable()
    {
        var system = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<ECSToMono>();
        system.action += TestAction;
    }
    private void OnDisable()
    {
        var system = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<ECSToMono>();
        system.action -= TestAction;
    }
    public void TestAction(int i)
    {
        Debug.Log(i);
    }
}
```
ここのキーポイントは、システムの取得方法です。
`World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<T>()`でsystemを取得できます。
ここから先ほど書いた`action`というフィールドにアクセスし関数を代入します。
なお、これは`ISystem`ではMonoBehaviourから取得することが出来なさそうだったので`SystemBase`を使う必要がありそうです。もし、`ISystem`で取得できそうだったら教えてほしいです。

これで、コンソールに100と表示されます。
ECS→MonoBehaviourの連携ができました！

# まとめ
MonoBehaviourからECSは
- コンポーネントをMonoBehabiourから設定
- それをSytemで検知しデータを取得

という手順でした。

ECSからMonoBehaviourは
- Action型の変数をSystemに定義
- そのAction型の変数にMonoBehaviourに定義したメソッドを代入

という手順です。

いい感じに設定できましたね。やったね。
# さいごに
ほかにもDOTS（主にECS）の記事を書いていますので良かったら見てください。
ゲームジャム頑張るぞー！！