Unity ECSでシングルトンをInputActionsを用いた操作で変更する

Unity 6000.0.0f1
Entities 1.3.5


# 何をするか
Unity Entitiysでシングルトンを作成し、InputActionsを用いたユーザの操作で状態を変更させる処理です


# InputActionsとは
様々な入力デバイスを抽象的に扱えるようにする仕組みです。
例えば「攻撃」という操作をする際に、キーボードなら「スペース」、ゲームパットなら「A」ボタンというように、さまざまな入力に対応しなければなりませんが、InputActionsはそこら辺をまとめてくれる機能になります。

## InputActionsの準備 
Unity のメインメニューから Assets > Create > Input Actions を選択すると以下のような物が作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78379/08a44834-6caf-ad5a-83c1-b8341aeb6cac.png)

次にインスペクターの"Generate C# Class"にチェックマークを押し、Applyをクリック。
(Class File等は好きなもので)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78379/27e33356-966f-610e-0d1a-d8c36d41d54e.png)

この操作によってInputActionsのC#のコードが作成されました。

使い方の例
```C#
private GlobalInputActions inputActions; // 先ほど作成したクラスの変数

void OnCreate()
{
    inputActions = new GlobalInputActions();
    inputActions.Enable();
}

void OnUpdate() {
    if (inputActions.Player.Attack.WasPressedThisFrame()) {
        Debug.Log($"現在のフレームで攻撃ボタンが押されました")
    }
}
```

これでInputActionsを使う準備が整いました


# Unity Eneitiyでシングルトンを用意

・まずはシングルトンにするコンポーネントを用意
```C#
    [Serializable]
    public struct GamePhaseComponent : IComponentData
    {
        public Phase CurrentPhase;
    }

    public enum Phase
    {
        Main,
        End
    }

```

・次にこれをシーンにベイクするAuthoringを用意
```C#
    [DisallowMultipleComponent] // 2つ以上同じコンポーネントをアタッチできないように
    public class GamePhaseAuthoring : MonoBehaviour
    {
        public class Baker : Baker<GamePhaseAuthoring>
        {
            public override void Bake(GamePhaseAuthoring authoring)
            {
                var entity = GetEntity(TransformUsageFlags.None);
                AddComponent(entity, new GamePhaseComponent()); // コンポーネントを追加
            }
        }
    }
```

・Authoringをサブクラスに追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78379/0fffaa28-e62c-f1de-45fd-3a560a687fd4.png)

・InputActionsで切り替えるシステムを追加

```C#
    public partial class PhaseSwitchSystem : SystemBase
    {
        private GlobalInputActions inputActions;

        protected override void OnCreate()
        {
            base.OnCreate();
            inputActions = new GlobalInputActions();
            inputActions.Enable();
        }

        protected override void OnUpdate()
        {
            if (inputActions.Player.Attack.WasPressedThisFrame() && SystemAPI.HasSingleton<GamePhaseComponent>())
            {
                Entities.ForEach((ref GamePhaseComponent phaseComponent) =>
                {
                    Phase previousPhase = phaseComponent.CurrentPhase;

                    // Cycle through phases on click
                    phaseComponent.CurrentPhase = phaseComponent.CurrentPhase switch
                    {
                        Phase.Main => Phase.End,
                        Phase.End => Phase.Main,
                        _ => phaseComponent.CurrentPhase
                    };

                    if (previousPhase != phaseComponent.CurrentPhase)
                    {
                        Debug.Log($"{previousPhase} から {phaseComponent.CurrentPhase}に切り替わりました");
                    }
                }).Run();
            }
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            inputActions.Disable();
        }
    }

```
## 補足
Singletonが常に一つになるようにプログラム側からは保証できなさそうです。(多分)
なので、シングルトンになるクラスをサブクラスに追加する際には常に一つだけになるように気をつけましょう。

## 主な関数
シングルトンを利用する際に主に使う関数です
| 関数名 | 機能
|:-:|:-:
|GetSingletonEntity|シングルトンのコンポーネントがあるEntityを取得|
|GetSingleton|シングルトンのコンポーネントを取得|
|TryGetSingleton|エンティティがワールド内に1つだけ存在する場合はTrueを返す|
|HasSingleton|一致するシングルトンが1つ見つかった場合はTrueを返す|