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.

未来のプログラミング技術をUnityで -UniRx-

90,050 views

Published on

スライド中で登場するサンプルはこちら
http://torisoup.net/unirx-examples/

UniRxを使えば「非同期処理」「イベント処理」「判定が複数フレームにまたがる処理」といった時間が絡んだ処理全般をとても簡単に記述できるようになります。今回はUniRxの便利な利用例をいくつか紹介したいと思います。

Published in: Technology
  • Be the first to comment

未来のプログラミング技術をUnityで -UniRx-

  1. 1. 未来のプログラミング技術をUnityで ~UniRx~ @toRisouP 2015/03/20
  2. 2. スライド中に登場するサンプル http://torisoup.net/unirx-examples/
  3. 3. 【2015/10/5 】 一部加筆修正しました
  4. 4. 合わせて読みたい • もう少し踏み込んだ使い方の話はこちらのスライドで • http://www.slideshare.net/torisoup/uni-rx
  5. 5. 自己紹介 • とりすーぷ(@toRisouP) • 26歳 • 本業はWebエンジニア • 趣味でUnityでゲーム作ってます
  6. 6. 過去に作ったもの 交通事故・渋滞シミュレータ (sm16238908) みくみくまうす (ニコ生配信支援ツール) NITORI BOX (東方2次創作ゲーム)
  7. 7. みくみくまうす • ニコ生の配信支援ツール • MMDモデルがコメントを読み上げる • Unity製 • フリーソフトとして公開中 http://mikumikumouth.net/
  8. 8. 今作ってるゲーム みこバト~レ • 東方2次創作の同人ゲーム • Unity + PhotonCloud • 例大祭と夏コミあたりで体験版出したい…
  9. 9. ところで
  10. 10. 「マウスのダブルクリック判定」 実装できますか?
  11. 11. マウスのダブルクリック判定 • どうやって実装する? – 最後にクリックされてから一定時間以内ならダブルクリック? – クリック回数の変数とタイマの変数をフィールドに定義? – Update内に判定処理を書く?
  12. 12. めんどい
  13. 13. これをUniRxを使うと たったの数行で済みます
  14. 14. たったのこれだけ!
  15. 15. すごくね?
  16. 16. 今回の発表の目標 実際の使用例を通して UniRxの凄さ便利さを伝えたい!
  17. 17. ターゲット LINQは使えるレベルの人 プログラミング始めたばかりの人には少々厳しいかも
  18. 18. 端折った説明や 厳密ではない説明をします ご了承ください (わかりやすさ第一で行きます)
  19. 19. もくじ 1. UniRxって何? 2. UniRxって何が便利なの? 3. ストリームを使うメリットと例 4. よく使うオペレータ解説 5. Unity上での実用例5つ 6. まとめ
  20. 20. UniRxって何?
  21. 21. UniRx • Reactive Extensions for Unity • 作者は@neueccさん • MITライセンスで公開 • AssetStoreまたはgithubからダウンロード可(無料)
  22. 22. Reactive Extensionsとは • FanctionalReactivePrograming をC#で実現するためのライブラリ – LINQ to Events – 元はMicrosoftReserchが開発 • 決してオレオレライブラリでは無い! • 最近になって流行の兆しが来ている – いろんな言語に移植されている • RxJS、RxJava、ReactiveCocoa、RxPy、UniRx… – どんな言語でもRxの考え方は共通している! • 覚えておいて損は絶対にしない!
  23. 23. UniRxは何が便利なの?
  24. 24. UniRxを使うと 時間の取り扱いが とても簡単になります
  25. 25. 時間が絡んだ処理の例 • イベントの待ち受け – マウスクリックやボタン入力のタイミングで処理をする • 非同期処理 – 別スレッドで通信したり、別スレッドでデータロードしたり • 時間計測が判定に必要な処理 – 長押し、ダブルクリックの判定 • 時間変化する値の監視 – False→Trueになった瞬間に1回だけ処理したい
  26. 26. こういった処理をRxを使うと とても簡潔に記述できます
  27. 27. まずはRxの基本的な 考え方を実際のコードを見せつつ 説明します
  28. 28. ボタンがクリックされたら画面に表示する Buttonをクリックした時にTextに”Clicked”と表示してみる
  29. 29. クリックされたら画面に表示するスクリプト ←メイン処理
  30. 30. クリックされたら画面に表示するスクリプト ←Unity側が用意しているクリックイベント
  31. 31. クリックされたら画面に表示するスクリプト ←イベントをストリームに変換
  32. 32. クリックされたら画面に表示するスクリプト ←ストリームの購読 (最終的に何をするか書く)
  33. 33. ストリーム • 「イベントが流れる配管」みたいなイメージ – 難しく言えば「時間軸上に並んだイベントのシーケンス」 – 分岐させたり合流させたりできる • コード中では IObservable<T> として扱われる – LINQで言うIEnumerable<T>に相当 イベントメッセージ イベントの流れそのものが「ストリーム」
  34. 34. ストリームに流すイベント「メッセージ」 メッセージは3種類ある • OnNext – 通常使用するメッセージ – 普通はこれを使う • OnError – エラー発生時の例外を通知するメッセージ • OnCompleted – ストリームが終了した事を通知するメッセージ
  35. 35. ボタンは 「クリック時にイベントをストリームに流している」 と考えることができる ストリームとボタンクリック Button ボタンがクリックされたタイミングで ストリームにメッセージを送り込む(OnNext)
  36. 36. • ストリームの末端でメッセージが来た時に何をするかを定義する • ストリームはSubscribeされた瞬間に生成される – 基本的にSubscribeしない限りストリームは動かない – Subscribeのタイミングによって結果が変わる可能性がある • OnError, OnCompleteが来るとSubscribeは終了する Subscribe(ストリームの購読) Subscribe ストリームを購読して メッセージが来た時に処理をする ストリーム
  37. 37. (補足)Subscribeとメッセージ Subscribeはオーバーロードで複数定義されているので用途に合わせて使う と良い • OnNextのみ • OnNext & OnCompleted • OnNext & OnError & OnCompleted
  38. 38. 全体の流れ ← ボタンクリックイベントを ストリームに変換し メッセージが到着した時に テキストに”Clicked”を表示する
  39. 39. Subscribeのタイミング Awake()/Start()でSubscribeするべき Update()に書くと無数のストリームが生成される
  40. 40. ちなみに UniRxには、 uGUI用のObservableやSubscribeが準備されている ↑さっきの奴はこれくらい簡略化して書ける
  41. 41. 「ストリーム」という考え方 のメリット
  42. 42. イベントの 射影、フィルタリング、合成 などができる
  43. 43.
  44. 44. Buttonが3回押されたらTextに表示する • ボタンがクリックされた回数をカウントする? – カウンタ用の変数をフィールドに定義する?
  45. 45. Buttonが3回押されたらTextに表示する • Buffer(3)を加えるだけ! – 余計なフィールド変数不要! – なお、Skip(2)でも同じ動作をする • ここではわかりやすくBufferを使ったが、 n回後に動作するというシチュエーションではSkipの方が適切ではある
  46. 46. Buffer • メッセージを蓄えて特定のタイミングで流す – 放出する条件はいろいろ指定できる • n個溜まったら流す • 別のストリームにメッセージが流れてきたら流す 画像はhttp://reactivex.io/documentation/operators/map.htmlより引用
  47. 47. 例2
  48. 48. Buttonが2つとも押されたらTextに表示する • 両方が交互に1回ずつ押された時にTextに表示する – 連打しても「1回押された」と判定させる
  49. 49. Zip • 複数本のストリームのメッセージが全て揃うまで待つ – メッセージが揃った時に1個ずつ取り出して後続に流す – 揃ったメッセージは任意に加工して出力できる 画像はhttp://reactivex.io/documentation/operators/zip.htmlより引用
  50. 50. Buttonが2つとも押されたらTextに表示する
  51. 51. Buttonが2つとも押されたらTextに表示する ←1度動作した後にZip内のバッファをクリアする (後で説明します)
  52. 52. Rxを使わない従来のやり方では、 イベントを受け取った後に どうするかを書いていた
  53. 53. Rxでは イベントを受け取る前に 何をしたいかが書ける
  54. 54. 「ストリームを加工して 自分が欲しいイベントだけ 通知させればいいじゃん!」
  55. 55. まとめると、 Rxは 1.ストリームを用意して 2.ストリームをオペレータで加工して 3.最後にSubscribeする という考え方で使われる
  56. 56. オペレータ ストリームをこねこねするもの
  57. 57. オペレータ • ストリームに操作を加える関数のこと • メチャクチャたくさんある Select, Where, Skip, SkipUntil, SkipWhile, Take, TakeUntil, TakeWhile, Throttle, Zip, Merge, CombineLatest, Distinct, DistinctUntilChanged, Delay, DelayFrame, First, FirstOfDefault, Last, LastOfDefault, StartWith, Concat, Buffer, Cast, Catch, CatchIgnore, ObserveOn, Do, Sample, Scan, Single, SingleOrDefault, Retry, Repeat, Time, TimeStamp, TimeInterval…
  58. 58. よく使うオペレータ紹介
  59. 59. Where • 条件を満たすメッセージのみ通過させるオペレータ – 他の言語では「filter」とも呼ばれる 画像はhttp://reactivex.io/documentation/operators/filter.htmlより引用
  60. 60. Select • 要素の値を射影(変換)する – 他の言語では「map」とも呼ばれる 画像はhttp://reactivex.io/documentation/operators/map.htmlより引用
  61. 61. SelectMany • 新たなストリームを生成して、そのストリームが流すメッセージを本流のス トリームのメッセージとして扱う – ストリームを別ストリームで差し替えるイメージ(厳密に言うと違う) – 他の言語では「flatMap」とも呼ばれる 画像はhttp://reactivex.io/documentation/operators/flatmap.htmlより引用
  62. 62. Throttle/ThrottleFrame • 落ち着いた時に最後のメッセージを流す – メッセージが集中して流れてきたら最後以外を無視する – 他の言語では「debounce」とも呼ばれる – よく使う 画像はhttp://reactivex.io/documentation/operators/debounce.htmlより引用
  63. 63. ThrottleFirst/ThrottleFirstFrame • 最初にメッセージが来てから一定時間遮断する – 1つメッセージがそこからしばらくメッセージを遮断する – 大量に流れてきたデータのうち最初だけ使いたいみたいなときに有 効 画像はhttp://reactivex.io/documentation/operators/sample.htmlより引用
  64. 64. Delay/DelayFrame • メッセージの伝達を遅延させる 画像はhttp://reactivex.io/documentation/operators/delay.htmlより引用
  65. 65. DistinctUntilChanged • メッセージが変化した時のみ通知する – 同じ値が連続している場合は無視する 画像はhttp://rxmarbles.com/#distinctUntilChangedより引用
  66. 66. SkipUntil • 指定したストリームにメッセージが来るまで メッセージをSkipする 画像はhttp://rxmarbles.com/#skipUntilより引用
  67. 67. TakeUntil • 指定したストリームにメッセージが来たら、 自身のストリームにOnCompletedを流して終了させる 画像はhttp://rxmarbles.com/#takeUntilより引用
  68. 68. TakeUntil • 指定したストリームにメッセージが来たら、 自身のストリームにOnCompletedを流して終了させる
  69. 69. Repeat • ストリームがOnCompletedで終了した時にもう一度Subscribe を行う
  70. 70. SkipUntil+TakeUntil+Repeat • よく使う組み合わせ – イベントAが来てからイベントBが来るまでの間だけ処理したいような 時に使う
  71. 71. SkipUntil+TakeUntil+Repeatの例 例)ドラッグでオブジェクトを回転させる – MouseDownが来てからMouseUpが来るまで処理したい
  72. 72. First • ストリームに最初に来たメッセージのみを流す – OnNextの直後にOnCompleteも流れる 画像はhttp://reactivex.io/documentation/operators/first.htmlより引用
  73. 73. さっきのZipの例でFirst+Repeatを使った意図 First+Repeatで1回動作する度にストリームを作り直している (Zip内のメッセージキューをリセットするため)
  74. 74. ここまでが基礎 基礎の説明だけでスライド70枚突破してる
  75. 75. ここから実例を紹介
  76. 76. 実際の使用例5つ 1. ダブルクリック判定 2. 値の変動を監視する 3. 値の変動を丸める 4. WWWを使いやすくする 5. PhotonCloudと組み合わせる
  77. 77. 1.ダブルクリック判定
  78. 78. ダブルクリック検知のコード
  79. 79. クリックストリームの定義 クリックのストリームを定義(≠生成)
  80. 80. クリックストリームの定義 【10/5 加筆】現在はUniRx.Triggersをusingに加えた上で、 this.UpdateAsObservable()で呼び出せます
  81. 81. クリックストリーム UpdateAsObservable() Updateのタイミングを通知 Where() クリックがあったフレームのみ通過 clickStream クリックイベントのストリーム
  82. 82. よくみると2つのストリームがある
  83. 83. よくみると2つのストリームがある
  84. 84. 意味 クリックストリームを塞き止めてまとめる。 開放条件は「最後にクリックされてから200ミリ秒経過した時」である。
  85. 85. クリックストリーム clickStream マウスクリックのストリーム Throttle(200ms) 200ms間イベントが 起きなかったら通知 200ms Buffer イベントをまとめる 終了条件はThrottle イベントが来るまで 3 1 2
  86. 86. 2.値の変動を監視する
  87. 87. プレイヤが着地した瞬間にエフェクトを再生する
  88. 88. 着地した瞬間の検知方法 1. CharacterController.isGroundedを毎フレーム監視 2. 現フレームにおける値をフィールド変数に保存 3. 次フレームでFalse → Trueに変わった時にエフェクトを再生する
  89. 89. UniRxを使わずに着地した瞬間の検知をしてみる
  90. 90. UniRxを使わずに着地した瞬間の検知をしてみる パッと見で何やってるか判断できない! ←一時保存用のためだけのフィールド変数!!
  91. 91. UniRxで着地した瞬間の検知をしてみる
  92. 92. UniRxで着地した瞬間の検知をしてみる ここだけ! フィールド変数なんぞいらん!
  93. 93. 着地判定のイメージ図 F F F T T T T F F F T F T UpdateAsObservable() Updateのタイミングを通知 Select() IsGroundedの値に差し替え DistinctUntilChanged() 値に変化があった時のみ通過 Where() 値がTrueの時のみ通過 Subscribe() isGroundedがFalse→Trueになった瞬間が通知される
  94. 94. 【追記】ObserveEveryValueChanged 毎フレーム値の変動を監視するなら「ObserveEveryValueChanged」の方がシ ンプルにかける
  95. 95. 3.値の変動を丸める
  96. 96. isGroundedの変動を丸める • isGroundedの精度の改善 – 斜面を移動するとTrue/Falseが激しく変動する – この値の変動をUniRxで抑えこんでみる
  97. 97. 方針 • isGroundedの変動をThrottleで無視させる – DistinctUntilChangedと併用すればOK
  98. 98. UniRxでisGroundedの変動を丸める
  99. 99. isGroundedの丸め込みイメージ図 T F T T T T T T T T T UpdateAsObservable() Updateのタイミングを通知 Select() IsGroundedの値に差し替え DistinctUntilChanged() 値に変化があった時のみ通過 ThrottleFrame(5) 値が5フレームの間 来なかったら最後の値を放流 Subscribe() isGroundedが6フレーム以上 安定していた時に最後の値が通知される F T 1 2 3 4 5
  100. 100. 4.WWWを使いやすくする
  101. 101. UnityのWWW • Unityの標準のHTTP通信用モジュール – コルーチンとして使う必要があり – そこまで使い勝手は良くない(HttpWebRequestよりはだいぶマシだけどさ…)
  102. 102. ObservableWWW • WWWをObservableとして扱えるようにしたもの – Subscribeされた瞬間に通信が行われる – 後は勝手に裏で通信して結果がストリームに流れてくる – コルーチンを使わずにかける
  103. 103. 例)ボタンが押されたらテクスチャを読み込む • ボタンが押されたら指定URLのテクスチャ画像をダウンロード してImageに表示する
  104. 104. WWWでテクスチャ読み込み
  105. 105. WWWでテクスチャ読み込み クリックストリームをObservableWWWの ストリームで上書きする ←ボタンを連打されても通信は1回しかさせないためにFirstを入れる
  106. 106. WWWでテクスチャ読み込み 複雑なことも上から読めば何やってるかすぐわかる 1. ボタンがクリックされたら 2. HTTPでテクスチャ画像をダウンロードして 3. その結果をSpriteに変換し 4. Imageとして表示する
  107. 107. タイムアウトを付け加える タイムアウトが欲しいならここにオペレータを挟むだけ
  108. 108. ObservableWWWでいろいろ • 同時に通信して全てデータが揃ったら処理を進める
  109. 109. ObservableWWWでいろいろ • 前の通信の結果を使って次の通信を行う – サーバに「リソースへのURL」を問い合わせて、サーバに教えてもらっ たURLからデータをダウンロードする resourcepath.txtの中に書かれたURLへアクセスするコード
  110. 110. 5.Photon Cloudと組み合わせる
  111. 111. PhotonCloud • Unityで簡単にネットワーク対戦が実装できるライブラリ • 通知が全てコールバックで微妙に使い勝手が悪い – UniRxでなんとかしたい
  112. 112. • PhotonCloudのコールバックがストリームに変換される – ごちゃごちゃしたコールバックは隠蔽される UniRxと組み合わせると? PhotonCloud コールバックで ごちゃごちゃした世界 最新の部屋情報の通知ストリーム ロビー参加に成功した通知 ストリーム 部屋に参加成功した通知 ストリーム UniRxでコールバックを隠蔽
  113. 113. コールバックからストリームに変換するメリット • IDEの補完が効くようになる – PhotonのコールバックはSendMessageで呼び出される – Observableとしてちゃんと定義してあげれば補完が効く • 多様なオペレータによる柔軟な制御ができるようになる – ログインに失敗したら3秒後にリトライ試すとか – 全員からレスポンスがあった時に処理をするとか – 部屋情報リストが更新されたことを通知するとか
  114. 114. コールバックからストリームに変換するメリット • IDEの補完が効くようになる – PhotonのコールバックはSendMessageで呼び出される – Observableとしてちゃんと定義してあげれば補完が効く • 多様なオペレータによる柔軟な制御ができるようになる – ログインに失敗したら3秒後にリトライ試すとか – 全員からレスポンスがあった時に処理をするとか – 部屋情報リストが更新されたことを通知するとか
  115. 115. 例)最新の部屋情報を通知するストリームを作る • OnRevicedRoomListUpdate – PhotonNetwork.GetRoomList()が更新された時に実行される – これをストリームにしてしまう
  116. 116. つまりこういう形にしたい 最新のRoomInfo[]が流れるストリーム これをSubscribeしておけば、 部屋リストに更新があったことがすぐわかる
  117. 117. ストリームの根源の作り方 • Observableのファクトリメソッドを使う • 既存のイベント等から変換する • Subject<T>系を使う • ReactiveProperty<T>を使う
  118. 118. ReactiveProperty • Subscribeすることができる変数 • ObservableとしてSubscribeができる • 値を書き込んだ時にOnNextメッセージが飛ぶ
  119. 119. ReactivePropertyで部屋情報を通知する
  120. 120. ReactivePropertyで部屋情報を通知する OnRecivedRoomListUpdateのタイミングで _reactiveRoomsの値を書き換え、同時にストリームに通知が飛ぶ
  121. 121. ReactivePropertyで部屋情報を通知する Observableとしてクラスの外に公開
  122. 122. 受信側(さっきと同じスライド) コールバック地獄からの開放!
  123. 123. 【追記】PhotonRx • PhotonRx – PUNの使い勝手を上げるライブラリ – 各コールバックをObservableとして取得できる – 詳しくは以下のスライドをどうぞ – http://www.slideshare.net/torisoup/unirxpun
  124. 124. 紹介したい機能は まだまだたくさんある
  125. 125. これ以上話すと さすがに詰め込み過ぎなので ココらへんで終わります (というか既に詰め込み過ぎ)
  126. 126. 補足スライドも用意してあるので そちらも見てね
  127. 127. まとめ • UniRxは便利なので使ってみよう!!!!!!!! – 「時間」をすごい簡単に扱えるようになる – GUI周りの実装もスッキリ書ける – ゲームロジックに適用することもできる • UniRxは便利だが難しい面もある – 学習コストが高くて概念的にも難しい – 導入する場合はプログラムの設計から考え直す必要が出てくる • 真価を発揮させるには設計の根幹にUniRxがガッツリ食い込む • 「便利ライブラリ」ではなく「言語拡張」だと思う必要がある
  128. 128. ありがとうございました @toRisouP
  129. 129. 以下補足とか
  130. 130. 参考にするとよいサイト • ReactiveX – http://reactivex.io/ – Rxについて細かく解説されているサイト(ただし英語) • Reactive extensions入門v0.1 – http://www.slideshare.net/okazuki0130/reactive-extensionsv01 – @okazukiさんがまとめてくれたRxについての日本語資料 – すごいよくまとまっているので1度目を通すべし • Rx入門 – http://blog.xin9le.net/entry/rx-intro – じんぐる(@xin9le)さんがRxについてまとめてくれた日本語サイト
  131. 131. 参考にするとよいサイト2 • Qiita - UniRxについて書いた記事をまとめてみた – http://qiita.com/toRisouP/items/48b9fa25df64d3c6a392 – 自分がUniRxを使う上でのメモ書きとして残したもの – 参考になれば
  132. 132. 補足)Subject<T> • ストリームの根源を作るもの – Subject,ReplySubject,BehaviorSubject,AsyncSubjectと複数種類あり – 外に公開するときは必ずAsObservableを挟んで公開する • 外から直接OnNextが叩ける状態にしない
  133. 133. • Disposeを呼び出すとSubscribeが中止される – ストリームの根源が削除されると自動的にDisposeされる – ストリームが終了状態になってもDisposeされる – staticなストリームを作った場合は手動Disposeが必要 補足)Subscribeの止め方 button.onClickで作ったストリームは Buttonがシーンから削除されたタイミングでDisposeされる
  134. 134. 補足)UpdateAsObservableとObservable.EveryUpdate どちらもUpdate()のタイミングを通知してくれるObservable • UpdateAsObservable – ObservableMonoBehaviour を継承すると使える – (追記)ObservableMonoBehabiourを継承する方式は非推奨になりました 代わりにUniRx.Triggers名前空間に用意されている拡張メソッドの方 を使いましょう – IObservable<Unit> – コンポーネントのDestory時に自動Disposeされる • Observable.EveryUpdate – どのスクリプトからでも使える – IObservable<long> (Subscribeした時からのフレーム数) – 使い終わったら明示的にDisposeする必要がある • またはOnCompleteがちゃんと発火するストリームにする
  135. 135. おまけ
  136. 136. おまけ)コルーチンをObservableに変換する • Observable.FromCoroutineを使うと変換できる – コルーチンの実行順序や実行条件をストリームで定義できる コルーチンAが終わったらコルーチンBを実行する例
  137. 137. おまけ)UniRx+コルーチン • FromCoroutine<T>を使うと自由なストリームが作れる カウントダウンタイマの例
  138. 138. 続き)カウントダウンタイマを作るのなら… • ただしFromCoroutine<T>でカウントダウンタイマを作るよりも Observable.Timerで作った方がスマート
  139. 139. おまけ)HttpWebRequest を使ってPUT/DELETE • WWW/ObservableWWWはGETとPOSTのみサポート – PUT/DELETEを使いたい場合HttpWebRequestを使う必要がある • HttpWebRequestをそのまま使うとそこそこツライ – 同期処理で書くとスレッドを分離する必要がある – 非同期処理で書くのも結構めんどくさい
  140. 140. おまけ)UniRxでHttpWebRequest • HttpWebReqeustでDELETEを叩く – Observable.Startを使えば別スレッドで実行できる – それをObserveOnでメインスレッドに戻す
  141. 141. おまけ)ObserveOn • 処理を行うスレッドを切り替えるオペレータ – ObserveOnを使えばスレッド間でのデータのやり取りを考慮する必要 は無い
  142. 142. 応用例)テキスト入力された時に検索サジェストを出す 1. InputFiledにテキストが入力された時に 2. 最後に入力されてから200m秒以上間隔が開いたら 3. GoogleSuggestAPIを叩いて 4. その時のサジェスト結果をTextに表示する
  143. 143. 応用例)テキスト入力された時に検索サジェストを出す

×