[unity]Animator からアニメーションの名前を取るには?

animator.Play("DispOn"); と文字列指定するのが嫌。
でも public const DispOn = "DispOn"; と手書きで書くのも面倒(だし間違えるかも)。とにかく楽したい。

そう思い Editor 機能で自動的に const ファイルが作れないか、模索した時の話。

簡単そうで実は結構面倒でした…。

RuntimeAnimatorController.animationClip

Animator anim = GetComponent<Animator>();
RuntimeAnimatorController ac = anim.runtimeAnimatorController;
Debug.Log(ac.animationClips[0].name);

これは罠。一見取れそうに思え…たものの、DispOn という AnimationClip の名前が DisplayOn となっていた。
なぜだろう…記憶を思い起こす。
そういえば最初 DisplayOn という名前で Animation を作成、その後 DispOn にしたかも…?
この最初の名前は、後から Animation ファイル名を変えてもそのままになっており、それを取得しているようです。

そもそも Animator でつけた名前が欲しいので、Animation ファイルの名前ではだめですね。

〇UnityEditor.Animations.AnimatorControllerにキャスト

Animator anim = GetComponent<Animator>();
RuntimeAnimatorController ac = anim.runtimeAnimatorController;
UnityEditor.Animations.AnimatorController acc = ac as UnityEditor.Animations.AnimatorController;
for (int layer = 0; layer < acc.layers.Length; layer++)
{
    for (int s = 0; s < acc.layers[layer].stateMachine.states.Length; s++)
    {
        UnityEditor.Animations.ChildAnimatorState state = acc.layers[layer].stateMachine.states[s];
        Debug.Log(state.state.name);
    }
}

鬼のようなキャストや配列に目がくらむものの、欲しい情報には届いた。
Parameter、その他諸々もアクセスできそう。

この辺が全部アクセスできそう

〇Animator のファイルを直接覗いてみる

これは最終手段と思っていますが、テキスト抽出能力の高い人なら最初から選択するかもしれない道筋。
Disp.controllerAnimator ファイル)はテキスト形式なので…。

--- !u!1102 &7658924678026381332
AnimatorState:
  serializedVersion: 5
  m_ObjectHideFlags: 1
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_Name: DispOn
  m_Speed: 1
  m_CycleOffset: 0
  m_Transitions: []
  m_StateMachineBehaviours: []

AnimatorState:m_Name を抽出すれば取れなくはなさそう? 他にもずらりとメンバが並ぶ。
Assets/ 下にある .controller ファイルを探し、AnimatorController: という文字列があれば Animator と判断していいのではないでしょうか。
ただ、ファイルフォーマットが常にこのままとは限らないのがリスク。

コードで全ての Animator を取得するには?

シーンに存在しないものも含め、すべての Animator を取得するには以下のコード。

Animator[] animators = (Animator[])UnityEngine.Resources.FindObjectsOfTypeAll(typeof(Animator));

…で、取れるかと思ったら取れてませんでした(2020/10/2)。代わりの方法。

string[] files     = Directory.GetFiles(Application.dataPath, "*.controller", SearchOption.AllDirectories);
var      animators = new List<RuntimeAnimatorController>();
foreach (string file in files)
{
    // 雑なパス変換
    string assetpath = FileUtil.GetProjectRelativePath(file).Replace("\\", "/");
"/");
    RuntimeAnimatorController anim = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(assetpath);
    if (anim != null)
    {
        animators.Add(anim);
    }
}

Animator の拡張子 .controller のファイル名を Assets/ 下から全て取得、Animator にキャストロードしてリスト化します。拡張子 .controller で Animator ではないファイルの場合、キャストロードが null になるのでリストには積まれません。

アニメーション名を自動で const 化するツール

ついでに公開。使えるかどうかは定かではない。

anim_ -> An にする

const 生成
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;

