7
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

【Unity(C#)】MV(R)Pパターンで作ってみる

2021/04/25 追記
こちらの記事見返してて思ったんですが、
Sliderの値をReactivePropertyに代入して公開するのではなくて、
Sliderそのものを公開すればいいのでは?ってなりました。
以下の記事ではそうしてます。
【参考リンク】:【Unity(C#)】UniRxとExtenjectでMV(R)Pやってみる

デモ

早速ですがデモです。
Sliderの値を変更するとCubeがそれぞれの軸を基準に回転します。

MVP_Sample.gif

このコード自体は簡単ですが、一つのコードにべた書きするとロジックやUIが絡み合ってくるので
クラスが肥大化し、それぞれの役割を差し替えたりテストしたりが難しくなります。

そこでMV(R)Pパターンを使います。

MV(R)Pとは

UniRxを使ったデザインパターンです。
Model、View、Presenterがそれぞれ役割を持ちます。

Model View Presenter
ロジック 見た目 ロジックと見た目を繋ぐ

詳細な説明は最後に参考リンクを貼ってます。

PresenterはModelとViewを繋ぎ、
ModelとViewがお互いに依存関係のない状態を作り出せます。

クラス図にすると下記です。
MVP_Image.PNG

正確には、ViewとModelの変更がReactiveに互いに反映されるつくりのことを指すようですが、
今回は下記のような一方通行の変更通知パターンを実際に作りながら学びます。
・Viewの変更→Presenterに通知→Modelに反映

逆に下記のようなパターンは参考リンクの図がイメージしやすいと思います。
・Modelの変更→Presenterに通知→Viewに反映
【参考リンク】:プログラミング MVPパターン ~理論編①~

バージョン情報

Unity 2019.4.8f1
UniRx 7.1.0

Model

まずはModelを作ります。Cubeを回転させる というロジックを持ちます。
Viewの存在は知りません。

using UnityEngine;

namespace Ono.MVP.Model
{
    /// <summary>
    /// ビジネスロジックを持つモデルクラス
    /// キューブを回転させる
    /// キューブにアタッチ
    /// </summary>
    public class CubeRotationModel : MonoBehaviour
    {
        /// <summary>
        /// インスタンス
        /// </summary>
        public static CubeRotationModel Instance;

        private void Awake()
        {
            Instance = this;
        }

        /// <summary>
        /// 与えられたパラメータに応じてX軸方向にキューブを回転
        /// </summary>
        /// <param name="x">X軸回転</param>
        public void SetRotationX(float x)
        {
            var rot = Quaternion.AngleAxis(x, Vector3.right);
            transform.rotation =  rot;
        }

        /// <summary>
        /// 与えられたパラメータに応じてY軸方向にキューブを回転
        /// </summary>
        /// <param name="y">X軸回転</param>
        public void SetRotationY(float y)
        {
            var rot = Quaternion.AngleAxis(y, Vector3.up);
            transform.rotation =  rot;
        }

        /// <summary>
        /// 与えられたパラメータに応じてZ軸方向にキューブを回転
        /// </summary>
        /// <param name="z">Z軸回転</param>
        public void SetRotationZ(float z)
        {
            var rot = Quaternion.AngleAxis(z, Vector3.forward);
            transform.rotation =  rot;
        }
    }
}

Model内の公開された機能(パブリックなメソッド)はPresenterにて呼び出します。
Model内では使いません。

View

ViewはSliderの変更をPresenterに通知する役割を持ちます。
Modelの存在は知りません。

using UniRx;
using UnityEngine;
using UnityEngine.UI;

namespace Ono.MVP.View
{
    /// <summary>
    /// SliderのViewを担うクラス
    /// </summary>
    public class SliderView : MonoBehaviour
    {
        [SerializeField] private Slider _sliderX, _sliderY, _sliderZ;
        [SerializeField] private Text _textX, _textY, _textZ;

        /// <summary>
        /// X軸操作のSlider
        /// 購読機能のみ外部に公開
        /// </summary>
        public IReadOnlyReactiveProperty<float> SliderValueRP_X => _floatReactivePropertyX;
        private readonly FloatReactiveProperty _floatReactivePropertyX = new FloatReactiveProperty();

        /// <summary>
        /// Y軸操作のSlider
        /// 購読機能のみ外部に公開
        /// </summary>
        public IReadOnlyReactiveProperty<float> SliderValueRP_Y => _floatReactivePropertyY;
        private readonly FloatReactiveProperty _floatReactivePropertyY = new FloatReactiveProperty();

        /// <summary>
        /// Z軸操作のSlider
        /// 購読機能のみ外部に公開
        /// </summary>
        public IReadOnlyReactiveProperty<float> SliderValueRP_Z => _floatReactivePropertyZ;
        private readonly FloatReactiveProperty _floatReactivePropertyZ = new FloatReactiveProperty();

        void Start()
        {
            //X軸操作用Sliderの値の変更を監視
            _sliderX.OnValueChangedAsObservable()
                .DistinctUntilChanged()
                .Subscribe(value => { OnValueChange(value, _floatReactivePropertyX, _textX); })
                .AddTo(this);

            //Y軸操作用Sliderの値の変更を監視
            _sliderY.OnValueChangedAsObservable()
                .DistinctUntilChanged()
                .Subscribe(value => { OnValueChange(value, _floatReactivePropertyY, _textY); })
                .AddTo(this);

            //Z軸操作用Sliderの値の変更を監視
            _sliderZ.OnValueChangedAsObservable()
                .DistinctUntilChanged()
                .Subscribe(value => { OnValueChange(value, _floatReactivePropertyZ, _textZ); })
                .AddTo(this);
        }

        /// <summary>
        /// Sliderの値変更時の処理
        /// </summary>
        /// <param name="value">Sliderの値</param>
        /// <param name="floatReactiveProperty">値を更新をしたいRP</param>
        /// <param name="valueText">更新するテキスト</param>
        private void OnValueChange(float value, FloatReactiveProperty floatReactiveProperty, Text valueText)
        {
            //値の整形
            var arrangeValue = Mathf.Floor((value - 0.5f) * 100) / 100 * 360;
            //値の更新
            floatReactiveProperty.Value = arrangeValue;
            //テキストに値を反映
            valueText.text = arrangeValue.ToString();
        }
    }
}

ReactivePropertyの購読機能のみ外部に公開します。
これによりPresenter側で通知を受け取ることができます。

Presenter

ModelとViewを繋ぐPresenterです。
Model、Viewの存在を知っています。

using Ono.MVP.Model;
using Ono.MVP.View;
using UniRx;
using UnityEngine;

namespace Ono.MVP.Presenter
{
    /// <summary>
    /// ViewとModelを繋ぐPresenter
    /// </summary>
    public class CubeRotationPresenter : MonoBehaviour
    {
        [SerializeField] private SliderView _sliderView;

        void Start()
        {
            var cubeRotationLogic = CubeRotationModel.Instance;

            // ================================
            // Sliderの値の更新を監視
            // ================================

            _sliderView.SliderValueRP_X
                .Subscribe(value => { cubeRotationLogic.SetRotationX(value); }).AddTo(this);

            _sliderView.SliderValueRP_Y
                .Subscribe(value => { cubeRotationLogic.SetRotationY(value); }).AddTo(this);

            _sliderView.SliderValueRP_Z
                .Subscribe(value => { cubeRotationLogic.SetRotationZ(value); }).AddTo(this);
        }
    }
}

View側で公開されている購読機能の中でModel側で公開されている機能を利用します。

最後に

一応GitHubに置いときました。
MVP_Demo

続きとして、MonoBehaviourを継承していないクラス間でのMVPパターンを
Extenjectと合わせて作ってみようと思ってます。


2021/04/25 追記
やってみました。
【参考リンク】:【Unity(C#)】UniRxとExtenjectでMV(R)Pやってみる

参考リンク

UnityにおけるMVPパターンについて
Web出身のUnityエンジニアによる大規模ゲームの基盤設計
UniRxでMV(R)Pパターンをやってみた
UniRxとZenjectを使ってMV(R)Pで南京錠を作る
Unityで学ぶMVPパターン ~ UniRxを使って体力Barを作成する ~

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
7
Help us understand the problem. What are the problem?