i-school - ReactiveProperty を活用した MVP パターンによる実装例

1.MVP パターンによる設計


 MVP パターンとは、UI(GUI) 周りに関する設計方法の1つです。
煩雑になりがちな処理について、明確な責務(役割)を持つクラスに分けて実装を行うことで、
疎結合化した設計でなるべくわかりやすい形での UI 周りの実装を行う、という考え方になります。



 MVP とは、設計に利用される要素(クラス)の頭文字であり、それぞれ、M = Model、V = View、P= Presenter という役割になっています。

 Model とはデータの実体です。値を保持するための変数の管理などを受け持ちます。

 View とは、UI の制御を受け持つ部分です。Model の管理しているデータを画面に表示したり、ボタンなどのユーザーからの操作を受け取る部分を受け持ちます。

 Presenter とは、Model クラスと View クラスとをつなげる仲介者です。
Presenter クラスのみが Model クラスと View クラスへの参照を持つ形にすることで、各クラスの疎結合化を実現しています。



 MVP パターンの実装には、UniRx という無償で提供されているライブラリを利用します。

 Web アプリなどを作成する際に利用されている実装方法ですが、UniRx の ReactiveProperty の機能を利用することにより、
UI の部分については、Unity においても MVP パターンでの実装が行えます。
 

2.<UniRx とは?>


 UniRx とはリアクティブプログラミングという考え方を Unity で利用できるように設計されているライブラリです。
非同期処理イベント処理に対して効率的な実装方法を提供する構造になっています。

 便利かつ、高度な処理を実現することができますが、学習コストも非常に高いものになっています。
また学習したからといって、なんでもこの構造にすればいい、というものでもありません

 C# の基礎だけではなく、Linq、ラムダ式(および、メソッドチェーン)といった機能も理解していないと扱うことが難しいため、
これらの機能についてまだ理解が不十分である場合には、そちらを学習してから進めるようにしてください。

 UniRx については記事も多いですし、参考となる書籍もありますので、そちらも合わせて学習を行うようにしてください。

 スクリプト内で UniRx を利用する場合、using UniRx; の宣言が必要になります。


3.<オブザーバーデザインパターンを利用した設計>


 UniRx はデザインパターンの1つである、オブザーバーデザインパターンというもので設計されています。

 具体的には、イベント(ボタンなど)を実行した際の処理を、通知する側通知を受け取る側、という概念を利用して実装を行う設計になります。

 以下の記事は非常にわかりやすくまとめられています。こちらを順次読み込んで、理解を深めてください。
Qiita @toRisouP 様
UniRx入門 その1


4.実装方法


 では今回の実装方法は、どのように利用していくのか、どんな部分に利点があるのかを説明します。

 ゲーム内においてスコアの要素(int 型 score 変数)があるとした場合、その値をゲーム画面に反映するためには、
Canvas に配置した UI (Text)部分の表示更新を行う必要があります。その場合には、主に以下のような手順の処理で実装していると思います。

 1.score 変数を管理する Score クラスに対して、値を加算する処理を実行する
       ↓
 2.score 変数をゲーム画面に表示している UI 管理用クラスに対して命令を出して、 Text コンポーネントを利用してゲーム画面のスコア表示を更新する

 このうち、2の処理をクラス間での処理内容に置き換えましょう。

 2−1.Score クラス (score 変数をゲーム画面に表示している UI 管理用クラスに対して命令を出して)
       ↓
 2ー2.Text コンポーネントを管理している UI 管理クラス (Text コンポーネントを利用してゲーム画面のスコア表示を更新する)

 以上のことより、Score クラスが UI 管理用のクラスと依存関係を結んで、その情報を利用してメソッドを実行している流れです。

 この部分に、オブザーバーパターンを利用した場合、処理は次のようになります。

 1.Score クラス(Model)に ReactiveProperty を定義して、この変数でスコアの値を管理する。ReactivePropery の変数に値を加算する処理を実行する(加算までの処理の流れは同じ)
       ↓
 2.ReactivePropery の値を新しい Presenter クラスを作成して購読(監視)させる。
   ReactivePropery の値が更新されるたびに、UI 管理クラス(View)に対して自動的に命令を出して、ゲーム画面の表示を更新する(以前と違う部分)

 どちらも処理は違いますが、大きく異なるのは、2の部分です。

 ReactivePropery を利用することにより、この値を外部クラス(Presenter クラス)から購読(監視)する処理を実装することができます。
この処理により、以前のように、値が更新されるたびに、Score クラスから UI 管理クラスに対して都度、表示更新の命令を行うのではなく、
ReactiveProperty の値の状態を監視(購読)する状況をつくり、この値が更新されるのに合わせて、自動的に UI の更新処理を行うようにする仕組みです。

 そのため、最初に値を監視(購読)する処理を作成し、それと UI の更新を行う処理を紐づけしておけば、あとは何もしなくてもよい(自動化)状態を作り出します。


5.MVP パターンによる実装例


 それでは、実際に MVP パターンによる設計を利用した、UI 表示更新機能の製作を行います。


1.Canvas の作成


 まずは Scene ビューに Canvas を作成し、Text コンポーネントの機能を利用して、ゲーム画面に獲得しているスコアの値を表示させる UI を作成してください。
サイズや位置などは任意です。