public class CreateAnimationConst : EditorWindow
{
    /// <summary>
    /// コマンド名
    /// </summary>
    const string ITEM_NAME         = "Tools/Create Animation Const";

    const string WINDOW_TITLE      = "Create Animation Const";
    const string BUTTON_SAVECREATE = "Save & Create";
    const string BUTTON_CLEAR      = "Clear";
    const string PREFS_CS_PATH     = ".cspath";
    const string PREFS_PATTERN     = ".pattern";
    const string PREFS_REPLACEMENT = ".replacement";

    [System.Serializable]
    public class AnimationName
    {
        public string RealName;
        public string Name;
    }

    [System.Serializable]
    public class DataParameter
    {
        /// <summary></summary>
        public string              GroupName;
        /// <summary></summary>
        public List<AnimationName> Names;
        
        /// <summary></summary>
        public DataParameter(string groupName)
        {
            GroupName = groupName;
            Names     = new List<AnimationName>();
        }
    }

    Dictionary<string, DataParameter>    animatorList;
    Vector2                              curretScroll  = Vector2.zero;
    string                               csPath        = null;
    string                               pattern       = null;
    string                               replacement   = null;


    /// <summary>
    /// ウィンドウを開きます
    /// </summary>
    [MenuItem(ITEM_NAME)]
    static void create()
    {
        var window = GetWindow<CreateAnimationConst>(true, WINDOW_TITLE);
        window.Init();
    }

    /// <summary>
    /// ウィンドウオープンの可否を取得します
    /// </summary>
    [MenuItem(ITEM_NAME, true)]
    static bool CanCreate()
    {
        bool enable = !EditorApplication.isPlaying && !Application.isPlaying && !EditorApplication.isCompiling;
        if (enable == false)
        {
            Debug.Log ($"{WINDOW_TITLE}: can't create. wait seconds.");
        }
        return enable;
    }

    /// <summary>
    /// 初期化
    /// </summary>
    void Init()
    {
        loadPrefs();

        animatorList = new Dictionary<string, DataParameter>();

        addResourcesAnimator();
    }

    void saveCode(string cspath)
    {
        StringBuilder sb = new StringBuilder();

        foreach (var pair in animatorList)
        {
            DataParameter param = pair.Value;
            string name = getAnimationName(param.GroupName);

            sb.AppendLine( "///<summary>");
            sb.AppendLine($"/// {name}");
            sb.AppendLine( "///</summary>");
            sb.AppendLine($"public class {name}");
            sb.AppendLine( "{");
            int length = 0;
            foreach (var namepair in param.Names)
            {
                if (length < namepair.Name.Length)
                {
                    length = namepair.Name.Length;
                }
            }

            foreach (var namepair in param.Names)
            {
                sb.AppendLine($"    public const string {namepair.Name.PadRight(length)} = \"{namepair.RealName}\";");
            }
            sb.AppendLine( "}");
            sb.AppendLine( "");
        }
        File.WriteAllText(cspath, sb.ToString(), Encoding.UTF8);
        AssetDatabase.ImportAsset(cspath);
    }

    /// <summary>
    /// GUI を表示する時に呼び出されます
    /// </summary>
    void OnGUI()
    {
        if (animatorList == null)
        {
            Close();
            return;
        }

        GUILayout.Space(20);

        csPath = EditorGUILayout.TextField("source code path", csPath);
        pattern = EditorGUILayout.TextField("regex pattern", pattern);
        replacement = EditorGUILayout.TextField("replacement", replacement);
        GUILayout.Space(20);

        // Button
        EditorGUILayout.BeginHorizontal();
        if (GUILayout.Button(BUTTON_SAVECREATE))
        {
            savePrefs();

            saveCode(csPath);
        }
        if (GUILayout.Button(BUTTON_CLEAR))
        {
            animatorList = new Dictionary<string, DataParameter>();
            addResourcesAnimator();
        }
        EditorGUILayout.EndHorizontal();
        GUILayout.Space(20);

        // AnimatorLabels
        curretScroll = EditorGUILayout.BeginScrollView(curretScroll);
        EditorGUILayout.BeginVertical("box");
        foreach (var pair in animatorList)
        {
            DataParameter param = pair.Value;
            string name = getAnimationName(param.GroupName);

            GUILayout.Label($"{name}", EditorStyles.boldLabel);

            for (int i = 0; i < param.Names.Count; i++)
            {
                param.Names[i].Name = EditorGUILayout.TextField(" ", param.Names[i].Name);
            }

            GUILayout.Space(20);
        }
        EditorGUILayout.EndVertical();
        EditorGUILayout.EndScrollView();
    }

