Programming Serendipity

気まぐれに大まかに生きるブログ

Animatorの自動生成

我々が普段Animatorを作成するときは、まずアニメーションさせたいオブジェクトを選択してAnimationウインドウからCreateを選んで、AnimationClipを追加していきますが、 自分の場合、Parameterを追加して、Transitionを設定して、HasExitTimeのチェックを外して、TransitionDurationは0にして、WriteDefaultsはオフにして、 AnimationClipのほうもLoop Timeのチェックは外さないといけないし、しかも複数同時編集ができない…と、悩みは尽きませんでした。 そこで、定型文が決まっているAnimatorであれば自動生成することにします。 続きからどうぞ。

ここでは、例えばInfoObjというコンポーネントをアタッチされたオブジェクトに対して、そのコンポーネントの情報をもとにアニメーターを自動生成する、ということを考えます。

// InfoObj.cs
using UnityEngine;

public class InfoObj : MonoBehaviour
{
    public int index;
    public int count;
}

例えば、indexを番号としてアニメーターの名前に付与し、countの数だけStateを作成する、という感じです。 以下のようなスクリプトをEditorフォルダに入れてTools > Animation > Create Animatorを選択してください。

// AnimatorCreator.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using System.IO;

/// <summary>
/// Animator / Animation Clip を自動生成
/// </summary>
public static class AnimatorCreator
{
    /// <summary>ファイルを置く場所</summary>
    private const string AssetPath = "Assets/Animations/Test";

    /// <summary>パラメータ名</summary>
    private const string ParamName = "Param";

    /// <summary>
    /// 作成
    /// </summary>
    [MenuItem("Tools/Animation/Create Animator")]
    public static void Create()
    {
        // 選択したオブジェクトをベースにする
        var obj = Selection.activeGameObject;
        if (obj == null)
        {
            Debug.LogErrorFormat("InfoObjがアタッチされたGameObjectを選択した状態で起動してください。");
            return;
        }

        var info = obj.GetComponent<InfoObj>();
        if (info == null)
        {
            Debug.LogErrorFormat("選択されているオブジェクトにInfoObjがアタッチされていません。処理を中止します。");
            return;
        }

        // animator作成
        if (!Directory.Exists(AssetPath))
        {
            Directory.CreateDirectory(AssetPath);
        }

        var path = string.Format("{0}/Anim{1}.controller", AssetPath, info.index);
        if (File.Exists(path))
        {
            if (!EditorUtility.DisplayDialog("Overwrite Confirmation", string.Format("{0}はすでに存在します。上書きして続行しますか?", path), "OK", "Cancel"))
            {
                Debug.LogFormat("操作はキャンセルされました。");
                return;
            }
        }

        // AnimatorController作成
        AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(path);
        animatorController.AddParameter(ParamName, AnimatorControllerParameterType.Int);

        AnimatorStateMachine stateMachine = animatorController.layers[0].stateMachine;

        var states = new List<AnimatorState>();

        // Stateの作成
        for (int i = 0; i < info.count; i++)
        {
            var clip = new AnimationClip();
            clip.wrapMode = WrapMode.Clamp;
            clip.name = string.Format("Anim{0}_Eff{1}", info.index, i);
            AssetDatabase.CreateAsset(clip, string.Format("{0}/{1}.anim", AssetPath, clip.name));
            var state = stateMachine.AddState(clip.name, new Vector2(320, 0 + i * 70));
            state.motion = clip;
            state.writeDefaultValues = false;
            states.Add(state);
        }

        // Transitionの作成
        stateMachine.defaultState = states[0];
        for (int i = 0; i < stateMachine.states.Length - 1; i++)
        {
            var transition = stateMachine.states[i].state.AddTransition(stateMachine.states[i + 1].state);
            transition.AddCondition(AnimatorConditionMode.Equals, i + 1, ParamName);
            transition.hasExitTime = false;
            transition.duration = 0;
        }

        // 設定反映
        var animator = obj.GetComponent<Animator>();
        if (animator == null)
        {
            obj.AddComponent<Animator>();
        }

        animator.runtimeAnimatorController = animatorController;

        // セーブ
        AssetDatabase.SaveAssets();
        EditorUtility.SetDirty(animatorController);
    }
}

解説

ざっと説明すると、

  • 選択中のオブジェクトから情報を取り出し、AnimatorControllerを作成。
  • AnimationClipを作り、Stateを作り、AnimationClipをStateに紐づけ ←これを必要なStateの数だけ繰り返す
  • State間のTransitionを設定して、最後に全体を保存して完成

という感じです。 基本的には公式のAnimatorControllerをベースにしているので特に解説することはないですが、

  • Animatorウインドウの我々が普段見る画面に相当する部分がStateMachineであること
  • StateAnimationClipを入れる場所はmotionという名前であること

あたりが自分が時間を食った部分なので注意しましょう。
終結果↓ f:id:q7z:20170526223737p:plain これが1クリックで作成できるようになります。
Animatorは自動生成するのもいいんじゃないでしょうか。