はじめてのUniRx
2015/06/19 UniRx勉強会
@toRisouP
このプレゼンの内容
UniRxをどう使っていけば
良いかの紹介が中心
ターゲット層
UniRx初~中級者向
(けどかなり中級者より)
このスライドに出てくることは
皆さん知っている前提で話させて下さい
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
自己紹介
• 名前 とりすーぷ(@toRisouP)
• 本業はWeb系(最近はScalaを書いてる)
• 趣味でC#/Unity開発をやってます
Unityでつく(った|ってる)もの
• みくみくまうす
– ニコ生配信支援ツール
• みこバト~レ
– 東方2次創作ゲーム
• YASAI KUDAMONO
– 超あほげーで作成
3つともUniRxを使ってます
YASAI KUDAMONO
• 超あほげー向けに18時間くらいで作ったゲーム
• UniRxを随所に使った設計になってる
• ソースコードを公開しているのでUniRxの参考になれば
– https://github.com/TORISOUP/YasaiKudamonoSrc
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
Updateを消し去る
• Update()をObservableに変換して
Awake()/Start()内にまとめて記述する
Observable化していない実装
やりたいことを直列に書く必要があり流れが追いづらい
Observable化した実装
比較(Observable化)
やりたいことを並列に書けるので読みやすい
Updateを消し去るメリット
• 処理ごとに横に並べて記述ができる可能
– 処理ごとのスコープが明示されるようになる
– 機能追加、削除、変更が容易にできるようになる
– 記述が宣言的になり処理の意図がわかりやすくなる
• Rxのオペレータがロジックの実現に使用可能
– 複雑なロジックがオペレータの組み合わせで実装できる
Observable化する3つの方法
• UpdateAsObservable
– 指定したgameObjectに紐づくObservableが作れる
– gameObjectのDestroy時にOnCompletedが発行される
• Observable.EveryUpdate
– gameObjectから独立したObservableが作れる
– MonoBehaviourに関係ない場所でも使える
• ObserveEveryValueChanged
– Observable.EveryUpdateの派生版
– 値の変動を毎フレーム監視するObservableが作れる
Observable.EveryUpdateの注意点
• Destroy時にOnCompletedが発行されない
– UpdateAsObservableと同じ感覚で使うと罠を踏む
このgameObjectが破棄されると
nullになって例外が飛ぶ
寿命管理の楽な方法
• AddTo
– 指定のgameObjectが破棄されたら自動Disposeしてくれる
– OnCompletedが発行されるわけではない
AddToに渡したgameObjectが
Destroyされると一緒にDisposeされる
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
ストリームでつなぐ
• コンポーネントをストリームでつなぐことで
Observerパターンな設計にしてしまう
– 全体がイベント駆動になるようにしてしまう
– ちなみにRxはObserverパターンそのもの
タイマを例にして解説
• タイマのカウントを画面に表示する
– UniRxを使わずに実装
– UniRxのストリームで実装
タイマを使った例
• タイマのカウントを画面に表示する
– UniRxを使わずに実装
– UniRxのストリームで実装
例)ポーリングの例
例)ポーリングの例
毎フレーム、値が更新されたか確認する(ムダが多い)
タイマを使った例
• タイマのカウントを画面に表示する
– UniRxを使わずに実装
– UniRxのストリームで実装
まずタイマ側をストリーム化
ゲームの残り時間(秒数)を表すReactivePropertyを公開
このReactivePropertyをObserverがSubscribeする
まずタイマ側をストリーム化
ゲームの残り時間(秒数)を表すReactivePropertyを公開
このReactivePropertyをObserverがSubscribeする
CurrentTimeをReactivePropertyとして公開する
(値が更新されるとOnNextでその値が通知される)
タイマを使う側の実装
タイマを使う側の実装
向こうから更新通知が送られてくる
そのタイミングで描画を更新するだけ
ストリームでつなぐメリット
• Observerパターンが簡単に実装できる
– 変化をポーリングする実装が消え去る
– 必要なタイミングで必要な処理をするように記述すればよい
• 既存のイベント通知機構より簡単
– C#のEventは下準備が面倒で使いたくない
– UnityのSendMessageは使いたくなく
– RxならObservableを用意すればOK!ラクチン!
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
Hot/Coldな性質
Observableは性質により2種類に別けられる
• Hotな性質のもの
• Coldな性質 のもの
Hotな性質のObservable
• Observerがいなくても稼働する
• ストリームを枝分かれさせ、メッセージを分配させることができる
Coldな性質のObservable
• Observerがいないと動作しない
• Subscribeされる度に新しく生成される(枝分かれしない)
例)Cold Observableの複数回Subscribe
• intervalStreamを時間をズラして3回Subscribeする
– 同じストリームのはずなのにOnNextのタイミングがバラバラ
– それぞれ別々のストリームが生成されてしまっているため
図解
intervalStream
Coldな性質のintervalStreamを複数回Subscribe
図解
intervalStream
intervalStream
intervalStream
3つとも別のストリームが生成されてしまう
(それぞれ別々のタイマを持っているため時間がバラバラになる)
続き)Hot変換してみる
• ObservableはHotな性質に変換できる
– Hot変換オペレータを挟むことでHotにすることができる
– さっきのintervalStreamをHot変換してみる
図解
intervalStream Publish
Hot変換オペレータを末尾に配置してからSubscribe
図解
intervalStream Publish
Hot変換した部分で枝分かれする
(1つのタイマを3つのObserverが共有することができた)
OnNextのタイミングが全て一致
Hot/Coldのまとめ
• Rxのハマりやすい罠の1つ
– 型から判別できないので非常にやっかい
– ストリームがHotであるかColdであるか気をつける必要がある
– ストリームを外に公開するときはHot変換を挟むと安全
• ほとんどのストリームはColdである
– オペレータはほぼ全てColdな性質
– Subjectを内部に持つものが唯一Hotな性質である
• Subject,ReactivePropety,Hot変換用オペレータなど
詳しく知りたい人は
• Qiitaにまとめたのでこちらを読んでください
– http://qiita.com/toRisouP/items/f6088963037bfda658d3
– http://qiita.com/toRisouP/items/c955e36610134c05c860
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
UniRxとuGUIと組み合わせる
• uGUIで使えるModel-View-○○パターン
– uGUIで有用なMV○パターンが今まで存在しなかった
• MVCパターンはそもそも人によって考え方がバラバラすぎる
• MVVMはデータバインディングが無いので使えない
– ObservableとReactivePropertyを組み合わせると
uGUI周りがスッキリ書ける
Model-View-(Reactive)Presenter
• MV(R)Pパターン
– Model-View-Presenterパターン + UniRx
– 3つのレイヤをObservableでシームレスにつなぐ
View
Presenter
Model
MV(R)Pの図
Presenterが
ModelとViewを参照する
View
Presenter
Model
MV(R)Pの図
ReactiveProperty
内部状態の変化の通知
Subscribe
Viewへ反映
xxxAsObservable
ユーザ入力
Subscribe
Modelに反映
Presenterが
ModelとViewを参照を持つ
MV(R)Pパターンの作り方
1. ModelにReactivePropertyを持たせる
2. Presenterをつくる
3. PresenterにModelとViewを登録する
4. Presenter内で Viewの
Observableと ModelのReactiveProperty をそれぞ
れSubscribeしてつなぎこむ
実装例)みくみくまうす
• MMDモデルにニコ生のコメントを
読み上げさせるツール
• UIをMV(R)Pパターンで実装してある
MV(R)Pな実装例の紹介
• コメントの読み上げタイミングの調整スライダ
– SliderとInputFieldが連動する
– 入力された数値はConfigComponentが保持する
コンポーネント間の関係図
SpeechTimingSlider
Presenter
SpeechTimingReactiveProperty
(読み上げタイミング調整時間の実体)
OnValueChangedAsObservable()
スライダが変化した時にその数値を
通知するObservable
SpeechTimingInputField
Presenter
OnEndEditAsObservable()
InputFieldの入力が完了した時に
その値を通知するObservable
View
Presenter
Model
ConfigComponent
Modelの実装
ReactivePropertyを外に公開
Presenterの実装(Slider側)
Model → View
View → Model
Presenterの実装(InputField側)
Model → View
View → Model
uGUIで使うまとめ
• MV(R)PパターンでuGUI周りの設計が楽になる
– uGUIを使う場合はおそらく現時点での最適解
– プログラマにとってはやりやすいが、
ノンプログラマの人には触りにくく可能性に注意
• Presenterのベストな作り方はまだ模索中
– PresenterにどうやってModelとViewを登録するか
– Presenterを1つにまとめるか、分割して作るか
– 動的にPresenterを生成する場合はどうするか
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
コルーチンと組み合わせる
• UnityのコルーチンとObservableは相互変換可能
– コルーチンと組み合わせることでRxの弱点を補うことができる
コルーチン⇔Observable
• コルーチン→Observable
– Observable.FromCoroutine
• Observable→コルーチン
– StartAsCoroutine
コルーチン⇔Observable
• コルーチン→Observable
– Observable.FromCoroutine
• Observable→コルーチン
– StartAsCoroutine
コルーチン→Observableのメリット
• 複雑なストリームを簡単に作成できる
– ファクトリメソッドやオペレータチェーンだけでは
作れない複雑なロジックのストリームを作ることができる
• 手続き的な処理を隠蔽できる
– 煩雑な処理をコルーチンに隠蔽し外からはObservableとして
宣言的に扱えるようにできる
例)コルーチンからObservableを作
る
• プレイヤの生死に連動したタイマ
– プレイヤが生きている間のみカウントダウン
– プレイヤが死亡している間はタイマを停止する
– ファクトリメソッドやオペレータチェーンで作るのは難しい
タイマの定義
コルーチンから変換する
コルーチン中でOnNext
コルーチン⇔Observable
• コルーチン→Observable
– Observable.FromCoroutine
• Observable→コルーチン
– StartAsCoroutine
Observableをコルーチン化する
• StartAsCoroutine
– OnCompletedが発行されるまでyield return nullし続ける
– 完了時に最後のOnNext値を1つ出力する
• 非同期処理を同期処理っぽく書けるようになる
– Taskのawaitに近いことをUnityコルーチンで実現できる
実装例
• テキストをWebからダウンロード
• ボタンでテキスト送りしながら表示する
実装したコルーチン
テキストのダウンロードを待つ
ボタンが押されるまで待つ
コルーチンと組み合わせるのまとめ
• UnityのコルーチンとObservableは相互変換可能
– 複雑なストリームをコルーチンを使って実装可能
– コルーチン中でイベントの待ち受けが可能
– 非同期処理を同期処理っぽく書くことができるようになる
もくじ
• 自己紹介
• UniRxの使いドコロ
– Update()を消し去る
– コンポーネントをストリームでつなぐ
• (補足)HotとColdについて
– uGUIと組み合わせて使う
– コルーチンと組み合わせる
• おわりに
UniRxを使う上での心得
• 「全てはストリーム」という世界観を持つ
– ストリーム化できる所を探してストリームにすると便利になる
– ただしやりすぎは禁物
• Rxは万能ではない知る
– やりたいことが宣言的に上手く書けない場合も当然ある
– そういった場合はコルーチンと組み合わせると上手くいったりする
• 設計を意識する
– 行き当たりばったりの設計でUniRxを使うのは非常に危険
– Observerパターンのメリット・デメリットが活かせる
Qiitaにいろいろまとめました
• 過去にいくつか記事を書いているので参考になれば
– http://qiita.com/toRisouP/items/48b9fa25df64d3c6a392
ありがとうございました
@toRisouP
例)Animation同期をキレイに書く
• ユニティちゃんがボールを投げる
– アニメーションに同期して投げる
– 投げるボールのパラメータも指定できる
ボールを投げるアニメーション
ボールを投げるアニメーション
このタイミングでボールを生成して射出する
AnimationEvent
• このタイミングで「BallThrowEvent」が定義し
てある
UniRxを使わずに書くとこうなる
UniRxを使わずに書くとこうなる
ここでAnimation再生開始
UniRxを使わずに書くとこうなる
ここにコールバック
UniRxを使わずに書くとこうなる
一連の処理なのに
分断されてしまう
UniRxを使わずに書くとこうなる
一時保存用の変数
これをStartAsCoroutineで書くと
これをStartAsCoroutineで書くと
AnimationEventをObservable化
これをStartAsCoroutineで書くと
コルーチン
これをStartAsCoroutineで書くと
Animation開始
これをStartAsCoroutineで書くと
コールバックが来るまで
yield return
これをStartAsCoroutineで書くと
コールバックが混ざった非同期処理を
同期処理っぽく書けた
これをStartAsCoroutineで書くと
一時保存変数も消え去った

はじめてのUniRx