【Unity5】StateMachineBehaviourでAnimatorを監視する

  • 77
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

UniRxについての記事のまとめはこちら


StateMachineBehaviourとは

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

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

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

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

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

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

コールバックのまま使うのは少々ツライものがあるので、UniRxを使ってコールバックをObservableに変換してあげます。

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すれば使えます。

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アニメーションを再生し5秒以上経過したらRestモーションに遷移する」

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

private Animator _animator;
private StateMachineObservalbes _stateMachineObservables;

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

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

Unitychan

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

参考

使ったもの

ライセンス