gracetory’s blog

東池袋にある合同会社グレストリのエンジニアブログです

初めてのUniRx + MVP(Model View Presenter)(Unity 2017.1.0) Part1

f:id:grshizawa:20170804194210p:plain:w800

自己紹介

こんにちは、初めまして、グレストリでエンジニアをしているShizawaです。

弊社では主にサーバーサイドでPHP、クライアントサイドでC#(Unity)、C++(Cocos2d-x)を使っています。 私は主にUnityを使ってゲームを製作していますが、初めてUnityに触れてから一年半が経ったほどで、まだまだ勉強中の身です。

最近プレイしているソーシャルゲームは「黒騎士と白の魔王」と「Fate/GO」たまに「デレステ」「バンドリ」
コンシューマゲームは今ホットなスプラトゥーンをプレイ中。暑いですね・・・

導入

ところで弊社からリリースしている「魔王さまの誤算」というゲームはUnityで製作しています。
魔王さまの誤算:超爽快ラインディフェンスゲームを App Store で
魔王さまの誤算:無料爽快ディフェンスゲーム - Google Play の Android アプリ
(良ければ、ぜひ遊んでください。)
このゲーム製作での反省点は数えればキリがないのですが、 あげるとすればUI周りのソースコードが煩雑になりすぎて・・・ 何か機能を追加するたびにスパゲッティなアレになっていったのです。
そこで何か解決策がないものかとネットを調べていたら、UniRxというライブラリに辿り着きました。

表題にもある通り、私はUniRxを触るのは初めてです。

ターゲット

Unityは少しできるけど、UniRxとは何か知らないという方。 私も初心者でざっくりとしか理解していないため、 ざっくりとした説明になります。
まずは使ってみることが大事だと、最低限の知識で乗り切っています。

UniRxを使うために必要な知識

必要前提となる知識を下記にざっくりとまとめます。 ちなみにUnityの知識はここでは割愛します。

C# LINQ
C# event
  • イベント - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

    「キーボードのボタンが押された」とか「マウスが移動した」等の、 コンピュータ上で発生するなんらかの事象のことをイベント(event)といい、 イベントが発生したときに行う処理のことをイベント ハンドラー(event handler)と呼びます。 このように、イベントとそれに対する処理により動作するようなプログラムのことをイベント駆動型(event drive)プログラムと呼びます。

Observerパターン
MVPパターン
UniRxの概要
  • UniRx入門シリーズ 目次 - Qiita
    • MicrosoftResearchが開発していたC#向け非同期処理向けライブラリ
    • デザインパターンの1つ、Observerパターンをベースに設計されている
    • 時間に関係した処理や、実行タイミングが重要となる処理を簡単に記述できるようになっている
    • 完成度が高く、Java,JavaScript,Swiftなど様々な言語に移植されている

引用しながら書きましたがやんわりと理解しているだけでも、
UniRxをちょっと知った気分になれる思います()。

実際にやってみよう

ボタンカウンタを作成

UniRx初心者の私にもわかりやすい UniRxの ReactiveProperty という機能を使って、ボタンカウンタを作ってみました。
ReactiveProperty とは?
→ 値の変更を通知してくれる変数のようなもの。

今回作ったサンプルの注意点は下記

  • Subject(イベントを発行する核となるインスタンス)を使用していませんが、 ReactivePropertyにその機能が付いています。

  • UniRxと相性の良い、 MVP(Model View Presenter)パターンで作成しています。

UniRxの導入方法

・Assetストアで検索して、インポートする

または

・GitHubからインポートする
GitHub - neuecc/UniRx: Reactive Extensions for Unity

Unityエディタ上

Unityエディタ上はこんな感じ
f:id:grshizawa:20170802134117p:plain

ざっくり説明すると、作成したスクリプトは下記2つ

  • ButtonPresenter.cs
  • ButtonModel.cs

HierarchyのCanvas/Board/ButtonにButtonPresenter.csをアタッチしていて、
インスペクタ上でメンバー変数にButtonとTextを参照づけしています。

ソースコード

ButtonModel.cs

// 主にデータ管理を行うModelクラス

using UnityEngine;
using UniRx;  // 忘れずに

