はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】unity1week「あける」でクラス設計してみたの補足(特にMV(R)Pパターン)

はじめに

先日noteの方にこのような記事を書きました。

note.com

正直Unityちゃんデスクトップフィギュアが欲しい*1という不純な動機で書き始めましたが、想像以上に多くの方から反応をいただけて嬉しかったです。

今回はこちらの記事で紹介仕切れなかったクラス設計に関することを書いていこうと思います。

UIの表示について

このゲームにおけるUI表示はほぼ全てMV(R)Pパターンを用いて作成されています。

まだMV(R)Pパターンを知らないよという方は以下のスライドをみてみると良いと思います。

www.slideshare.net

また今回作成したゲームで実際に活用した一例を紹介させてください。

設定画面の例

f:id:hanaaaaaachiru:20210104161655g:plain
設定画面

このスライダーはMV(R)Pパターンが用いて作りました。

using UniRx;
using UnityEngine;
using UnityEngine.UI;

public class SliderPresenter : MonoBehaviour
{
    // View
    // Viewクラスを作る場合もあるが,規模感によりけり。
    [SerializeField] private Slider _slider;

    // Model
    // ここの実装方法は無数に考えられる。こちらも規模感と要相談。
    // 1. MonoBehavoirを継承してコンポーネント化      ([SerializeField] 等)
    // 2. ScriptableObjectを使う                   ([SerializeField] 等)
    // 3. Singletonにする                          (Model.Instance)
    // 4. ServiceLocator, DIフレームワークを使う      (依存性を外部から注入する)
    // etc...
    [SerializeField] private Config _config;

    private void Start()
    {
        _slider.OnValueChangedAsObservable()
            .DistinctUntilChanged()
            .Subscribe(value => _config.ChangeMouseSensitivity(value))
            .AddTo(this);

        _config.MouseSensitivity
            .Subscribe(value => _slider.value = value)
            .AddTo(this);
    }
}

// Modelクラス
// 前述の通り,様々な実装が考えられる。
public class Config : MonoBehaviour
{
    private ReactiveProperty<float> _mouseSensitivity = new ReactiveProperty<float>();
    public IReadOnlyReactiveProperty<float> MouseSensitivity => _mouseSensitivity;

    /// <summary>
    /// マウス感度を変更する
    /// </summary>
    public void ChangeMouseSensitivity(float value)
        => _mouseSensitivity.Value = value;
}

Viewの扱い

今回はUnityEngine.UI.SliderViewとして扱いました。
docs.unity3d.com

ただViewクラスを自分で定義するパターンもありますが、ここら辺は規模感(大人数で作業したり)とかで変えると良いかもしれません。
developers.cyberagent.co.jp

Modelの扱い

Modelをどのように定義するかは無数に方法があると思います。

  1. MonoBehavourを継承するパターン
  2. ScriptableObjectを継承するパターン
  3. Singletonにするパターン(SingletonMonoBehavoirを継承する等)
  4. 依存性注入するパターン

開発の規模感によりどれを選ぶ可能性もあると思いますが,テストの事を考えると依存性注入するパターンが一番良さそうな気もします。(個人的な意見)

ただ依存性注入するにも色々パターンがあります。

  1. サービスロケーターを使うパターン
  2. DIフレームワークを使うパターン

もし依存性注入するならインターフェイスor抽象クラスを定義してあげるとモックを使ったテストが行えます。

ScriptableObjectを用いたMV(R)P

実は今回作成したゲームでは、ScsriptableObjectを継承するパターンを利用しています。

これは結構トリッキーな使い方で、こちらの記事を参考に自分で考えたやり方なのですが割と好みな手法です。
forpro.unity3d.jp

// Modelクラス
[CreateAssetMenu()]
public class Config : ScriptableObject, ISerializationCallbackReceiver
{
    private float _mouseSensitivity;

    private ReactiveProperty<float> _reactiveMouseSensitivity;
    public IReadOnlyReactiveProperty<float> MouseSensitivity => _reactiveMouseSensitivity;

    public void OnAfterDeserialize()
    {
        _reactiveMouseSensitivity = new ReactiveProperty<float>(_mouseSensitivity);
    }

    public void OnBeforeSerialize()
    {
    }

    /// <summary>
    /// マウス感度を変更する
    /// </summary>
    public void ChangeMouseSensitivity(float value)
        => _reactiveMouseSensitivity.Value = value;
}


この手法はシーンの遷移とかも一切考えずにでき,いろんなデータの初期値を簡単に切り替えられる(アセットを複数作れば良い)のでメリットは多いと思います。

さいごに

色々とMV(R)Pパターンについて書きましたが、正直どれが正解とかは特にないとは思います。

規模感であったり,開発メンバーの技術力であったりと考慮して自分なりの結論が出せればOKではないでしょうか。

ではまた。