simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:Animatorからステートを取得

前書き

ゲーム開発していて、入力モードが増えてきて、ステート管理していきたいと思う中
Unity の State Machine って Mecanim の AnimatorController だなぁと気づき
デバッグ時にステートを遷移を可視化しつつ、これをスクリプトから操作や取得できたらいいなというのが
この記事のモチベーションです。

Animator からステート名は取れない

どうもステート名を外から取るには Asset をロードしなければならないとのことであきらめ
Enum 値とステート名のハッシュ値の関連から、なるべくコストをかけずに Enum 値として State を見極められるようにしたいと思います。

アイディアの具現化

次のような Explore(探索モード)を必ず通って各ステートへ切り替わるステートマシンを用意します。
トリガーはステート名を指定すると、そのステートへ移動する設定

f:id:simplestar_tech:20191215163853p:plain
AnimatorControllerの例

これをスクリプト側で、現在のステートと次のステートへの移動を Enum で取り扱えるように工夫したものがこちら

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// ゲーム内の入力モード・ステート管理
/// </summary>
public class InputModeStateMachine : MonoBehaviour
{
    void Start()
    {
        // ステートマシンを Layer 0 に持つ AnimatorController を持つ Animator を期待
        this.animator = GetComponent<Animator>();
        // 各ステート名は Enum 名と同じように作られていることを期待
        for (InputModeState stateEnum = InputModeState.Explore; stateEnum < InputModeState.Max; stateEnum++)
        {
            var fullPath = INPUT_STATE_LAYER_NAME + "." + stateEnum.ToString();
            var hash = Animator.StringToHash(fullPath);
            this.stateHashToEnum.Add(hash, stateEnum);
        }
    }

    // Update is called once per frame
    void Update()
    {
        // スペースキーを押すと、ステートが切り替わる
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 現在のステート情報
            var stateInfo = this.animator.GetCurrentAnimatorStateInfo(INPUT_STATE_LAYER_INDEX);
            // ハッシュ値から Enum 値へマップ
            var currentState = this.stateHashToEnum[stateInfo.fullPathHash];
            Debug.Log(currentState.ToString());

            // Explore を中継にステートを切り替える(デバッグ機能)
            if (currentState != InputModeState.Explore)
            {
                this.animator.SetTrigger(InputModeState.Explore.ToString());
                Debug.Log(InputModeState.Explore.ToString());
            }
            else
            {
                this.animator.SetTrigger(nextState.ToString());
                Debug.Log(nextState.ToString());
                this.nextState = this.nextState + 1;
                if (InputModeState.Max == this.nextState)
                {
                    this.nextState = InputModeState.Explore + 1;
                }
            }
        }
    }

    /// <summary>
    /// ステート名
    /// </summary>
    public enum InputModeState
    {
        Explore = 0,
        Inventory,
        Mining,
        TextChat,

        Max
    }

    // デバッグ用
    InputModeState nextState = InputModeState.Inventory;
    /// <summary>
    /// ステート名HashからステートEnumへのマップ
    /// </summary>
    Dictionary<int, InputModeState> stateHashToEnum = new Dictionary<int, InputModeState>();
    /// <summary>
    /// レイヤー名(fullpath でプリフィックスに利用)
    /// </summary>
    const string INPUT_STATE_LAYER_NAME = "InputStateLayer";
    /// <summary>
    /// ステートマシンのレイヤーインデックス
    /// </summary>
    const int INPUT_STATE_LAYER_INDEX = 0;
    /// <summary>
    /// ステートマシンのコントローラを持つアニメータ
    /// </summary>
    Animator animator;
}

まとめ

期待通り、スペースキーを連打すると
Explore → Inventory → Explore → Mining → Explore → TextChat → Explore
というステート遷移のデバッグログが流れていきます。

別にスクリプトだけでステート遷移してもよかったんですけど
可視化することと、その可視化したものでステート管理することで、変なステート遷移をコードに仕込まなくて済むなぁと
ちょっと回りくどかったかな