UniRx-Unity3dのRx

みなさんこんにちは! Unity3dでUniRxに関する記事を書きたいとずっと思っていました。 RXプログラミングの小さな哲学から始めましょう。 たとえば、ゲームを開発する場合、ボタンを作成し、このボタンのクリックイベントを監視し、何らかのコードでそれに応答します。



リアクティブプログラミングはすべて同じで、ステロイドのみです。つまり、すべてのデータストリームを作成できます。 また、それらを見て反応します。 更新、OnCollisionEnter、コルーチン、イベント、マウス入力、キーボード入力、ジョイスティック入力はすべてスレッドです。

私たちを取り巻くのは流れだけです。



画像






さらに、これらのストリームを結合、作成、フィルタリングするための素晴らしい機能セットが提供されています。 これは、「機能的な」魔法が発生する場所です。 ストリームは別のストリームとして使用できます。 複数のスレッドでさえ、別のスレッドのデータとして使用できます。 2つのストリームを結合できます。 ストリームをフィルタリングして、関心のあるイベントのみを含む別のストリームを取得できます。 あるストリームから別のストリームにデータ値をマップできます。



ストリーム



この例では、キーストロークを追跡し、イベントに変換します。



void Start () { Observable.EveryUpdate() //  update .Where(_ => Input.anyKeyDown) //      .Select(_ => Input.inputString) //    .Subscribe (x => { //  OnKeyDown (x); //   OnKeyDown c    }).AddTo (this); //    gameobject- } private void OnKeyDown (string keyCode) { switch (keyCode) { case "w": Debug.Log ("keyCode: W"); break; default: Debug.Log ("keyCode: "+keyCode); break; } }
      
      





マルチスレッド



この例では、thread-eで重いメソッドを実行し、メインスレッド-eで既に取得した結果を使用しています。



 Observable.Start (() => { //  Observable  thread int n = 100000000; int res = Fibonacci(n); //    return res; //   }).ObserveOnMainThread () //     main thread- .Subscribe (xs => { //  Debug.Log ("res: "+xs); //     main thread- }).AddTo (this);
      
      





したがって、長い計算を実行してネットワークを操作すると非常に便利です。



HttpRequest



ネットワークでの作業について説明しているので、httpリクエストでの作業の小さな例を示します。



 var request = ObservableWWW.Get("http://api.duckduckgo.com/?q=habrahabr&format=json") .Subscribe(x => { //  Debug.Log ("res: "+x); //  }, ex => { //    Debug.Log ("error: "+ex); }); // request.Dispose ();   
      
      





コルーチン

ここでは、3つのコルーチンを同時に起動し、ストリームに変換して、1つのストリームに結合します。 次に、このストリームにサブスクライブします。



 void Start () { Observable.WhenAll ( //  WhenAll    Observable  Observable.FromCoroutine (AsyncA), //      Observable Observable.FromCoroutine (AsyncB), Observable.FromCoroutine (AsyncC) ).Subscribe (_ => { //        WhenAll Debug.Log ("end"); }).AddTo (this); } IEnumerator AsyncA () { Debug.Log("a start"); yield return new WaitForSeconds (1); Debug.Log("a end"); } IEnumerator AsyncB () { Debug.Log("b start"); yield return new WaitForFixedUpdate (); Debug.Log("b end"); } IEnumerator AsyncC () { Debug.Log("c start"); yield return new WaitForEndOfFrame (); Debug.Log("c end"); }
      
      





コルーチンを1つずつ実行することもできます



 Observable.FromCoroutine (AsyncA) //   AsyncA .SelectMany (AsyncB) // AsyncB     AsyncA .SelectMany (AsyncC) //    AsynC .Subscribe(_ => { Debug.Log ("end"); }).AddTo (this);
      
      





非同期シーンの読み込み



 SceneManager.LoadSceneAsync ("HeavyScene") //    .AsAsyncOperationObservable () //    Observable  .Do (x => { //     Debug.Log ("progress: " + x.progress); //   }).Subscribe (_ => { //  Debug.Log ("loaded"); }).AddTo (this);
      
      





ロード画面で簡単なシーンを最初にロードし、既にロードしている場合、それを使用することは非常に便利です

重いシーンを非同期でロードすると、ロード画面のアニメーションはフリーズしません。



非同期リソースの読み込み



 void Start () { SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer> (); Resources.LoadAsync<Sprite> ("sprite") //    .AsAsyncOperationObservable () //    Observable  .Subscribe (xs => { //  if (xs.asset != null) { //   null Sprite sprite = xs.asset as Sprite; //  asset  sprite spriteRenderer.sprite = sprite; //   sprite- } }).AddTo (this); }
      
      





この方法で、プレハブ、テキストリソースなどをロードできます。



また、重いプレハブやスプライトをロードする必要がある場合に使用すると非常に便利です。ゲームのロード中にゲームの速度が低下しないため、ゲームの応答性が向上します。



タイマー



 void Start () { Observable.Timer (System.TimeSpan.FromSeconds (3)) //  timer Observable .Subscribe (_ => { //  Debug.Log (" 3 "); }).AddTo (disposables); //    disposable Observable.Timer (System.TimeSpan.FromSeconds (1)) //  timer Observable .Repeat () //    .Subscribe (_ => { //  Debug.Log (" 1 "); }).AddTo (disposables); //    disposable } void OnEnable () { //  disposable disposables = new CompositeDisposable(); } void OnDisable () { //   if (disposables != null) { disposables.Dispose (); } }
      
      





メッセージブローカー



UniRxのメッセージブローカーは、RXベースのパブリッシャー/サブスクライバーシステムであり、タイプでフィルターされています。



パブリッシャー-サブスクライバー(英語のパブリッシャー-サブスクライバーまたは英語のパブ/サブ)-パブリッシャー(英語のパブリッシャー)と呼ばれるメッセージ送信者が、サブスクライバー(英語のサブスクライバー)にメッセージを送信するためのプログラムコードに直接結び付けられていないメッセージ送信用の動作デザインテンプレート。 代わりに、メッセージはクラスに分割され、サブスクライバーについての情報は含まれていません。 同様に、サブスクライバーは特定のパブリッシャーから抽象化された1つ以上のクラスのメッセージを処理します。



ゲームを開発するときに、直接アクセスできないコンポーネントのメソッドを呼び出す必要がある場合があります。 もちろん、DIまたはシングルトンを使用できますが、すべては特定のケースに依存します。 そして、これは多くのオブジェクトでメソッドを呼び出す必要があるとき、または単にMessageBrokerを使用したいときです。



上記で説明したように、フィルタリングはタイプごとに行われ、各サブスクライバーのクラスの束を作成しないように、フィールドが含まれるMessageBaseクラスを作成しました。 System.Object)キャストする必要があるデータを転送します。 また、このクラスには、MessageBaseを作成して返す静的メソッド(Create)があります。



MessageBase



 public class MessageBase { public MonoBehaviour sender {get; private set;} // MonoBehaviour  public int id {get; private set;} // id  public System.Object data {get; private set;} //  public MessageBase (MonoBehaviour sender, int id, System.Object data) { this.sender = sender; this.id = id; this.data = data; } public static MessageBase Create (MonoBehaviour sender, int id, System.Object data) { return new MessageBase (sender, id, data); } }
      
      





また、すべてのメッセージのIDが現在保存されているデバッグを簡素化するために、ServiceShareDataクラスも作成しました。 これは、開発プロセス中にメッセージリークやコードの混乱が生じないようにするために必要です。



ServiceShareData



 public class ServiceShareData { public const int MSG_ATTACK = 1001; }
      
      





メッセージ送信の例



 MessageBroker.Default .Publish (MessageBase.Create ( this, // sender MonoBehaviour ServiceShareData.MSG_ATTACK, // message id "attack!" // data System.Ojbect ));
      
      





Publishメソッドは、タイプでフィルタリングされたクラスをディスパッチします。 ここでは、送信者this、フィルタリングされるID、および理論上は何でもよいデータの最後にMessageBaseを送信します。



メッセージ受け入れの例



 public CompositeDisposable disposables; void OnEnable () { disposables = new CompositeDisposable(); MessageBroker.Default .Receive<MessageBase>() //   MessageBase .Where(msg => msg.id == ServiceShareData.MSG_ATTACK)// message  id .Subscribe(msg => { //  string data = (string)msg.data; //      //      sender-     Debug.Log ("sender:"+msg.sender.name+" receiver:"+name+" data:"+data); }).AddTo (disposables); } void OnDisable () { //  if (disposables != null) { disposables.Dispose (); } }
      
      





Receiveメソッドには、タイプごとの汎用フィルターがあります。 すでにメッセージIDでフィルタリングしている場所。 サブスクライバー(メッセージの受信者)は、メッセージのIDに応じてデータをキャストすることを理解することが重要です。



MVP



古典的なMVPパターンの例。 Modelは、データの保存およびデータの逆シリアル化などのシリアル化に役立ちます。 このデータを表示するために表示します。 さて、ビジネスロジックを担当するプレゼンター。



モデル



  public class SomeModel { public ReactiveProperty<int> count { get; private set; } public SomeModel () { count = new ReactiveProperty<int> (0); //  ReactiveProperty c 0 } }
      
      





ご覧のとおり、この場合はReactivePropertyがあります。これは、サブスクライブして対応できる変更に対するステロイドのintです。



表示する



 public class SomeView : MonoBehaviour { public Text someText; public Button someButton; public void RenderCount (int count) { //   count someText.text = count.ToString (); } public void AnimateButton () { //   someButton.transform .DOShakeScale (0.5F, 0.3F) //   .OnComplete (() => { //       someButton.transform.localScale = Vector3.one; }); } }
      
      





ビューにはテキストとボタンがあります。 ボタンをアニメーション化し、データを表示する方法も同様です。 アニメーションの場合、DoTweenアセットが使用されます。



発表者



 public class SomePresenter : MonoBehaviour { public SomeView someView; // view      public SomeModel someModel = new SomeModel (); // model void Start () { someModel.count // ReactiveProperty count .ObserveEveryValueChanged (x => x.Value) //     .Subscribe (xs => { //  someView.RenderCount (xs); //     }).AddTo (this); someView.someButton //  .OnClickAsObservable () //    Observable  .Subscribe (_ => OnClick (someView.someButton.GetInstanceID ())) .AddTo (this); } private void OnClick (int buttonId) { if (buttonId == someView.someButton.GetInstanceID ()) { someModel.count.Value++; someView.AnimateButton (); } } }
      
      





ご覧のとおり、まず変更のみを描画するために、reactivePropertyの変更をサブスクライブします。



次に、ボタンにサブスクライブします。クリックすると、OnClickメソッドが呼び出され、このボタンのinstanceId(Unity3dがその一意性を保証します)をプッシュします。



OnClickでは、このintanceIdのチェックがあり、クリックするとカウント(reactiveProperty)を増やしてボタンをアニメーション化します。



質問やコメントをお待ちしております。



読むことをお勧めします:

github.com/neuecc/UniRx

gist.github.com/staltz/868e7e9bc2a7b8c1f754

www.reactivemanifesto.org



ソースコード



All Articles