Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

はじめてのUniRx

75,066 views

Published on

2015/06/19 UniRx勉強会での発表資料です

Published in: Technology
  • Be the first to comment

はじめてのUniRx

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

×