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

Post on 15-Jul-2015

24.704 views 8 download

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

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

@toRisouP

2015/03/20

スライド中に登場するサンプル

http://torisoup.net/unirx-examples/

【2015/10/5 】

一部加筆修正しました

合わせて読みたい

• もう少し踏み込んだ使い方の話はこちらのスライドで

• http://www.slideshare.net/torisoup/uni-rx

自己紹介

• とりすーぷ(@toRisouP)

• 26歳

• 本業はWebエンジニア

• 趣味でUnityでゲーム作ってます

過去に作ったもの

交通事故・渋滞シミュレータ

(sm16238908)

みくみくまうす

(ニコ生配信支援ツール)

NITORI BOX

(東方2次創作ゲーム)

みくみくまうす

• ニコ生の配信支援ツール

• MMDモデルがコメントを読み上げる

• Unity製

• フリーソフトとして公開中

http://mikumikumouth.net/

今作ってるゲーム

みこバト~レ

• 東方2次創作の同人ゲーム

• Unity + PhotonCloud

• 例大祭と夏コミあたりで体験版出したい…

ところで

「マウスのダブルクリック判定」

実装できますか?

マウスのダブルクリック判定

• どうやって実装する?

– 最後にクリックされてから一定時間以内ならダブルクリック?

– クリック回数の変数とタイマの変数をフィールドに定義?

– Update内に判定処理を書く?

めんどい

これをUniRxを使うと

たったの数行で済みます

たったのこれだけ!

すごくね?

今回の発表の目標

実際の使用例を通して

UniRxの凄さ便利さを伝えたい!

ターゲット

LINQは使えるレベルの人プログラミング始めたばかりの人には少々厳しいかも

端折った説明や

厳密ではない説明をします

ご了承ください(わかりやすさ第一で行きます)

もくじ

1. UniRxって何?

2. UniRxって何が便利なの?

3. ストリームを使うメリットと例

4. よく使うオペレータ解説

5. Unity上での実用例5つ

6. まとめ

UniRxって何?

UniRx

• Reactive Extensions for Unity

• 作者は@neueccさん

• MITライセンスで公開

• AssetStoreまたはgithubからダウンロード可(無料)

Reactive Extensionsとは

• FanctionalReactivePrograming をC#で実現するためのライブラリ

– LINQ to Events

– 元はMicrosoftReserchが開発

• 決してオレオレライブラリでは無い!

• 最近になって流行の兆しが来ている

– いろんな言語に移植されている

• RxJS、RxJava、ReactiveCocoa、RxPy、UniRx…

– どんな言語でもRxの考え方は共通している!

• 覚えておいて損は絶対にしない!

UniRxは何が便利なの?

UniRxを使うと

時間の取り扱いが

とても簡単になります

時間が絡んだ処理の例

• イベントの待ち受け

– マウスクリックやボタン入力のタイミングで処理をする

• 非同期処理

– 別スレッドで通信したり、別スレッドでデータロードしたり

• 時間計測が判定に必要な処理

– 長押し、ダブルクリックの判定

• 時間変化する値の監視

– False→Trueになった瞬間に1回だけ処理したい

こういった処理をRxを使うと

とても簡潔に記述できます

まずはRxの基本的な

考え方を実際のコードを見せつつ

説明します

ボタンがクリックされたら画面に表示する

Buttonをクリックした時にTextに”Clicked”と表示してみる

クリックされたら画面に表示するスクリプト

←メイン処理

クリックされたら画面に表示するスクリプト

←Unity側が用意しているクリックイベント

クリックされたら画面に表示するスクリプト

←イベントをストリームに変換

クリックされたら画面に表示するスクリプト

←ストリームの購読(最終的に何をするか書く)

ストリーム

• 「イベントが流れる配管」みたいなイメージ

– 難しく言えば「時間軸上に並んだイベントのシーケンス」

– 分岐させたり合流させたりできる

• コード中では IObservable<T> として扱われる

– LINQで言うIEnumerable<T>に相当

イベントメッセージ

イベントの流れそのものが「ストリーム」

ストリームに流すイベント「メッセージ」

メッセージは3種類ある

• OnNext

– 通常使用するメッセージ

– 普通はこれを使う

• OnError

– エラー発生時の例外を通知するメッセージ

• OnCompleted

– ストリームが終了した事を通知するメッセージ