2.UniRx のインストール


 無償で提供されているライブラリを利用します。

 Unity のアセットストアの検索にて UniRx と入力すると検索結果として表示されますので、そちらをダウンロードして Unity にインポートします。
あるいは下記のリンクから直接参照も出来ます。
UniRx
https://assetstore.unity.com/packages/tools/integr...


アセットストア



3.Model クラスの作成


 値を管理する役割を持つクラスです。


<= クリックすると開きます。



4.View クラスの作成


 UI 表示の更新を制御するための View クラスを作成します。


<= クリックすると開きます。




5.Presenter クラスの作成


 Model クラスと View クラスを仲介するための Presenter クラスを作成します。

 サンプル処理として、マウスの左クリックをする度に、Score 変数の値が加算される、という処理を記述しています。


<= クリックすると開きます。



6.<ReactivePropery<T> >


 ReactivePropery(リアクティブプロパティ)は、UniRx の持つ機能の1つです。
利用するには using UniRx; の宣言が必要になります。

  using UniRx;

  public ReactiveProperty<int> Score = new ReactiveProperty<int>(0);

 プロパティであることを示すため、変数名の頭文字は大文字で定義しています。



 ReactivePropery はジェネリック型であるため、<T> の部分には任意の型を指定できます。
今回は score 変数の代わりとなるため同じ int 型ですが、自作したクラス(例えば、複数のメンバ変数を持つクラス)などを指定することもできます。

 ReactivePropery は Value プロパティを持っており、この中に ReactivePropery の情報が代入されています。
そのため、 Value プロパティを利用することで値を参照したり、代入処理をして更新することが出来ます。
 
 今回であれば、int 型の整数の情報、すなわち、score 変数の代わりとなる値が代入されている部分が Value になります。
通常のプロパティにおける、set 内にある value と同じ役割であると言えます。


<Value プロパティ>
  Score.Value += amount;

 Score.Value++;

  int x = Score.Value;


 
 もっとも大きな特徴として、ReactivePropery には「値が更新されると通知を行う」という機能が備わっています
そのため、ReactivePropery の値が 0 => 10 というように変化をすると、ReactivePropery から通知を受け取る(監視する)処理を施すことで
この処理に付随して、自動的にイベント処理を発生させることが出来ます。

 今回は Score クラスに用意した ReactivePropery の値を、Presenter クラス側にて監視してもらうように設計しています。

 ReactivePropery 側が「通知を行う側 = 発行」といい、ReactivePropery の値の監視を行う側を「通知を受け取る側 = 購読」といいます。



 ReactivePropery からの通知を受け取るためには、通知を受け取りたい ReactivePropery に対して、Subscribe メソッドを実装します。
Subscribe メソッドは引数内に、処理の実装、あるいはメソッドの呼び出し処理を実装することにより、
ReactivePropery の値が更新されたら、Subscribe メソッドの引数に記述してある処理を自動的に毎回実行する」という処理が動きます。

 この機能によって、「ある状態になったときに、指定してある処理を実行する」というイベント処理を実装することが出来るようになっています。


  modelScore.Subscribe(x => view.UpdateDisplayCount(x))

 Subscribe メソッドはラムダ式で処理を記述します。引数を利用しない場合には、(_ => ) とアンダーバーを指定できます

 今回の Subscribe メソッドの処理は【 x => view.UpdateDisplayCount(x) 】ですので、
''ReactivePropery の値(Score 変数)が変更になったら、その都度、View クラスの UpdateDisplayCount メソッドを実行します。
その際の引数(今回は x 変数)には Score 変数の最新の値が利用されます。

 そのため今回の実装では、スコア用の値を更新したあとに、手動で UI 表示を更新するという処理がなくなっていますが、
この ReactivePropery の機能によって、いままでと同じ処理を自動的に動作するように機能を作っています。ここが大きな違いになります。

 実装したプログラムを読み直してみてください。
画面の表示を更新する処理は、Presenter クラスの Start メソッドで1回実行されているだけであることが分かると思います。



 もう1つ大切なことがあります。この ReactiveProperty の Subscribe(購読)の処理は、実行しているスクリプトが破棄されても、処理のみが残り続けます
そのため、適切なタイミング(スクリプトのアタッチしているゲームオブジェクトの破棄など)に合わせて、Subscribe の処理も破棄させる必要があります。

 この破棄させる処理を忘れてしまうと、裏側で永続的に処理が動いてしまうことになり、メモリリークの原因となります。

 破棄させるには Dispose メソッドを利用し、明示的に破棄します。
あるいは Subscribe させるときに AddTo メソッドを記述することで、ゲームオブジェクトの破棄のタイミングに Dispose メソッドを紐づけておくことが出来ます。
停め忘れをしないためにも、予め、ゲームオブジェクトの破棄のタイミングに紐づけておく方法が安全です。


6.大切なこと


 この教材では、MVPパターンによる実装を行い、その処理の動きを体験していただきました。

 ただし、本当に重要なのは、動いた、ことではなくて、どうしてそのような動きになるのか、を自分で考えていくようにすることです。
そのためには、ReactiveProperty を構成している UniRx 自体の機能をしっかりと理解していく必要があります。

 難しい機能の場合には、このような後追い学習の方が理解が早いケースもあります。
まずは実際に処理を動かしてみて、処理を改造してみてください。

 そして、UniRx の構造、オブザーバーパターンについて、学習をしていくようにしてください。



 以上になります。