gracetory’s blog

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

初めてのUniRx(Unity 2017.1.0) Part2

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

まえがき

こんにちは、プログラマのShizawaです。

僕の中の「やりたいけどやってはいけないゲームリスト」の一つである、
FF14というオンラインゲームを今更始めてしまいました。
32レベルの竜騎士です。

こんな美麗なグラフィックでMMO・・・これをコンシューマーのゲーム機(PS4)で
プレイできるのは感動ひとしおです。(カクツキもほとんどない)

ゲームは1日1時間まで、なんて守れるわけがない。
そろそろ現実(本題)に戻らなければ・・・

導入

前回の記事のURL
techblog.gracetory.co.jp

前回はUniRxをとりあえず使ってみようという趣旨で書きましたが いきなりUniRxとMVPという2つの要素を詰め込んでしまったのは反省・・・

あれから隙間時間でUniRxの勉強をしているのですが、基礎の半分は理解できたかなという感じです。

今回は参考にしたサイトからざっくり引用しつつ、 UniRxの基本となる仕組みを勉強しながら紹介していきます。

UniRxの基礎

下記サイトがとてもわかりやすかったので、参考に載せておきます。

www.slideshare.net

ストリームとは

「イベントが流れる配管」みたいなイメージ
例えば、ボタンが3回押された時は ストリームにイベントが押された順に3つ流れているという感じ。

ストリームの構成

qiita.com

1.メッセージを発行するソースとなるもの(Subjectなど)(ストリームソース)
2.メッセージを伝播するオペレータ(Where,Selectなど)
3.メッセージを購読するもの(Subscribe)

の3つからなります。

オペレータとは

ストリームに操作を加える関数のこと

例えば以下のコード

this.button.onClick.AsObservable()    // ①ボタンが押された時
.Where(_ => this.count < 5)            // ②countが5より小さい場合
.Subscribe(_ => {                    // ③カウント処理をする
    count++;
    counterText.text = count.ToString();
});

①②③いずれもオペレータです。
①ButtonコンポーネントがUniRxによって拡張された関数であり、
ボタンが押された時にストリームにイベントを流し込みます(ストリームソース)。

②Whereはフィルタで条件を満たさないとそれ以上流れないようにフィルタします。 つまり③まで処理が届きません。この場合はcountが5より大きいとカウント処理をしないようにしています。

③は最終的に行う処理です。 とそれぞれストリームに流れるイベントを元に作用する関数となっています。

オペレータにはたくさん種類があり、とても書ききれないので、下記にまとめてくれているサイトを 貼っておきます。

qiita.com

このようにコードを上から下に見ると、イベントがストリームを流れているイメージがつきやすいと思います。 オペレータを使いこなせは、様々なパターンの処理に対応できそうですね。

Subjectとは

ストリームの構成で紹介した

メッセージを発行するソースとなるもの(Subjectなど)

で、ストリームソースになるものは他には下記があるようです。

・ReactivePropertyシリーズを使う
・ファクトリメソッドシリーズを使う
・UniRx.Triggersシリーズを使う
・コルーチンを変換して使う
・uGUIイベントを変換して使う
・その他UniRxが用意しているものを使う

この記事や前回の記事で紹介したコードでは ストリームリソースがButtonコンポーネントに拡張メソッドで用意されているObservableでした。 コードで言うと、this.button.onClick.AsObservable()

このストリームソース(発火タイミング)を自分で書く場合にSubjectを使います。

   // ①Subject:ストリームの発行元(ストリームソース)
    Subject<string> subject = new Subject<string> ();

    // イベント監視側
    subject.Where(msg => msg == "B")           // "B"の時のみ
        .Subscribe(msg => Debug.Log (msg) ); // イベント発火時に実行する処理を登録
        
    // イベント発行側
    subject.OnNext("A"); // 登録した処理を実行する
    subject.OnNext("B");
    subject.OnNext("C");
    subject.OnCompleted();// 完了を通知、ストリームを破棄
    subject.OnNext("B"); // 表示されない

実行結果: B

とこんな感じで記述することができます。

①このインスタンスを指定してイベントの発火や購読の処理を追加していきます。 Subject<string>となっている理由は文字列をイベント発火時に引数として渡すためです。

OnNext、OnError、OnComplete

OnNextなどの通知の種類は下記の通り。こちらもわかりやすいサイトがあったので、下記URLを引用。
UniRx入門 その2 - メッセージの種類/ストリームの寿命 - Qiita

OnNext:通常イベントが発行された時に通知されるメッセージ
OnError:ストリームの処理中で例外が発生したことを通知する
OnCompleted:ストリームが終了したことを通知する。

Subscribe()で上記3つのメソッド(OnNext、OnError、OnCompleted)に対応した書き方ができます。

subject.Subscribe (msg => Debug.Log (msg) );

これはOnNextの通知時に実行するように登録する処理。

下記の場合はOnNextされた時とOnErrorされた時の処理を登録しています。

subject.Subscribe ( msg => Debug.Log (“OnNext”), error => Debug.Log (error + “ : OnError”));

subject.OnError(new System.Exception());    // エラー内容と" : OnError"が表示される
subject.OnNext("A");    // OnErrorの後のストリームは破棄されるため、表示されない

Subscribe時にOnNextとOnCompletedの組み合わせを指定するなど 指定のパターンがそれぞれあるので、それはURLを参考ください・・・。

OnCompleted()の後にOnNextしていますが、ストリームの終了を意味するので、 後のOnNextの通知は実行されていません。後でストリームの寿命についての説明があるので、 そちらを参照してください。

ストリームの寿命

ストリームには寿命があります。 ストリームの実態はSubjectであり、Subjectが消えない限りストリームは生き続けます。

下記コードの場合はplayerGameObjectが破棄されてもストリーム自体は 破棄されないので、破棄されたplayerGameObjectを参照し、エラーになってしまいます。

   [SerializeField] GameObject playerGameObject; // プレイヤーのゲームオブジェクトをUnity Editor上でアタッチする
.
.
.
    // ストリームを管理するSubjectを作成
    Subject<string> subject = new Subject<string> ();

    // イベント監視側
    subject.Subscribe(objName => playerGameObject.name = objName);   // ゲームオブジェクトの名前を変更
        
    // イベント発行側
    subject.OnNext("A");
});

このような場合にはAddTo()を使用します。

   [SerializeField] GameObject playerGameObject; // プレイヤーのゲームオブジェクトをUnity Editor場でアタッチする
.
.
.
    // ストリーム作成
    Subject<string> subject = new Subject<string> ();

    // イベント監視側
    subject.Subscribe(objName => playerGameObject.name = objName)    // ゲームオブジェクトの名前を変更
        .AddTo(playerGameObject);   // 追加
        
    // イベント発行側
    subject.OnNext("A");
});

AddToオペレータでゲームオブジェクトを指定すると、 指定したオブジェクトが破棄された時にSubjectに登録した処理を削除することができます。

次回は

次のUniRxブログではストリームのHotとColdを説明した後、
今までで学んだ内容でちょっとしたゲームを作成してみたいと思います。

あとがき

今回はただUniRxの基礎について引用しまくり、ところどころ自分の言葉に置き換えて説明している。
完全に勉強として書いてるだけになってて、これまたちょっと反省です。

いやほんとブログ書くの苦手で時間がかかります・・・ でも新しく学んだことをブログに書き起こすのって身になるし、とても勉強になってうれしいなと思います。 早く慣れたいものです。

そして、UniRxを次のUnityプロジェクトで活かせるように頑張らねば。