UnityECS MonoBehaviourからイベントやデータを送る方法

# MonoBehaviourとの連携
UnityECSは素晴らしいフレームワークですが、UnityECSは現行のすべてのUnityの機能を完全には扱うことはできません。
前回から行っているAnimationについても既存のMonoBehaviourと連携しHybridアプローチを行う必要があります。

# GameObjectとEntityを紐づけ
例えばMonoBehaviour側が何かイベントを発行してそれに対してECSで処理をしたい場合を考えてみましょう。
MonoBehaviourからECS側に何か送る場合はあらかじめ`Component`に送りたいデータを提示して、`SetComponent`から送る方法や`AddComponent`でタグをつけて、タグが付いていたらその`System`で処理する方法があります。

いずれにしてもこのGameObjectに紐づけられているEntityを知ることが必要です。
まずGameObjectにEntityを格納するための`EntityHolder`コンポーネントを作成し、アタッチします。
```csharp
public class EntityHolder : MonoBehaviour
{
    public Entity MyEntity;
}
```
単純にEntityを保持するだけのクラスです。

アタッチされたGameObject
![スクリーンショット 2024-12-22 104115.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949075/5c433b7d-49cf-566e-7611-0bea9a380a46.png)

次にECS側からGameObjectに紐づけます。
これは以前`PlayerHybridSystem`でやったことの逆をすればいいわけです。
```diff_c_sharp
protected override void OnUpdate()
{
    EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);

    //Create
    foreach (var (hybridData, entity) in 
        SystemAPI.Query<CharacterHybridData>()
                 .WithNone<CharacterHybridLink>().WithEntityAccess())
    {
        // AuthoringでアタッチされたGameObjectを実体化する。
        GameObject tmpObject = GameObject.Instantiate(hybridData.MeshPrefab);
        Animator animator = tmpObject.GetComponent<Animator>();
       // その実体化されたGameObjectの`EntityHolder`を取得し
+       EntityHolder holder = tmpObject.GetComponent<EntityHolder>();
+       // entityを入れてやればよい
+       holder.MyEntity = entity;

        // こちらは以前やったもので、Entity側にGameObjectを紐づけている
        ecb.AddComponent(entity, new CharacterHybridLink
        {
            Object = tmpObject,
            Animator = animator,
        });
        ecb.AddComponent<CharacterHybridData>(entity);
    }
    ecb.Dispose();
}
```

ECS側のAuthoringの写真も一応載せます。
![スクリーンショット 2024-12-22 104427.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949075/2df5194f-a95a-f113-b7e8-b9501ab10528.png)
Authoringの`Character Authoring`の`Mesh Prefab`に先ほどアタッチしたGameObjectのプレファブを入れるわけです。

# MonoBehaviourでAddComponent,SetComponent
ここまでくればあとはMonoBehaviour側で`AddComponent`や`SetComponent`をやってしまえばいいわけです。

例えばメカニムの`StateMachineBehaviour`を使って呼び出してみる
```csharp
public class AttackStateBehaviour : StateMachineBehaviour
{
    Entity entity;
    //OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
        if(entity != null)
        {            
            var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
            // AddComponentでタグをつける
            manager.AddComponent<AttackState>(entity);
        }
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    //OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
        if(entity != null)
        {            
            var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
            // AddComponentでタグをつける
            manager.AddComponent<NormalState>(entity);
        }
    }

    // OnStateMove is called right after Animator.OnAnimatorMove()
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that processes and affects root motion
    //}

    // OnStateIK is called right after Animator.OnAnimatorIK()
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
}
```
これはメカニムのステートマシーンに入ったとき対象のEntityに`AttackState`というタグをつけて、ステートマシーンから出たときに`NormalState`というタグをつけているサンプルになります。
GameObject側から`EntityManager.〇〇Component`を呼ぶ場合は`World.DefaultGameObjectInjectionWorld.EntityManager`で`EntityManager`を取得することで呼ぶことができます。

それぞれのStateの処理をECSの`System`で行っています。
`SetComponent`の例ものせておきます。

```csharp
public class AttackStateBehaviour : StateMachineBehaviour
{
    Entity entity;
    //OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
        if(entity != null)
        {            
            var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
            // GetComponentで現在のコンポーネントの値を取得
            PlayerInputs inputs = manager.GetComponentData<PlayerInputs>(entity);
            // データを書き換えて
            inputs.FirePressed = true;
            // 再設定する
            manager.SetComponentData(entity, inputs);
        }
    }

    //OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
        if(entity != null)
        {            
            var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
            // 現在のコンポーネントのデータを参照しなくてもいい場合は新しくコンポーネントを作成しセットしてもいい
            // 同じコンポーネントにデータがある場合はそれも初期化されるので注意
            manager.SetComponentData(entity, new PlayerInputs
            {
                FirePressed = true,
            });
        }
    }
}
```

これでUnityECSとMonoBehaviourの連携をうまくすることができるようになります。