めちゃくちゃ難しかった
世の中には大量にUniRxに関する
最強にわかりやすい資料が転がっているのですが、
どうやら私は人より完全に理解した気になるのが早いらしく、
悪い勘違いをしたまま、完全に理解した気になっていたようです。
現時点でもその可能性は否定できませんが、
実際のサンプルを用いて人に説明するつもりで学んでいけば、
より理解が深まるのではないかと思い、メモを残すことにしました。
実際のサンプル
実際のサンプルがこちらです。
右の黒い柱のようなオブジェクト(この記事内では"壁"とします)に
球体(この記事内では"ボール"とします)が衝突すると
ライトの色がボールの色と同じ色に変化します。
この程度ならUniRxを使わずとも簡単に実装可能なので
行われている処理をイメージしやすいと思いサンプルに選びました。
UniRxを利用
では実際にUniRxを使った場合はどうなるのか見ていきます。
図の通りです。
と言いたいところですが
自分でも嫌になるくらいごちゃごちゃしてしまったので、
コードを追って説明する上での補足として見るぐらいがいいと思います。
では実際にコードを追っていきます。
コード
using UniRx;
using UnityEngine;
using System;
/// <summary>
/// 壁にアタッチ
/// </summary>
public class CollisionNotify : MonoBehaviour
{
//下のコメントアウトしたプロパティを簡潔に書いたらこうなる
//IObservableは外部のクラスで監視されるためpublicで公開しておく
public IObservable<Color> colorObservable => colorSubject;
//public IObservable<Color> triggerObservable
//{
// get { return colorSubject; }
//}
//何かしら起きたことをお知らせする機能(SubjectのIObserver)はこのクラス内で使用するので外部に公開する必要はない
readonly Subject<Color> colorSubject = new Subject<Color>();
//オブジェクトの衝突時、メッセージを発行する
void OnCollisionEnter(Collision collision)
{
Color otherObjColor = collision.gameObject.GetComponent<MeshRenderer>().material.color;
colorSubject.OnNext(otherObjColor);
}
}
CollisionNotify
クラス内ではIObservable
,Subject
,OnNext
が書かれています。
しかし、実際にObserverパターンの中の具体的な処理を担っているのは
ここではSubject
,OnNext
のみです。
IObservable
は外部に公開しており、
他のクラス内でやってほしい処理を登録する役割を担います。
次に先ほど具体的な処理を担っていなかった
IObservable
に対して別クラスにて役割を与えます。
using UnityEngine;
using UniRx;
/// <summary>
/// 適当なオブジェクトにアタッチ
/// </summary>
public class LightColorChanger : MonoBehaviour
{
[SerializeField]
CollisionNotify collisionNotify;
[SerializeField]
Light directionalLight;
void Start()
{
//OnNextメッセージを受け取ったら実行(≒OnNextメッセージが飛んでくるまで監視される)
collisionNotify.colorObservable
.Subscribe(collisionObjectColor =>
{
directionalLight.color = collisionObjectColor;
Debug.Log("色変わったよ!");
})
.AddTo(this);
}
}
コード内のコメントにもある通り、
発行された値(OnNextの引数に指定した値)を受け取って、
Subscribe内の処理を実行します。
なのでSubscribe(collisionObjectColor => {})
のcollisionObjectColor
にはcolorSubject.OnNext(otherObjColor);
で
発行したメッセージの中身のotherObjColor
が入っています。
つまり、
①オブジェクトが壁にぶつかる
②メッセージが発行される
③メッセージの中の値を受け取る
④登録した処理が実行される(値を利用できる)
となります。
ここまでのメモを振り返って改めて最初の図を見ると、
最初に見た時よりはマシに見えてきました。
Subjectは外部のクラスに公開しない
Subject
をreadonly
にして、OnNext
を発行するクラスを制限していました。
readonly Subject<Color> colorSubject = new Subject<Color>();
設計の観点から、メッセージを発行するクラスは、
不用意に外部のクラスに公開するのはよろしくないそうです。
これは別にprivateでも問題ないのですが、
"外では使わないよ"というのを強調するために使っています。
UniRx.Triggers
先ほどの図で説明した一連の流れをストリームソースと呼びます。
ただ、Unityには既にいろんなコールバックイベント(Button押したら...衝突したら...とか)が存在しています。
そこで、既存のコールバックイベントを活用してストリームソースを作りたい。。。
という願いに答えてくれる機能が既にあります。
例えば、先ほど例に挙げた、
OnCollisionEnterを検知しOnNextメッセージを発行するサンプル
は下記のように書き換えできます。
using UniRx.Triggers; //これ必要
using UniRx;
using UnityEngine;
public class UseOnCollisionEnterAsObservable : MonoBehaviour
{
[SerializeField]
Light directionalLight;
void Start()
{
this.OnCollisionEnterAsObservable()
.Subscribe(collisionObject =>
{
ColorChange(collisionObject);
Debug.Log("色変わったよ!");
})
.AddTo(this);
}
void ColorChange(Collision collision)
{
directionalLight.color = collision.gameObject.GetComponent<MeshRenderer>().material.color;
}
}
いろんなのがある(特にUI関連)ので実際に触ってみると便利さがわかるかと思います。
【参考リンク】:UniRx.Triggers
UniRxって結局何
Q. UniRxって結局何がすごいの?
という疑問。
その答えとしては、いろいろあるのでしょうが、
A. MVPパターンを簡単に実装できる
が一番大きな利点でしょうか。(特にお仕事で利用する場合は)
それに伴ってまた疑問となるのが、
Q. MVPパターンって何がすごいの?
ですが、
A. 規模が大きくなっても比較的楽に拡張ができる
が答えでしょうか。
(私もあまり詳しくないので、もっと他に理由があれば教えてください)
【参考リンク】:Unityで学ぶMVPパターン ~ UniRxを使って体力Barを作成する ~
最後に
もうちょっと踏み込んだ理解をしようと試みたんですが、
今の自分ではまだ難しかったです。
これから学習する方の入門書の入門書となれば幸いです。
今後はそもそもの理解度を深めつつ、Hot、Cold等の話もインプットして自分なりのメモを残そうと思います。