MVPパターンにUniRxを適用

## はじめに
こんにちは。今回はUnityにおける設計パターンの「MVPパターン」を実装してアウトプットという形で記事にしてみたいと思います。
この記事ではMVPの説明はメモ程度にとどめておきます。詳しいことを知りたい人は、参考にした記事を以下においておくので、そちらを参照ください。

* [Web出身のUnityエンジニアによる大規模ゲームの基盤設計](https://developers.cyberagent.co.jp/blog/archives/4262/)
* [【Unity】Model-View-(Reactive)Presenterパターンとは何なのか](https://qiita.com/toRisouP/items/5365936fc14c7e7eabf9)

## ざっくりMVPとは
このパターンを用いることで、Unity内で複雑になりがちなGUI周りの処理が綺麗に書けるようになります。「Model」「View」「Presenter」の頭文字をとってMVPモデルです。今回はオブザーバーにUniRxの「ReactiveProperty」を使用するので、MV(R)Pとも表現できますね。$\tiny{最近だとUniRxの代わりにR3というライブラリが使われることもあるそうで。}$
　
さておき、それぞれの役割は以下の通りです。

* Model
GUIに表示させるデータを保持する部分。例えばint型の残り時間であるとか。
PresenterのこともViewのことも知らない。独立している。
　
* View
GUI。UnityだとButtonとかSliderとかTextMeshProとか。
PresenterのこともModelのことも知らない。独立している。
　
* Presenter
ModelとViewの橋渡し役。
Modelの情報をViewに反映させるし、Viewで変更が行われたらModelに反映させる。

## MVPパターンの実装
BGM音量設定のUI部分をMVPで実装していこうと思います。
## Unity内のオブジェクト
Unity内で適当にUI部分とその他を配置しました。ヒエラルキーはこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877133/68b3fb7b-2226-acec-b1e3-6814c5ad1493.png)

CanvasにViewスクリプトをアタッチし、空っぽのゲームオブジェクトScriptにPresenterスクリプトをアタッチしています。ModelはMonobihaviourを継承しないピュアクラスですので、Unityエディタ上ではインスタンス化しません。

画面内のUIはこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877133/ee884246-fdfa-cc8c-df63-6601c2f3eac7.png)

画像内の+ボタンを押したら音量が上がり、数字が増える。-ボタンを押したら音量が下がり、数字が減る。この簡単な処理をMVPパターンで実装していきます。

## スクリプト
### Model
<details open><summary>Modelサンプルコード</summary>

```cs:Model
using UniRx;

namespace MVP_pettern
{
    public class Model
    {
        const int MAX_SPEED = 15;
        const int MIN_SPEED = 0;
    
        private ReactiveProperty<int> _speed;
        public IReadOnlyReactiveProperty<int> Speed
        { 
            get { return _speed; }
        }

        //コンストラクタ
        public Model()
        {
            _speed = new ReactiveProperty<int>(0);
        }

        //スピードのセット
        public void SetSpeed(int value)
        {
            value = Mathf.Clamp(value, MIN_SPEED, MAX_SPEED);
            _speed.Value = value;
        }
    }
}
```
</details>

スピードはプロパティでgetだけできるようにしています。setはメソッドを通しています。ここはお好みでプロパティからそのままsetできるようにしてもいいと思います。
 
そして、getで公開しているIReadOnlyReactivePropertyは、interfaceの継承を追っていけば分かると思いますが、Subscribeが出来るようになっています。publicとして他クラスに公開することで、Presenterから購読と読み取りだけが出来るようになるってわけですね。

### View
<details open><summary>Viewサンプルコード</summary>

```cs:View
using UnityEngine;
using System;
using TMPro;

namespace MVP_pettern
{
    public class View : MonoBehaviour
    {
        //テキスト
        [SerializeField] TextMeshProUGUI _speedText;

        //「+」ボタンのイベントリスナー
        public Action OnPlusButtonClickedListener;

        //「-」ボタンのイベントリスナー
        public Action OnMinusButtonClickedListener;

        //スピードが変わったときのメソッド
        public void OnSpeedChanged(int speed)
        {
            _speedText.text = speed.ToString();
        }

        //プラスボタンがクリックされたとき
        public void OnPlusButtonClicked()
        {
            if (OnPlusButtonClickedListener != null) { OnPlusButtonClickedListener(); }
        }

        //マイナスボタンがクリックされたとき
        public void OnMinusButtonClicked()
        {
            if (OnMinusButtonClickedListener != null) { OnMinusButtonClickedListener(); }
        }
    }
}
```
</details>

それぞれのボタンが押されたときのActionをpublicで宣言しています。
このActionに、Presenterがボタンを押されたときの挙動(メソッド)を購読します。

### Presenter
<details open><summary>Presenterサンプルコード</summary>

```cs:Presenter
using UnityEngine;
using UniRx;

namespace MVP_pettern
{
    public class Presenter : MonoBehaviour
    {
        [SerializeField] View _view;
        private Model _model;

        private void Start()
        {
            _model = new Model();

            SetEvents();
            Bind();
        }

        //Viewにボタンが押されたときのメソッドを購読
        private void SetEvents()
        {
            _view.OnPlusButtonClickedListener += OnPlusButtonClicked;
            _view.OnMinusButtonClickedListener += OnMinusButtonClicked;
        }

        //Modelにスピードが変わったときのメソッドを購読
        private void Bind()
        {
            _model.Speed
                .Subscribe(_view.OnSpeedChanged)
                .AddTo(this.gameObject);
        }

        private void OnPlusButtonClicked()
        {
            _model.SetSpeed(_model.Speed.Value + 1);
        }

        private void OnMinusButtonClicked()
        {
            _model.SetSpeed(_model.Speed.Value - 1); 
        }
    }
}

```
</details>

Modelが公開しているIReadOnlyReactivePropertyにスピードが変わったときのViewのメソッドを購読します。
また、Viewが公開しているActionに、それぞれModelのスピードを変えるメソッドを購読しています。

MVPパターンを用いることで、ModelとViewが独立した状態を実装することが出来ました。ちょっと複雑な構造ですが、ゲーム内のGUI周りの処理がごちゃごちゃするのをある程度防いでくれます。便利ですね。