Upgrade to Pro — share decks privately, control downloads, hide ads and more …

UniRx とか Reactive Property とか

UniRx とか Reactive Property とか

「Unity のための C# 勉強会」での発表資料です (セッション タイトル決めるのをすっかり忘れていて、発表 5 分前に適当に埋めた!)。
・UniRx の紹介と、社内で実施したハンズオンをベースに簡単な解説
・Reactive Property を使った弊社の取り組み (MV(R)P) について

http://grabacr.net/archives/4711

Grabacr07

March 21, 2015
Tweet

More Decks by Grabacr07

Other Decks in Technology

Transcript

  1. Self Introduction Work 亀谷学人 – 株式会社グラニ C# + Unity でソーシャルゲーム開発

    Private Microsoft MVP for .NET (Visual C#) Room metro Tokyo staff (Windows クライアント開発 勉強会) Twitter: @Grabacr07 Facebook: manato.kameya Blog: http://grabacr.net/ 最先端の C# をフルに活用 CTO: @neuecc (UniRx 開発者)
  2. C# Everywhere Windows Desktop WinForms, WPF Windows 8 Windows Universal

    apps Mac application Xamarin.Mac Web application ASP .NET MVC, ASP .NET Web API Game Unity, Paradox, Unreal Engine Mobile (iOS, Android, WP) MonoTouch, Mono for Android Embedded, IoT .NET Framework Embedded Cloud Windows Azure, AWS
  3. Topics Unity 製ゲーム開発 Unity 5.0 + uGUI + UniRx 社内

    UniRx ハンズオン (勉強会? ハッカソン?) 的なことをやったり その内容を共有します! Goal Rx と UniRx を知ってもらいたい!
  4. Reactive Extensions (Rx) LINQ to Events LINQ が扱うデータ ソースの概念を「イベント」に広げる LINQ

    to Objects • Array • List<T> • Stream • etc… LINQ to XML • XML • JSON LINQ to Events • User (input) • Sensor device (input) • Web service (notification) • etc... あらゆるものがイベント
  5. イベントを、時間軸に乗ったシーケンスと見なす イベント ストリームにメッセージが流れてくる LINQ で使える時属性魔法 (Where でフィルター、Select で射影 (変換)、etc…) 時間をベースにした処理

    (計測、非同期、イベントの合成、etc…) Event stream Reactive Extensions (Rx) IObservable<T> onClick time onClick (3 秒後) onClick (5 秒後) onClick (1 秒後)
  6. Reactive Extensions (Rx) for .NET Microsoft の DevLabs で 2009

    年に公開されたプロジェクト Across Languages その後、多くの言語向けに移植されていく RxJS, bacon.js, RxJava, RxScala, ReactiveCocoa, Rx.rb, etc… つまり今アツい分野!
  7. UniRx Reactive Extensions for Unity https://github.com/neuecc/UniRx 弊社 CTO、@neuecc が Unity

    向けに移植した Rx オレオレライブラリではない (重要!) 言語は違っても、概念は同じ つまり、覚えておけば他の言語でも使える!
  8. Study 勉強用の資料 & 解説ページ等 Reactive Programming by UniRx for Asynchronous

    & Event Processing 開発者、弊社 CTO @neuecc による Rx + UniRx 解説資料 Rx とは? なぜ Unity で Rx が良いのか? というのが解ります http://www.slideshare.net/neuecc/reactive-programming-by-unirxfor-asynchronous-event-processing 未来のプログラミング技術をUnityで ~UniRx~ @toRisouP 氏による Rx + UniRx 解説資料 サンプル・図解が多く、めっちゃわかりやすい (おすすめ!) http://www.slideshare.net/torisoup/unity-unirx
  9. Study ReactiveX Rx の概念や、Observable、各 Operator の動作等を図解付きで詳しく解説したサイト (英語) http://reactivex.io/ Reactive extensions入門

    @okazuki 氏による Rx 解説資料 http://www.slideshare.net/okazuki0130/reactive-extensionsv01 Rx入門 @xin9le 氏による Rx 解説ページ http://blog.xin9le.net/entry/rx-intro
  10. Enable / Disable ストリームの射影 public class EnableDisablePresenter : MonoBehaviour {

    public Button SearchButton; public InputField WordInput; private void Start() { this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .SubscribeToInteractable(this.SearchButton); } }
  11. Enable / Disable ストリームの射影 public class EnableDisablePresenter : MonoBehaviour {

    public Button SearchButton; public InputField WordInput; private void Start() { this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .SubscribeToInteractable(this.SearchButton); } } OnValueChangeAsObservable() で UniRx のイベント ストリーム (IObseravable<T>) にする
  12. Enable / Disable ストリームの射影 public class EnableDisablePresenter : MonoBehaviour {

    public Button SearchButton; public InputField WordInput; private void Start() { this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .SubscribeToInteractable(this.SearchButton); } } OnValueChangeAsObservable() で UniRx のイベント ストリーム (IObseravable<T>) にする Select<T, TR>() で射影 (というか変換) string から bool へ (空文字なら false、それ以外は true) IObservable<bool> な On/Off ストリームにする
  13. Enable / Disable ストリームの射影 public class EnableDisablePresenter : MonoBehaviour {

    public Button SearchButton; public InputField WordInput; private void Start() { this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .SubscribeToInteractable(this.SearchButton); } } OnValueChangeAsObservable() で UniRx のイベント ストリーム (IObseravable<T>) にする Select<T, TR>() で射影 (というか変換) string から bool へ (空文字なら false、それ以外は true) IObservable<bool> な On/Off ストリームにする Subscribe() でストリームの購読 On/Off ストリームからの interactable 設定は SubscribeToInteractable() が便利
  14. ストリームの射影 Enable / Disable OnValueChangeAsObservable() InputField (WordInput) の値が変更されると配信 Select(x =>

    !string.IsNullOrEmpty(x)) string を bool に射影 false Subscribe() イベント購読 true false this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .SubscribeToInteractable(this.SearchButton); (空文字) IObservable<string> から IObservable<bool> へ True/False ストリーム (= On/Off ストリーム) に変換 "h" true "ho" "hog" true (空文字) true "foo"
  15. Enable / Disable ストリームのフィルター public class EnableDisablePresenter : MonoBehaviour {

    public Button SearchButton; public InputField WordInput; private void Start() { this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .DistinctUntilChanged() .SubscribeToInteractable(this.SearchButton); } }
  16. Enable / Disable ストリームのフィルター public class EnableDisablePresenter : MonoBehaviour {

    public Button SearchButton; public InputField WordInput; private void Start() { this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .DistinctUntilChanged() .SubscribeToInteractable(this.SearchButton); } } DistinctUntilChanged() でフィルター 同じ値が連続している場合は無視する
  17. ストリームのフィルター Enable / Disable OnValueChangeAsObservable() InputField (WordInput) の値が変更されると配信 Select(x =>

    !string.IsNullOrEmpty(x)) string を bool に射影 false Subscribe() イベント購読 true false this.WordInput .OnValueChangeAsObservable() .Select(x => !string.IsNullOrEmpty(x)) .DistinctUntilChanged() .SubscribeToInteractable(this.SearchButton); (空文字) "h" true "ho" "hog" true (空文字) true "foo" DistinctUntilChanged() 同じ値が連続している場合は無視する false true false true 連続した値は配信されない
  18. Simple Calculation ストリームの結合 public class CalculatorPresenter : MonoBehaviour { public

    InputField Left; public InputField Right; public Text Result; void Start() { var left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); var right = this.Right .OnValueChangeAsObservable() .Select(x => int.Parse(x)); left.CombineLatest(right, (l, r) => l + r) .SubscribeToText(this.Result); } }
  19. Simple Calculation ストリームの結合 public class CalculatorPresenter : MonoBehaviour { public

    InputField Left; public InputField Right; public Text Result; void Start() { var left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); var right = this.Right .OnValueChangeAsObservable() .Select(x => int.Parse(x)); left.CombineLatest(right, (l, r) => l + r) .SubscribeToText(this.Result); } } OnValueChangeAsObservable() で UniRx のイベント ストリーム (IObseravable<T>) にする
  20. Simple Calculation ストリームの結合 public class CalculatorPresenter : MonoBehaviour { public

    InputField Left; public InputField Right; public Text Result; void Start() { var left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); var right = this.Right .OnValueChangeAsObservable() .Select(x => int.Parse(x)); left.CombineLatest(right, (l, r) => l + r) .SubscribeToText(this.Result); } } OnValueChangeAsObservable() で UniRx のイベント ストリーム (IObseravable<T>) にする InputField の値は文字列 (string) で配信されるので 整数値 (int) に変換する
  21. Simple Calculation ストリームの結合 public class CalculatorPresenter : MonoBehaviour { public

    InputField Left; public InputField Right; public Text Result; void Start() { var left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); var right = this.Right .OnValueChangeAsObservable() .Select(x => int.Parse(x)); left.CombineLatest(right, (l, r) => l + r) .SubscribeToText(this.Result); } } OnValueChangeAsObservable() で UniRx のイベント ストリーム (IObseravable<T>) にする CombineLatest() で 2 つの IObservable<T> を結合 left と right いずれかのイベントが配信されたとき、 互いの最後の値を使って 1 つのイベントを配信する (ただし、両方揃ってからしか配信されない) InputField の値は文字列 (string) で配信されるので 整数値 (int) に変換する
  22. ストリームの結合 Left Simple Calculation OnValueChangeAsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x))

    string を int に変換 "0" 0 var left = this.Left .OnValueChangeAsObservable() // Left (InputField) の値が変更されるとイベント配信 .Select(x => int.Parse(x)); // 信された入力値 (x) は文字列なので、整数値に変換 "12" 12 "7" 7 ユーザーのテキスト入力
  23. ストリームの結合 Left Simple Calculation OnValueChangeAsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x))

    string を int に変換 "0" 0 Right OnValueChangeAsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x)) string を int に変換 "25" 25 "12" 12 "7" 7 "0" 0 "6" 6 "3" 3 CombineLatest(left, right) 互いの最後の値を結合して配信 7+3 0+0 12+0 12+6 12+3 7+25
  24. ストリームの結合 Left Simple Calculation OnValueChangeAsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x))

    string を int に変換 "0" 0 Right OnValueChangeAsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x)) string を int に変換 "25" 25 "12" 12 "7" 7 "0" 0 "6" 6 "3" 3 CombineLatest(left, right) 互いの最後の値を結合して配信 7+3 0+0 12+0 12+6 12+3 7+25 CombineLatest たとえば left ストリームから 12 が配信されたとき、 left の最新値 12 と right の最新値 0 を Combine する
  25. uGUI Integration UnityEvent をお手軽ハンドリング Button.OnClickAsObservable Slider.OnValueChangeAsObservable Toggle.OnValueChangeAsObservable InputField.OnEndEditAsObservable IObservable<string>.SubscribeToText IObservable<bool>.SubscribeToInteractable

    this.MyButton .OnClickAsObservable() .Subscribe(_ => Debug.Log("clicked!")); this.MyInput .OnEndEditAsObservable() .SubscribeToText(this.MyText); 文字列ストリームや On/Off ストリームを Subscribe して UI に反映 (頻出パターン) UnityEvent を IObservable<T> の イベント ストリームにする
  26. UnityEvent to IObservable<T> OnValueChangeAsObservable() と onValueChange.AsObservable() は何が違うのか? uGUI Integration var

    left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); // どっちがいいの? var left = this.Left.onValueChange .AsObservable() .Select(x => int.Parse(x));
  27. ストリームの結合 (onValueChange の場合) Left Simple Calculation onValueChange.AsObservable() InputField の値が変更されると配信 Select(x

    => int.Parse(x)) string を int に変換 Right onValueChange.AsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x)) string を int に変換 "25" 25 "12" 12 "7" 7 "6" 6 "3" 3 CombineLatest(left, right) 互いの最後の値を結合して配信 7+3 12+6 12+3 7+25 "0" 0 "0" 0 0+0 12+0
  28. ストリームの結合 (onValueChange の場合) Left Simple Calculation onValueChange.AsObservable() InputField の値が変更されると配信 Select(x

    => int.Parse(x)) string を int に変換 Right onValueChange.AsObservable() InputField の値が変更されると配信 Select(x => int.Parse(x)) string を int に変換 "25" 25 "12" 12 "7" 7 "6" 6 "3" 3 CombineLatest(left, right) 互いの最後の値を結合して配信 7+3 12+6 12+3 7+25 "0" 0 "0" 0 0+0 12+0 初期値が配信されない 両方揃うまで配信されない (最初の配信がこのタイミングになる)
  29. UnityEvent to IObservable<T> OnValueChangeAsObservable() と onValueChange.AsObservable() は何が違うのか? uGUI Integration var

    left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); // どっちがいいの? var left = this.Left.onValueChange .AsObservable() .Select(x => int.Parse(x)); Control.XxxAsObservable() ったら最強ね! • イベント ストリームをお手軽生成 • UnityEvent.AsObservable() と違い、初期値を供給 → 宣言的な UI を作る上で重要な機能 初期値が供給されるかどうかの違いがある
  30. Model-View-(Reactive)Presenter Unity における M-V-◦◦ パターン View = Scene, Hierarchy どうやって

    View を更新するか? Passive View ??? Model updates view state-change events user events update model
  31. Model-View-(Reactive)Presenter Unity における M-V-◦◦ パターン View = Scene, Hierarchy どうやって

    View を更新するか? View (XAML) ViewModel Model 例えば XAML Platform なら
  32. Model-View-(Reactive)Presenter Unity における M-V-◦◦ パターン View = Scene, Hierarchy どうやって

    View を更新するか? View (XAML) ViewModel Model INotifyPropertyCanged INotifyPropertyCanged 例えば XAML Platform なら DataBinding system + INotifyProperyChanged をベースとした通知基盤がある <TextBox Text="{Binding SampleText, Mode=TwoWay}" /> ICommand method call
  33. Model-View-(Reactive)Presenter Unity における M-V-◦◦ パターン View = Scene, Hierarchy どうやって

    View を更新するか? Unity にデータ バインディングは無い 複雑だし、パフォーマンス的にも作りたくない View (XAML) ViewModel Model INotifyPropertyCanged INotifyPropertyCanged 例えば XAML Platform なら DataBinding system + INotifyProperyChanged をベースとした通知基盤がある <TextBox Text="{Binding SampleText, Mode=TwoWay}" /> ICommand method call
  34. Reactive Property 通知可能なプロパティ 値の変更を IObservable<T> で配信できる public class Enemy {

    public ReactiveProperty<long> CurrentHp { get; private set; } public ReactiveProperty<bool> IsDead { get; private set; } public Enemy(int initialHp) { // Declarative Property this.CurrentHp = new ReactiveProperty<long>(initialHp); this.IsDead = this.CurrentHp.Select(x => x <= 0).ToReactiveProperty(); } } コンストラクターで宣言的に書く IsDead がどういう動作をするか ( = CurrentHP がどう作用するか) 見て一発でわかりますよね?
  35. Model-View-(Reactive)Presenter Unity における M-V-◦◦ パターン View = Scene, Hierarchy どうやって

    View を更新するか? UniRx が可能にする MV(R)P View を操作する (Reactive)Presenter バインディングがない以上、誰かが View を知らなければならない ReactiveProperty<T> による通知 Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model
  36. Model-View-(Reactive)Presenter IObservable<T> で繋がる M-V-(R)P Passive View Presenter (Supervising Controller) Model

    updates view state-change events user events update model UIControl.XxxAsObservable UnityEvent.AsObservable ObservableEventTrigger Subscribe ToReactiveProperty ReactiveProperty Subscribe SubscribeToText SubscribeToInteractable
  37. Count up / down UniRx (+ Reactive Property) を使って MV(R)P

    …の、極小サンプルとしてひとつ ・Up ボタンで数値 + 1 ・Down ボタンで数値 -1
  38. Count up / down UniRx (+ Reactive Property) を使って MV(R)P

    // Presenter public class UpDownPresenter : MonoBehaviour { // View public Button UpButton; public Button DownButton; public Text ScoreText; // Model [InspectorDisplay] public IntReactiveProperty Score; private void Start() { this.UpButton.OnClickAsObservable().Subscribe(_ => this.Score.Value++); this.DownButton.OnClickAsObservable().Subscribe(_ => this.Score.Value--); this.Score.SubscribeToText(this.ScoreText); } } Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model
  39. Count up / down UniRx (+ Reactive Property) を使って MV(R)P

    // Presenter public class UpDownPresenter : MonoBehaviour { // View public Button UpButton; public Button DownButton; public Text ScoreText; // Model [InspectorDisplay] public IntReactiveProperty Score; private void Start() { this.UpButton.OnClickAsObservable().Subscribe(_ => this.Score.Value++); this.DownButton.OnClickAsObservable().Subscribe(_ => this.Score.Value--); this.Score.SubscribeToText(this.ScoreText); } } Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model View から Presenter へ user events を配信 (onClick など)
  40. Count up / down UniRx (+ Reactive Property) を使って MV(R)P

    // Presenter public class UpDownPresenter : MonoBehaviour { // View public Button UpButton; public Button DownButton; public Text ScoreText; // Model [InspectorDisplay] public IntReactiveProperty Score; private void Start() { this.UpButton.OnClickAsObservable().Subscribe(_ => this.Score.Value++); this.DownButton.OnClickAsObservable().Subscribe(_ => this.Score.Value--); this.Score.SubscribeToText(this.ScoreText); } } Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model user events を受信した Presenter が Model (Score) を update
  41. Count up / down UniRx (+ Reactive Property) を使って MV(R)P

    // Presenter public class UpDownPresenter : MonoBehaviour { // View public Button UpButton; public Button DownButton; public Text ScoreText; // Model [InspectorDisplay] public IntReactiveProperty Score; private void Start() { this.UpButton.OnClickAsObservable().Subscribe(_ => this.Score.Value++); this.DownButton.OnClickAsObservable().Subscribe(_ => this.Score.Value--); this.Score.SubscribeToText(this.ScoreText); } } Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model Model が state-change event を配信 (ReactiveProperty<int> のイベント ストリーム)
  42. Count up / down UniRx (+ Reactive Property) を使って MV(R)P

    // Presenter public class UpDownPresenter : MonoBehaviour { // View public Button UpButton; public Button DownButton; public Text ScoreText; // Model [InspectorDisplay] public IntReactiveProperty Score; private void Start() { this.UpButton.OnClickAsObservable().Subscribe(_ => this.Score.Value++); this.DownButton.OnClickAsObservable().Subscribe(_ => this.Score.Value--); this.Score.SubscribeToText(this.ScoreText); } } Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model state-change event を受信した Presenter が View (ScoreText) を update
  43. Model-View-(Reactive)Presenter UniRx (+ Reactive Property) を使って MV(R)P というものを試験的に導入し開発しています 試験的というかもうガッツリ書き始めました 実際、UniRx

    にピッタリのパターン UniRx readme: https://github.com/neuecc/UniRx#model-view-reactivepresenter-pattern 是非 Reactive Property と共に導入し、フィードバックして頂けると助かります
  44. Reactive UI Script を書く前提の環境 Inspector での onClick 紐づけ等は無視してます (ノンコーディングでやりたい人向け? うちではあんまりやってない…)

    Control.XxxAsObservable ったら最強ね! OnClick, OnValueChange, … イベント ストリームお手軽生成 初期値の供給 (UnityEvent.AsObservable と挙動が違う)
  45. Model-View-(Reactive)Presenter IObservable<T> で繋がる M-V-(R)P Passive View Presenter (Supervising Controller) Model

    updates view state-change events user events update model Subscribe ToReactiveProperty ReactiveProperty UIControl.XxxAsObservable UnityEvent.AsObservable ObservableEventTrigger Subscribe SubscribeToText SubscribeToInteractable
  46. Study 勉強用の資料 & 解説ページ等 (再掲) Reactive Programming by UniRx for

    Asynchronous & Event Processing 開発者、弊社 CTO @neuecc による Rx + UniRx 解説資料 Rx とは? なぜ Unity で Rx が良いのか? というのが解ります http://www.slideshare.net/neuecc/reactive-programming-by-unirxfor-asynchronous-event-processing 未来のプログラミング技術をUnityで ~UniRx~ @toRisouP 氏による Rx + UniRx 解説資料 サンプル・図解が多く、めっちゃわかりやすい (おすすめ!) http://www.slideshare.net/torisoup/unity-unirx
  47. Study ReactiveX Rx の概念や、Observable、各 Operator の動作等を図解付きで詳しく解説したサイト (英語) http://reactivex.io/ Reactive extensions入門

    @okazuki 氏による Rx 解説資料 http://www.slideshare.net/okazuki0130/reactive-extensionsv01 Rx入門 @xin9le 氏による Rx 解説ページ http://blog.xin9le.net/entry/rx-intro