読者です 読者をやめる 読者になる 読者になる

【Unity】State Machine Behaviour について

はじめに

こんにちは、ソーシャルゲーム事業部のUnityエンジニアのアファトです。 この記事はカヤックUnityアドベントカレンダー2016の15日目の記事になります。

ゲームにおいて、モーションに合わせるロジックはいろいろな方法で実装できますが、場合によって、実装しづらいや調整しにくいケースがあります。そのために Unity 5 から導入された State Machine Behaviour という仕組みがあります。アニメーションロジック または アニメーションに近い・依存するロジックをこの仕組みを利用することで、実装しやすくなり、メンテナンスもしやすくなります。

State Machine Behaviourについて

State machine behavioursはMecanimで作られたAnimationのロジックをよりコントロールしやすくするため、Animator Controllerの中にあるanimator stateやsub-state machinesにアタッチできるscriptです。StateのinspectorのAdd Behaviourボタンを押すと State Machine Behaviour (SMB) が利用できます。 image

新しいScriptを作る場合、以下の様なテンプレートができます。

using UnityEngine;
using System.Collections;

public class NewStateMachineBehaviour : StateMachineBehaviour
{

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    //override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    //override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateMove is called right after Animator.OnAnimatorMove(). Code that processes and affects root motion should be implemented here
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateIK is called right after Animator.OnAnimatorIK(). Code that sets up animation IK (inverse kinematics) should be implemented here.
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}
}

そこにStateMachineBehaviourを継承して、五つ関数をoverrideします:

  • OnStateEnter stateに入るときに一回呼ばれます。初期化のためなど
  • OnStateUpdate stateの中に行うUpdateはこの関数で対応できます
  • OnStateExit stateが完了する時、ほかのstateに移動するとき一回呼ばれます。clean upのためなど
  • OnStateMove root motionに関するロジックはこの関数で対応できます
  • OnStateIK Inverse Kinematics に関するロジックこの関数で対応できます

この五つ関数は全部同じ引数が渡されます: animatoranimatorStateInfolayerIndex

  • animator はアタッチされるstate machine behaviourのAnimatorのコンポーネントの参照です。このコンポーネントに GameObject の参照があるので MonoBehaviour とのやり取りができます。
  • animatorStateInfo は現状 state の状態の情報です。animation clip の長さに対する正規化した time などの情報が入ってます。
  • layerIndex は state machine behaviour のstateのレイヤーの情報。ベースレイヤーだと 0 になります。

State Machine Behaviourで出来ること

State に依存するロジックなどは SMB で実装しやすくなります。以下の Animator Controller で SMB ができることを試しに作ってみましょう。

image

5つ states: ChargeJumpAttackReturnEnd というアニメーションの流れで、End以外の state に入る時に Sound Effect (SE) を再生できる SMB を作ります。

using UnityEngine;
using System.Collections;

public class SoundEffectPlayerSMB : StateMachineBehaviour
{
    public AudioClip soundEffectClip;
    public float delay;

    private float elapsedTime;
    private AudioSource audioSource;

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (null == audioSource)
        {
            // 例えば AudioSource というマネージャーコンポーネントがあったら
            var audioSourceObj = GameObject.Find("AudioSourceManager");
            audioSource = audioSourceObj != null ? audioSourceObj.GetComponent<AudioSource>() : null;
        }

        if (null != soundEffectClip && 0 >= delay)
        {
            PlaySoundClip();
        }
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (null == soundEffectClip)
        {
            return;
        }

        elapsedTime += Time.deltaTime;
        if (elapsedTime >= delay)
        {
            PlaySoundClip();
        }
    }

    private void PlaySoundClip()
    {
        if (null == audioSource) { return; }
        if (null == soundEffectClip) { return; }

        audioSource.PlayOneShot(soundEffectClip);
        soundEffectClip = null;
    }
}

上記にの SMB script ができたら、state ごとにアタッチすると、その state に入る時に設定した audio clip と delay で SE が再生してくれます。 image

image

おわりに

State に依存するロジックは SMB script で実装しやすいし、メンテナンスもしやすくなりますので、結構便利に使えると思います。

明日は UI アニメーションなどによく使われる Tweening についての話になります。担当はです。お楽しみに〜