    void addResourcesAnimator()
    {
        string[] files     = Directory.GetFiles(Application.dataPath, "*.controller", SearchOption.AllDirectories);
        var      animators = new List<RuntimeAnimatorController>();
        foreach (string file in files)
        {
            // 雑なパス変換
            string assetpath = FileUtil.GetProjectRelativePath(file).Replace("\\", "/");
            RuntimeAnimatorController anim = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(assetpath);
            if (anim != null)
            {
                animators.Add(anim);
            }
        }

        foreach (RuntimeAnimatorController ac in animators)
        {
            if (ac == null || string.IsNullOrEmpty(ac.name) == true)
            {
                continue;
            }
            if (animatorList.ContainsKey(ac.name) == true)
            {
                continue;
            }
            animatorList.Add(ac.name, new DataParameter(ac.name));

            // Animator に登録された名前を取得
            UnityEditor.Animations.AnimatorController acc = ac as UnityEditor.Animations.AnimatorController;
            for (int layer = 0; layer < acc.layers.Length; layer++)
            {
                for (int s = 0; s < acc.layers[layer].stateMachine.states.Length; s++)
                {
                    UnityEditor.Animations.ChildAnimatorState state = acc.layers[layer].stateMachine.states[s];

                    AnimationName name = new AnimationName();
                    
                    name.RealName = state.state.name;
                    
                    if (name.RealName != ac.name)
                    {
                        // animator:Anim の時、AnimIn -> In
                        name.Name = state.state.motion.name.Replace(ac.name, "");
                    }
                    else
                    {
                        name.Name = getAnimationName(state.state.motion.name);
                    }
                    // FadeIn -> FADE_IN
                    name.Name = Regex.Replace(name.Name, "(?<tag>[a-z0-9]+)(?<tag2>[A-Z]{1}[a-z]+)", "${tag}_${tag2}");
                    name.Name = name.Name.ToUpper();
                    animatorList[ac.name].Names.Add(name);
                }
            }
        }

    }
    
    /// <summary>
    /// アニメーション名取得
    /// </summary>
    string getAnimationName(string name)
    {
        name = Regex.Replace(name, pattern, replacement);
        return name;
    }

    /// <summary>
    /// save prefs
    /// </summary>
    void savePrefs()
    {
        EditorPrefs.SetString(nameof(CreateAnimationConst) + PREFS_CS_PATH, csPath);
        EditorPrefs.SetString(nameof(CreateAnimationConst) + PREFS_PATTERN, pattern);
        EditorPrefs.SetString(nameof(CreateAnimationConst) + PREFS_REPLACEMENT, replacement);
    }

    /// <summary>
    /// load prefs
    /// </summary>
    void loadPrefs()
    {
        csPath = EditorPrefs.GetString(nameof(CreateAnimationConst) + PREFS_CS_PATH);
        pattern = EditorPrefs.GetString(nameof(CreateAnimationConst) + PREFS_PATTERN);
        replacement = EditorPrefs.GetString(nameof(CreateAnimationConst) + PREFS_REPLACEMENT);
        if (string.IsNullOrEmpty(csPath) == true)
        {
            csPath = "Assets/AnimationConst.cs";
        }
    }
}