imog

主にUnityとかの事を書いています

ArborのStateBehaviourを継承してUniRXを少し使いやすくする

無料期間中にArbor: State Diagram Editorを入手しました。

caitsithware.com

毎回statemachineを自前で書いてて面倒だと思ってたのでこれはありがたい。 昨日から触り始めているけど、特別引っかかることもなく使いやすいです。

なお、Arborの使い方とかはテラシュールブログさんが細かく書いてるのでそちらを参照すると良さそうです。 tsubakit1.hateblo.jp

これで作るスクリプト内部でUniRXをごにょごにょして、State遷移してから変更あるまでHogeHoge〜みたいな処理を書きたかったので拡張してみました。

こういう拡張どこに置くか悩んだので、とりあえず CustomArbor 名前空間を作って ObservableStateBehaviour を作った。

using UnityEngine;
using System.Collections;
using Arbor;
using UniRx;
using UniRx.Triggers;
using System.Linq;

namespace CustomArbor
{
    public class ObservableStateBehaviour : StateBehaviour
    {
        private Subject<Unit> stateBeginStream = new Subject<Unit>();

        public IObservable<Unit> stateBeginAsObservable {
            get { return stateBeginStream.AsObservable(); }
        }

        private Subject<Unit> stateEndStream = new Subject<Unit>();

        public IObservable<Unit> stateEndAsObservable {
            get { return stateEndStream.AsObservable(); }
        }

        public IObservable<Unit> updateAsObservable {
            get { return this.UpdateAsObservable().
                            SkipUntil(stateBeginAsObservable).
                            TakeUntil(stateEndAsObservable).
                            Repeat();
                }
        }

        // Use this for enter state
        public override void OnStateBegin ()
        {
            stateBeginStream.OnNext(default(Unit));
        }
        // Use this for exit state
        public override void OnStateEnd ()
        {
            stateEndStream.OnNext(default(Unit));
        }
    }
}

やってることは、状態の開始時と終了時にそれぞれストリームを用意して、通知しているだけです。

また、StateBehaviourの通常のUpdateは OnStateBegin ~ OnStateEnd の間のみ走るので、代用としてupdateAsObservableを作ってます。

  • StateAは、0.5秒ごとに A! と出力する
  • StateBは、1秒ごとに B! と出力する
  • キーボードのAを押された場合 State X Down と出力して遷移する
public class StateA : ObservableStateBehaviour {

    public StateLink nextState;

    void Awake()
    {
        this.UpdateAsObservable().
            SkipUntil(stateBeginAsObservable).
            Sample(TimeSpan.FromSeconds(0.5F)).
            TakeUntil(stateEndAsObservable).
            Repeat().
            Subscribe(_ => {
                print("A!");
            });

        updateAsObservable.
            Where(_ => Input.GetKeyDown(KeyCode.A)).
            Subscribe(_ => {
                print("StateA Down");
                Transition(nextState);
            });
    }
}

public class StateB : ObservableStateBehaviour {

    public StateLink nextState;

    void Awake()
    {
        this.UpdateAsObservable().
            SkipUntil(stateBeginAsObservable).
            Sample(TimeSpan.FromSeconds(1F)).
            TakeUntil(stateEndAsObservable).
            Repeat().
            Subscribe(_ => {
                print("B!");
            });

        updateAsObservable.
            Where(_ => Input.GetKeyDown(KeyCode.A)).
            Subscribe(_ => {
                print("StateB Down");
                Transition(nextState);
            });
    }
}

こんな感じになります。

n秒間隔で流すという処理が挟まるので、updateAsObservable 使わず SkipUntil -> Sample -> TakeUntil と書いてるけど、もっと綺麗なやり方ありそう。