ボタンは

「クリック時にイベントをストリームに流している」

と考えることができる

ストリームとボタンクリック

Button

ボタンがクリックされたタイミングでストリームにメッセージを送り込む(OnNext)

• ストリームの末端でメッセージが来た時に何をするかを定義する

• ストリームはSubscribeされた瞬間に生成される

– 基本的にSubscribeしない限りストリームは動かない

– Subscribeのタイミングによって結果が変わる可能性がある

• OnError, OnCompleteが来るとSubscribeは終了する

Subscribe(ストリームの購読)

Subscribeストリームを購読して

メッセージが来た時に処理をする

ストリーム

(補足)Subscribeとメッセージ

Subscribeはオーバーロードで複数定義されているので用途に合わせて使う

と良い

• OnNextのみ

• OnNext & OnCompleted

• OnNext & OnError & OnCompleted

全体の流れ

←ボタンクリックイベントをストリームに変換しメッセージが到着した時にテキストに”Clicked”を表示する

Subscribeのタイミング

Awake()/Start()でSubscribeするべきUpdate()に書くと無数のストリームが生成される

ちなみに

UniRxには、

uGUI用のObservableやSubscribeが準備されている

↑さっきの奴はこれくらい簡略化して書ける

「ストリーム」という考え方

のメリット

イベントの

射影、フィルタリング、合成

などができる

Buttonが3回押されたらTextに表示する

• ボタンがクリックされた回数をカウントする?

– カウンタ用の変数をフィールドに定義する?

Buttonが3回押されたらTextに表示する

• Buffer(3)を加えるだけ!

– 余計なフィールド変数不要!

– なお、Skip(2)でも同じ動作をする

• ここではわかりやすくBufferを使ったが、

n回後に動作するというシチュエーションではSkipの方が適切ではある

Buffer

• メッセージを蓄えて特定のタイミングで流す

– 放出する条件はいろいろ指定できる

• n個溜まったら流す

• 別のストリームにメッセージが流れてきたら流す

画像はhttp://reactivex.io/documentation/operators/map.htmlより引用

例2

Buttonが2つとも押されたらTextに表示する

• 両方が交互に1回ずつ押された時にTextに表示する

– 連打しても「1回押された」と判定させる

Zip

• 複数本のストリームのメッセージが全て揃うまで待つ

– メッセージが揃った時に1個ずつ取り出して後続に流す

– 揃ったメッセージは任意に加工して出力できる

画像はhttp://reactivex.io/documentation/operators/zip.htmlより引用

Buttonが2つとも押されたらTextに表示する

Buttonが2つとも押されたらTextに表示する

←1度動作した後にZip内のバッファをクリアする(後で説明します)

Rxを使わない従来のやり方では、

イベントを受け取った後に

どうするかを書いていた

Rxでは

イベントを受け取る前に

何をしたいかが書ける

「ストリームを加工して

自分が欲しいイベントだけ

通知させればいいじゃん!」

まとめると、

Rxは

1.ストリームを用意して

2.ストリームをオペレータで加工して

3.最後にSubscribeする

という考え方で使われる

オペレータストリームをこねこねするもの

オペレータ

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

• メチャクチャたくさんある

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…

よく使うオペレータ紹介

Where

• 条件を満たすメッセージのみ通過させるオペレータ

– 他の言語では「filter」とも呼ばれる

画像はhttp://reactivex.io/documentation/operators/filter.htmlより引用

Select

• 要素の値を射影(変換)する

– 他の言語では「map」とも呼ばれる

画像はhttp://reactivex.io/documentation/operators/map.htmlより引用

SelectMany

• 新たなストリームを生成して、そのストリームが流すメッセージを本流のス

トリームのメッセージとして扱う

– ストリームを別ストリームで差し替えるイメージ(厳密に言うと違う)

– 他の言語では「flatMap」とも呼ばれる

画像はhttp://reactivex.io/documentation/operators/flatmap.htmlより引用

Throttle/ThrottleFrame

• 落ち着いた時に最後のメッセージを流す

– メッセージが集中して流れてきたら最後以外を無視する

– 他の言語では「debounce」とも呼ばれる

– よく使う

画像はhttp://reactivex.io/documentation/operators/debounce.htmlより引用

ThrottleFirst/ThrottleFirstFrame

• 最初にメッセージが来てから一定時間遮断する

