【ScriptableObject】シングルトンを使わずにマルチシーンのイベント管理してみる

## はじめに
本記事は、[サムザップ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/sumzap) の12/21の記事です。
昨日の記事は[@kanasaki_kenzi](https://qiita.com/kanasaki_kenji)さんの[リマインドツールで少しでもストレスを減らそう](https://qiita.com/kanasaki_kenji/items/4d48cfce79c08e30b5a5)でした。

こちらの記事は以前チーム制作をしていた際にマルチシーンを活用したいと思ったが、シーン間のイベントやデータの受け渡しに困ったのでそちらを解決した紹介になります。

## 問題点
マルチシーンを活用しようと思うとイベントのアタッチや受け渡しができないという問題にあたりました。
![DefalutEvent.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634164/7f1199b5-41b1-38e1-494b-ab2413b9c614.gif)

シングルトンやStaticな変数を使うのも神クラスができそうでちょっとやだな～と思い、そこで、今回の主役であるScriptable Objectを使用することを考えました。

## ScriptableObjectとは

1. スクリプトインスタンスから独立している
2. 大量の共有データを保存できる
3. データが一つなので、メモリの節約につながる
4. エディター上から数値を変更できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634164/78742847-08e6-f1b1-b89a-de6f9ac2bb50.png)

## Eventとは

ここで明記しているEventとは**UnityEngine.Events**のことである登録した関数を指定のタイミングで
呼んでくれる機能です。プレイヤー死亡時にアニメーションやエフェクト・敵の挙動
ステートマシーンへのシーン遷移など複数のクラスに影響を与える時などすごく便利です！

## ScriptableObjectを使ったイベント

今回の本題。ScriptableObjectを使用したイベント管理システムです。
このシステムは2つのクラスがいります。

1. ScriptableObjectを継承したGameEventクラス
2. MonoBehaviourを継承したGameEventListenerクラス

####　**[コード例]** GameEvent ScriptableObject

```c#
[CreateAssetMenu]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new List<GameEventListener>();

    public void Raise()
    {
        for(int i = listeners.Count -1; i >= 0; i--)
            listeners[i].OnEventRaised();
    }

    public void RegisterListener(GameEventListener listener)
    {  
        listeners.Add(listener);
    }

    public void UnregisterListener(GameEventListener listener)
    {
        listeners.Remove(listener);
    }
}
```

####　**[コード例]** GameEventListener

```c#
using UnityEngine;
using UnityEngine.Events;

public class GameEventListener : MonoBehaviour
{
    public GameEvent Event;

    public UnityEvent Response;

    private void OnEnable()
    {
        Event.RegisterListener(this);
    }

    private void OnDisable()
    {
        Event.UnregisterListener(this);
    }

    public void OnEventRaised()
    {
        Response.Invoke();
    }
}
```

####　イベントの流れ

1. **GameEvent** から **Raise()** が呼ばれる
2. **GameEventListenerのリスト** へ **OnEventRaised()** 呼ばれる
3. イベントの発行

イベント作成
![MakeEvent.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634164/3d9e73a3-595b-cbd7-8028-a9c574f33f86.gif)

イベントリスナーへのアタッチ
![AddGameEventlistener.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634164/5e1aa32c-808f-f7a8-2b0a-66c232887046.gif)

イベントの設定
![SetEvent.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634164/1fa5bc95-e692-750e-fdf1-ae18048ea63b.gif)

イベントの呼び出し

```c#
public class PlayerController : MonoBehaviour
{
    [SerializeField, Header("プレイヤーステータス")]
    private StatusParam playerStatus = null;
    [SerializeField, Header("プレイヤー死亡イベント")]
    private GameEvent gameEvent = null;

    void Start()
    {
        // 値の変更イベント
        playerStatus.hp.mChanged += (data) =>
        {
            if (data <= 0)
            {
                // イベント呼び出し
                gameEvent.Raise();
            }
        };
    }
}
```

今回の問題点である同じシーンでしかイベントを配置できない問題ですが、ScriptableObjectが管理することによって スクリプトインスタンスから独立している特徴を活かしマルチシーンでのイベント管理を有効にできました。

## 最後に
今回のScriptableObjectの使用方法は正直作っていて驚きました。
なぜなら、ScriptableObject＝データとして 扱うといった固定概念があったため、Scriptable Objectをまるでシングルトンパターンの マネージャーのような扱い方ができることが衝撃的でした。
また、個人的に好きだった点としてイベントの作成が 視覚的でわかりやすい点です。
イベント管理というと便利だが、作っているときに複雑でわからなくなることもありますが、この方法は イベントを作成→購読者にアタッチ→関数の登録といった誰にでも理解できるところがいい点だと感じました。
最後まで読んでいただきありがとうございました。

明日は[@kida_hironari](https://qiita.com/kida_hironari)さんの記事です。