Unity公式が公開しているプログラミングパターンを読み取ってみた！～その12. MVP～

# はじめに
Unity公式がプログラミングパターンのデモを説明書と共に全12種公開していたので、1つずつ読み取っていきたいと思います！
英語の説明書でしたので間違った解釈をしているかもしれませんが、ご了承下さい！
最初の5種は、SOLID原則に沿ったデモとなっており、コードのみが公開されています。
残り7種は、Scene付きのデモが入っていますので、ZIPファイルをダウンロードして試してみてください。
今回は、12個目のMVPパターンについてです。

https://github.com/Unity-Technologies/game-programming-patterns-demo

https://resources.unity.com/games/level-up-your-code-with-game-programming-patterns

# 環境
OS : Windows10
Unity バージョン : 2021.3.8f1

# MVPパターンとは
データ(Model)、表示(View)、両者の中継(Presenter)に分けて処理を行う設計思想のことです。
Model⇔Vie間のやり取りは行われず、すべてPresenterを介してやり取りを行うため疎結合になります。
MVPパターンは以下のような流れになります。
1. Viewで何らかの入力を受け取る
2. Presenterに伝える
3. PresenterからModelに伝え計算などを行う
4. 計算結果をPresenterに伝える
5. PresenterからViewに伝えUIに反映する    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368729/c334b581-1178-db3d-fdf7-ccd0f641f619.png)

# デモ
デモでは、中心のボタンを押すと体力ゲージにダメージが反映されるようになっています。また、リセットボタンを押すと体力ゲージが元に戻ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368729/511b4b5e-ff91-9567-ad5c-bd84800b0871.png)

MVPパターンの説明で、何らか入力はViewが受け取ると説明しましたが、デモではPresenterが受けるようになっています。ユーザーの入力をViewで受け取るか、Presenterで受け取るかは状況によって変化させても良いのではないかと思います。
まず、ClickDamage.csでボタン入力を受け取ります。受け取ったら、PresenterのDamageメソッドを呼び出します。
```C#
public class ClickDamage : MonoBehaviour
{
    private Collider collider;
    private HealthPresenter healthPresenter;
    [SerializeField] private LayerMask layerToClick;
    [SerializeField] private int damageValue = 10;

    private void Start()
    {
        collider = GetComponent<Collider>();
        healthPresenter = GetComponent<HealthPresenter>();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, Mathf.Infinity, layerToClick))
            {
                // PresenterのDamageメソッドを呼び出す
                healthPresenter?.Damage(damageValue);
            }
        }
    }
}
```
HealthPresenter.cs(Presenter)は、Damageメソッド内でHealth.cs(Model)内のDecrementを呼び出します。
```C#
public class HealthPresenter : MonoBehaviour
{
    [Header("Model")]
    [SerializeField] Health health;

    [Header("View")]
    [SerializeField] Slider healthSlider;
    [SerializeField] Text healthText;

    private void Start()
    {
        if (health != null)
        {
            health.HealthChanged += OnHealthChanged;
        }
        Reset();
    }

    private void OnDestroy()
    {
        if (health != null)
        {
            health.HealthChanged -= OnHealthChanged;
        }
    }

    public void Damage(int amount)
    {
        health?.Decrement(amount);
    }

    public void Heal(int amount)
    {
        health?.Increment(amount);
    }

    public void Reset()
    {
        health?.Restore();
    }

    public void UpdateView()
    {
        if (health == null)
            return;

        if (healthSlider !=null && health.MaxHealth != 0)
        {
            healthSlider.value = (float) health.CurrentHealth / (float)health.MaxHealth;
        }

        if (healthText != null)
        {
            healthText.text = health.CurrentHealth.ToString();
        }
    }

    public void OnHealthChanged()
    {
        UpdateView();
    }
}
```
Health.cs(Model)内で体力の計算をしています。重量なのはModel内にeventを置いていることと、体力の増減の計算が終わったらそのUpdateHealth内で着火していることです。
これにより、PresenterのStart内で登録されてあるOnHealthChangedが走り、Viewに体力が反映されています。
一般的に、Viewもクラスを持っているのですが、このデモではUnityのSliderが使用されています。

```C#
public class Health : MonoBehaviour
{
    public event Action HealthChanged;

    private const int minHealth = 0;
    private const int maxHealth = 100;
    private int currentHealth;

    public int CurrentHealth { get => currentHealth; set => currentHealth = value; }
    public int MinHealth => minHealth;
    public int MaxHealth => maxHealth;

    public void Increment(int amount)
    {
        currentHealth += amount;
        currentHealth = Mathf.Clamp(currentHealth, minHealth, maxHealth);
        UpdateHealth();
    }

    public void Decrement(int amount)
    {
        currentHealth -= amount;
        currentHealth = Mathf.Clamp(currentHealth, minHealth, maxHealth);
        UpdateHealth();
    }

    public void Restore()
    {
        currentHealth = maxHealth;
        UpdateHealth();
    }

    public void UpdateHealth()
    {
        HealthChanged.Invoke();
    }
}
```

# まとめ
MVPパターンとは、データ(Model)、表示(View)、両者の中継(Presenter)に分けて処理を行う設計思想のことです。
使用することで疎結合になり可読性やテストがやりやすくなります。