– 1つメッセージがそこからしばらくメッセージを遮断する

– 大量に流れてきたデータのうち最初だけ使いたいみたいなときに有

画像はhttp://reactivex.io/documentation/operators/sample.htmlより引用

Delay/DelayFrame

• メッセージの伝達を遅延させる

画像はhttp://reactivex.io/documentation/operators/delay.htmlより引用

DistinctUntilChanged

• メッセージが変化した時のみ通知する

– 同じ値が連続している場合は無視する

画像はhttp://rxmarbles.com/#distinctUntilChangedより引用

SkipUntil

• 指定したストリームにメッセージが来るまで

メッセージをSkipする

画像はhttp://rxmarbles.com/#skipUntilより引用

TakeUntil

• 指定したストリームにメッセージが来たら、

自身のストリームにOnCompletedを流して終了させる

画像はhttp://rxmarbles.com/#takeUntilより引用

TakeUntil

• 指定したストリームにメッセージが来たら、

自身のストリームにOnCompletedを流して終了させる

Repeat

• ストリームがOnCompletedで終了した時にもう一度Subscribe

を行う

SkipUntil+TakeUntil+Repeat

• よく使う組み合わせ

– イベントAが来てからイベントBが来るまでの間だけ処理したいような

時に使う

SkipUntil+TakeUntil+Repeatの例

例)ドラッグでオブジェクトを回転させる

– MouseDownが来てからMouseUpが来るまで処理したい

First

• ストリームに最初に来たメッセージのみを流す

– OnNextの直後にOnCompleteも流れる

画像はhttp://reactivex.io/documentation/operators/first.htmlより引用

さっきのZipの例でFirst+Repeatを使った意図

First+Repeatで1回動作する度にストリームを作り直している(Zip内のメッセージキューをリセットするため)

ここまでが基礎基礎の説明だけでスライド70枚突破してる

ここから実例を紹介

実際の使用例5つ

1. ダブルクリック判定

2. 値の変動を監視する

3. 値の変動を丸める

4. WWWを使いやすくする

5. PhotonCloudと組み合わせる

1.ダブルクリック判定

ダブルクリック検知のコード

クリックストリームの定義

クリックのストリームを定義(≠生成)

クリックストリームの定義

【10/5 加筆】現在はUniRx.Triggersをusingに加えた上で、this.UpdateAsObservable()で呼び出せます

クリックストリーム

UpdateAsObservable()Updateのタイミングを通知

Where()クリックがあったフレームのみ通過

clickStreamクリックイベントのストリーム

よくみると2つのストリームがある

よくみると2つのストリームがある

意味

クリックストリームを塞き止めてまとめる。開放条件は「最後にクリックされてから200ミリ秒経過した時」である。

クリックストリーム

clickStreamマウスクリックのストリーム

Throttle(200ms)200ms間イベントが起きなかったら通知

200ms

Bufferイベントをまとめる終了条件はThrottleイベントが来るまで

3 1 2

2.値の変動を監視する

プレイヤが着地した瞬間にエフェクトを再生する

着地した瞬間の検知方法

1. CharacterController.isGroundedを毎フレーム監視

2. 現フレームにおける値をフィールド変数に保存

3. 次フレームでFalse → Trueに変わった時にエフェクトを再生する

UniRxを使わずに着地した瞬間の検知をしてみる

UniRxを使わずに着地した瞬間の検知をしてみる

パッと見で何やってるか判断できない!

←一時保存用のためだけのフィールド変数!!

UniRxで着地した瞬間の検知をしてみる

UniRxで着地した瞬間の検知をしてみる

ここだけ!フィールド変数なんぞいらん!

着地判定のイメージ図

F F F T T T T F F

F T F

T

UpdateAsObservable()Updateのタイミングを通知

Select()IsGroundedの値に差し替え

DistinctUntilChanged()値に変化があった時のみ通過

Where()値がTrueの時のみ通過

Subscribe()isGroundedがFalse→Trueになった瞬間が通知される

【追記】ObserveEveryValueChanged

毎フレーム値の変動を監視するなら「ObserveEveryValueChanged」の方がシ

ンプルにかける

3.値の変動を丸める

isGroundedの変動を丸める

• isGroundedの精度の改善

– 斜面を移動するとTrue/Falseが激しく変動する

– この値の変動をUniRxで抑えこんでみる

