【Unity5】StateMachineBehaviourでAnimatorを監視する

**UniRxについての記事のまとめは[こちら](http://qiita.com/toRisouP/items/48b9fa25df64d3c6a392)**

---
# StateMachineBehaviourとは

Unity5よりStateMachineBehaviourという、AnimatorControllerに貼り付けるタイプのスクリプトが作成できるようになりました。
このStateMachineBehaviourはAnimatorのステートマシンの状態遷移に合わせてCallBack関数を実行してくれるとい性質のものです。

```csharp:StateMachineBehaviourのサンプル

public class StateMachineExample : StateMachineBehaviour
{

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //新しいステートに移り変わった時に実行
    }

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //ステートが次のステートに移り変わる直前に実行
    }

    public override void OnStateMachineEnter(Animator animator, int stateMachinePathHash)
    {
        //スクリプトが貼り付けられたステートマシンに遷移してきた時に実行
    }

    public override void OnStateMachineExit(Animator animator, int stateMachinePathHash)
    {
        //スクリプトが貼り付けられたステートマシンから出て行く時に実行
    }

    public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //MonoBehaviour.OnAnimatorMoveの直後に実行される
    }

    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //最初と最後のフレームを除く、各フレーム単位で実行
    }

    public override void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //MonoBehaviour.OnAnimatorIKの直後に実行される
    }
}
```

# StateMachineBehaviourの使い方

StateMachineBehaviourは普通のComponentと違い、Animatorのレイヤに貼り付けて使います。

![StateMachineBehaviour](https://qiita-image-store.s3.amazonaws.com/0/47146/9a5e3cac-5f15-7a26-80f5-eaad938d7a9f.png)

通常のMonoBehaviourスクリプトから呼び出す場合はこんな感じで、AnimatorからGetBehaviourで取得します。

```csharp:StateMachineBehaviourの取得方法
private Animator animator;
private StateMachineExample stateMachineExample;

void Start()
{
    animator = GetComponent <Animator>();
    stateMachineExample = animator.GetBehaviour <StateMachineExample>();   
}
```

#(余談)UniRxを使いObservableとしてステート遷移を監視できるようにした

コールバックのまま使うのは少々ツライものがあるので、[UniRx](https://github.com/neuecc/UniRx)を使ってコールバックをObservableに変換してあげます。

```csharp:StateMachineObservalbes.cs
using UnityEngine;
using System.Collections;
using UniRx;

public class StateMachineObservalbes : StateMachineBehaviour
{
    #region OnStateEnter

    private Subject<AnimatorStateInfo> onStateEnterSubject = new Subject<AnimatorStateInfo>();

    public IObservable<AnimatorStateInfo> OnStateEnterObservable { get { return onStateEnterSubject.AsObservable(); } }

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        onStateEnterSubject.OnNext(stateInfo);
    }

    #endregion

    #region OnStateExit

    private Subject<AnimatorStateInfo> onStateExitSubject = new Subject<AnimatorStateInfo>();

    public IObservable<AnimatorStateInfo> OnStateExitObservable { get { return onStateExitSubject.AsObservable(); } }

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        onStateExitSubject.OnNext(stateInfo);
    }

    #endregion

    #region OnStateMachineEnter

    private Subject<int> onStateMachineEnterSubject = new Subject<int>();

    public IObservable<int> OnStateMachineEnterObservable { get { return onStateMachineEnterSubject.AsObservable(); } }

    public override void OnStateMachineEnter(Animator animator, int stateMachinePathHash)
    {
        onStateMachineEnterSubject.OnNext(stateMachinePathHash);
    }

    #endregion

    #region OnStateMachineExit

    private Subject<int> onStateMachineExitrSubject = new Subject<int>();

    public IObservable<int> OnStateMachineExitObservable { get { return onStateMachineExitrSubject.AsObservable(); } }

    public override void OnStateMachineExit(Animator animator, int stateMachinePathHash)
    {
        onStateMachineExitrSubject.OnNext(stateMachinePathHash);
    }

    #endregion

    #region OnStateMove

    private Subject<AnimatorStateInfo> onStateMoveSubject = new Subject<AnimatorStateInfo>();

    public IObservable<AnimatorStateInfo> OnStateMoveObservable { get { return onStateMoveSubject.AsObservable(); } }

    public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        onStateMoveSubject.OnNext(stateInfo);
    }

    #endregion

    #region OnStateMove

    private Subject<AnimatorStateInfo> onStateUpdateSubject = new Subject<AnimatorStateInfo>();

    public IObservable<AnimatorStateInfo> OnStateUpdateObservable { get { return onStateUpdateSubject.AsObservable(); } }

    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        onStateUpdateSubject.OnNext(stateInfo);
    }

    #endregion


    #region OnStateIK

    private Subject<AnimatorStateInfo> onStateIKSubject = new Subject<AnimatorStateInfo>();

    public IObservable<AnimatorStateInfo> OnStateIKObservable { get { return onStateIKSubject.AsObservable(); } }

    public override void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        onStateIKSubject.OnNext(stateInfo);
    }

    #endregion
}
```

あとは以下のように欲しいObservableを取得してSubscribeすれば使えます。

```csharp:Observableでステート遷移を監視する
private Animator _animator;
private StateMachineObservalbes _stateMachineObservables;

void Start()
{
    _animator = GetComponent <Animator>();
    _stateMachineObservables = _animator.GetBehaviour <StateMachineObservalbes>();
   
    //開始したアニメーションのshortNameHashを表示する
    _stateMachineObservables
        .OnStateEnterObservable
        .Subscribe(stateInfo => Debug.Log(stateInfo.shortNameHash));
}
```

##UniRxでステート遷移を制御してみる

**「Idelアニメーションを再生し５秒以上経過したらRestモーションに遷移する」**

というステート遷移を先ほどのStateMachineObservalbesを使うとこんな感じで記述できます。

```csharp:
private Animator _animator;
private StateMachineObservalbes _stateMachineObservables;

void Start()
{
    _animator = GetComponent <Animator>();
    _stateMachineObservables = _animator.GetBehaviour <StateMachineObservalbes>();

    _stateMachineObservables
        .OnStateEnterObservable                          //ステート遷移を監視
        .Throttle(TimeSpan.FromSeconds(5))               //最後にステート遷移してから５秒経過した時
        .Where(x => x.IsName("Base Layer.Idle"))         //現在再生中のアニメーションがBase LayerのIdelだったら
        .Subscribe(_ => _animator.SetBool("Rest", true));//AnimatorのRestパラメータをTrueにする
}
```

![Unitychan](https://qiita-image-store.s3.amazonaws.com/0/47146/52deadd7-f2f8-2e06-3523-5f3c2fa8ea20.png)

Idle（棒立ち）状態で5秒経ったらRest（休憩）モーションを再生するようにできました。

##参考

* [State Machine Behaviours](http://unity3d.com/jp/learn/tutorials/modules/beginner/5-pre-order-beta/state-machine-behaviours)

##使ったもの
* [UniRx] (https://github.com/neuecc/UniRx)
* [ユニティちゃん](http://unity-chan.com/)

![ライセンス](https://qiita-image-store.s3.amazonaws.com/0/47146/19407e01-adf5-77b4-b6b5-0ff552ccd36a.jpeg)