public class ButtonModel {
    // プロパティを用意する(値が変更されたら、イベントを通知する)
    public ReactiveProperty<int> PushCounter { get; private set; } // 外部からは取得のみできる

    // コンストラクタ
    public ButtonModel() {
        this.PushCounter = new ReactiveProperty<int> (); // ①実体を生成
        this.PushCounter.Value = 0;                      // 初期化
    }

    // ②カウント処理
    public void PushCount() {
        this.PushCounter.Value++; // ここでイベントが通知される
    }
}

(ソースコード内の番号に対応)
①this.PushCounter = new ReactiveProperty ();
値の変更を検知するプロパティの実体を生成します。

②カウント処理
外部からの呼び出し用にカウントメソッドを用意。 この関数で値が変更され、イベントが通知されます。


ButtonPresenter.cs

// View(UI)からユーザ操作イベントを受け取り、モデルデータを監視する側

using UnityEngine;
using UnityEngine.UI;
using UniRx;  // 忘れずに

public class ButtonPresenter : MonoBehaviour {

    // View(UI)
    public Button button;
    public Text counterText;

    // Model
    private ButtonModel buttonModel;

    // Use this for initialization
    void Start () {
        // モデルを新規作成
        this.buttonModel = new ButtonModel();

        // 押すたびにカウントをする処理を登録
        this.button.OnClickAsObservable()                 // ①ボタンが押された時に
            .Subscribe(_ => this.buttonModel.PushCount()); // ②カウントの処理するように登録

        // カウントをテキストに反映する処理を登録
        this.buttonModel.PushCounter                                          // ③値が変更された時に
            .Subscribe(countNum => this.counterText.text = countNum.ToString());// ④テキストを書き換える
    }
}

① this.button.OnClickAsObservable()
 OnClickAsObservableはButtonクラスに追加されている、UnitRxの拡張メソッド。
 これを記述すると「buttonが押された時」と通知タイミングを指定できる。

②.Subscribe(_ => this.buttonModel.PushCount())
 Subscribeは指定した処理を登録する。
 ここでは、Modelクラスの関数PushCount()を登録している。
 ちなみに_ =>で引数「 _ 」を渡していますが、使用していません。

③this.buttonModel.PushCounter
 このPushCounterはReactivePropertyです。
 イベント通知タイミングは値が変更された時。
 ②でbuttonModel.PushCount()を登録しているので、ボタンが押された時に
 PushCounterの値の変更がされ、イベント通知がきます。

④.Subscribe(countNum => this.counterText.text = countNum.ToString())
 これは②と同じ、
 テキスト表示を切り替える処理を登録しています。

①~②と意味を繋げてみるとわかりやすいですね。「ボタンが押された時に、カウント処理をするように登録する」ということです。 Start関数は一度だけ呼ばれる関数ですが、ここで一度登録しておけば、ボタンが押される度に内部的にカウントアップされます。
これだけで、できてしまうのはシンプルでわかりやすいですね。

完成したのがこちら

f:id:grshizawa:20170802183413g:plain:w400

まとめ

  • UniRxの実行タイミングの記述が容易にできるという利点
  • オブザーバーでMVPを意識すると拡張性と可読性が上がる利点
    これらを組み合わせると幸せになれると。

今まではPresenterに全ての処理を突っ込んでいたようなものだったので、とてもスパゲッティになっていましたが、 このやり方であれば値の変更と実際の処理を切り分けられるので、機能拡張しやすく、わかりやすいということです。 UniRxはその記述の仕方をめちゃくちゃ補助してくれます。

さて今回は単純にカウントアップさせるだけだったので、UniRxの本領はまだ全然発揮できていません。 イベントのフィルタ機能や実行タイミングの詳細を覚えれば、
色々応用ができるのですが、また次回以降ですね。

次は一歩進んだところを解説できるように頑張ります。

あとがき

UniRxを作ったのが「黒騎士と白の魔王」を手がけた、グラニのCTOの方だったなんて。。。
Unite 2017でこの記事を見つけてびっくり。
本ゲームの制作でUniRxが使われているらしく、UniRxを使っての反省点などを動画で解説してくれてます。
www.youtube.com