方針

• isGroundedの変動をThrottleで無視させる

– DistinctUntilChangedと併用すればOK

UniRxでisGroundedの変動を丸める

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

4.WWWを使いやすくする

UnityのWWW

• Unityの標準のHTTP通信用モジュール

– コルーチンとして使う必要があり

– そこまで使い勝手は良くない(HttpWebRequestよりはだいぶマシだけどさ…)

ObservableWWW

• WWWをObservableとして扱えるようにしたもの

– Subscribeされた瞬間に通信が行われる

– 後は勝手に裏で通信して結果がストリームに流れてくる

– コルーチンを使わずにかける

例)ボタンが押されたらテクスチャを読み込む

• ボタンが押されたら指定URLのテクスチャ画像をダウンロード

してImageに表示する

WWWでテクスチャ読み込み

WWWでテクスチャ読み込み

クリックストリームをObservableWWWのストリームで上書きする

←ボタンを連打されても通信は1回しかさせないためにFirstを入れる

WWWでテクスチャ読み込み

複雑なことも上から読めば何やってるかすぐわかる1. ボタンがクリックされたら2. HTTPでテクスチャ画像をダウンロードして3. その結果をSpriteに変換し4. Imageとして表示する

タイムアウトを付け加える

タイムアウトが欲しいならここにオペレータを挟むだけ

ObservableWWWでいろいろ

• 同時に通信して全てデータが揃ったら処理を進める

ObservableWWWでいろいろ

• 前の通信の結果を使って次の通信を行う

– サーバに「リソースへのURL」を問い合わせて、サーバに教えてもらっ

たURLからデータをダウンロードする

resourcepath.txtの中に書かれたURLへアクセスするコード

5.Photon Cloudと組み合わせる

PhotonCloud

• Unityで簡単にネットワーク対戦が実装できるライブラリ

• 通知が全てコールバックで微妙に使い勝手が悪い

– UniRxでなんとかしたい

• PhotonCloudのコールバックがストリームに変換される

– ごちゃごちゃしたコールバックは隠蔽される

UniRxと組み合わせると?

PhotonCloudコールバックで

ごちゃごちゃした世界

最新の部屋情報の通知ストリーム ロビー参加に成功した通知ストリーム

部屋に参加成功した通知ストリーム

UniRxでコールバックを隠蔽

コールバックからストリームに変換するメリット

• IDEの補完が効くようになる

– PhotonのコールバックはSendMessageで呼び出される

– Observableとしてちゃんと定義してあげれば補完が効く

• 多様なオペレータによる柔軟な制御ができるようになる

– ログインに失敗したら3秒後にリトライ試すとか

– 全員からレスポンスがあった時に処理をするとか

– 部屋情報リストが更新されたことを通知するとか

コールバックからストリームに変換するメリット

• IDEの補完が効くようになる

– PhotonのコールバックはSendMessageで呼び出される

– Observableとしてちゃんと定義してあげれば補完が効く

• 多様なオペレータによる柔軟な制御ができるようになる

– ログインに失敗したら3秒後にリトライ試すとか

– 全員からレスポンスがあった時に処理をするとか

– 部屋情報リストが更新されたことを通知するとか

例)最新の部屋情報を通知するストリームを作る

• OnRevicedRoomListUpdate

– PhotonNetwork.GetRoomList()が更新された時に実行される

– これをストリームにしてしまう

つまりこういう形にしたい

最新のRoomInfo[]が流れるストリームこれをSubscribeしておけば、部屋リストに更新があったことがすぐわかる

ストリームの根源の作り方

• Observableのファクトリメソッドを使う

• 既存のイベント等から変換する

• Subject<T>系を使う

• ReactiveProperty<T>を使う

ReactiveProperty

• Subscribeすることができる変数

• ObservableとしてSubscribeができる

• 値を書き込んだ時にOnNextメッセージが飛ぶ

ReactivePropertyで部屋情報を通知する

ReactivePropertyで部屋情報を通知する

OnRecivedRoomListUpdateのタイミングで_reactiveRoomsの値を書き換え、同時にストリームに通知が飛ぶ

ReactivePropertyで部屋情報を通知する

Observableとしてクラスの外に公開

受信側(さっきと同じスライド)

コールバック地獄からの開放!

【追記】PhotonRx

• PhotonRx

– PUNの使い勝手を上げるライブラリ

– 各コールバックをObservableとして取得できる

– 詳しくは以下のスライドをどうぞ

– http://www.slideshare.net/torisoup/unirxpun

紹介したい機能は

まだまだたくさんある

これ以上話すと

さすがに詰め込み過ぎなので

ココらへんで終わります(というか既に詰め込み過ぎ)

補足スライドも用意してあるので

そちらも見てね

まとめ

• UniRxは便利なので使ってみよう!!!!!!!!

– 「時間」をすごい簡単に扱えるようになる

– GUI周りの実装もスッキリ書ける

– ゲームロジックに適用することもできる

• UniRxは便利だが難しい面もある

– 学習コストが高くて概念的にも難しい

– 導入する場合はプログラムの設計から考え直す必要が出てくる

• 真価を発揮させるには設計の根幹にUniRxがガッツリ食い込む

• 「便利ライブラリ」ではなく「言語拡張」だと思う必要がある

ありがとうございました@toRisouP

以下補足とか

参考にするとよいサイト

• 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についてまとめてくれた日本語サイト

参考にするとよいサイト2

• Qiita - UniRxについて書いた記事をまとめてみた

– http://qiita.com/toRisouP/items/48b9fa25df64d3c6a392

– 自分がUniRxを使う上でのメモ書きとして残したもの

– 参考になれば

補足)Subject<T>

• ストリームの根源を作るもの

– Subject,ReplySubject,BehaviorSubject,AsyncSubjectと複数種類あり

– 外に公開するときは必ずAsObservableを挟んで公開する

• 外から直接OnNextが叩ける状態にしない

• Disposeを呼び出すとSubscribeが中止される

– ストリームの根源が削除されると自動的にDisposeされる

– ストリームが終了状態になってもDisposeされる

– staticなストリームを作った場合は手動Disposeが必要

補足)Subscribeの止め方

button.onClickで作ったストリームはButtonがシーンから削除されたタイミングでDisposeされる

補足)UpdateAsObservableとObservable.EveryUpdate

どちらもUpdate()のタイミングを通知してくれるObservable

• UpdateAsObservable

– ObservableMonoBehaviour を継承すると使える

– (追記)ObservableMonoBehabiourを継承する方式は非推奨になりました

代わりにUniRx.Triggers名前空間に用意されている拡張メソッドの方

を使いましょう

– IObservable<Unit>

– コンポーネントのDestory時に自動Disposeされる

• Observable.EveryUpdate

– どのスクリプトからでも使える

– IObservable<long> (Subscribeした時からのフレーム数)

– 使い終わったら明示的にDisposeする必要がある

• またはOnCompleteがちゃんと発火するストリームにする

おまけ

おまけ)コルーチンをObservableに変換する

• Observable.FromCoroutineを使うと変換できる

– コルーチンの実行順序や実行条件をストリームで定義できる

コルーチンAが終わったらコルーチンBを実行する例

おまけ)UniRx+コルーチン

• FromCoroutine<T>を使うと自由なストリームが作れる

カウントダウンタイマの例

続き)カウントダウンタイマを作るのなら…

• ただしFromCoroutine<T>でカウントダウンタイマを作るよりも

Observable.Timerで作った方がスマート

おまけ)HttpWebRequest を使ってPUT/DELETE

• WWW/ObservableWWWはGETとPOSTのみサポート

– PUT/DELETEを使いたい場合HttpWebRequestを使う必要がある

• HttpWebRequestをそのまま使うとそこそこツライ

– 同期処理で書くとスレッドを分離する必要がある

– 非同期処理で書くのも結構めんどくさい

おまけ)UniRxでHttpWebRequest

• HttpWebReqeustでDELETEを叩く

– Observable.Startを使えば別スレッドで実行できる

– それをObserveOnでメインスレッドに戻す

おまけ)ObserveOn

• 処理を行うスレッドを切り替えるオペレータ

– ObserveOnを使えばスレッド間でのデータのやり取りを考慮する必要

は無い

応用例)テキスト入力された時に検索サジェストを出す

1. InputFiledにテキストが入力された時に

2. 最後に入力されてから200m秒以上間隔が開いたら

3. GoogleSuggestAPIを叩いて

4. その時のサジェスト結果をTextに表示する

応用例)テキスト入力された時に検索サジェストを出す