アニメ自動生成/エディタ拡張

Last-modified: 2016-05-18 (水) 11:55:18
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

#if
using UnityEditor;
using UnityEditor.Animations;
using AD = UnityEditor.AssetDatabase;
using EGL = UnityEditor.EditorGUILayout;
#endif

/// <summary>
/// キャラクタ
/// </summary>
[RequireComponent(typeof(SpriteRenderer))]
public class Character : MonoBehaviour
{
#if
    /// <summary>
    /// エディタ拡張
    /// </summary>
    [CustomEditor(typeof(Character))]
    [CanEditMultipleObjects]
    public class CharacterEditor : Editor
    {
        /// <summary>
        /// 設定
        /// </summary>
        private static class Settings
        {
            /// <summary>
            /// 名前設定
            /// </summary>
            public static class Names
            {
                /// <summary>ステート名接頭語:歩行中</summary>
                public const string stateWalk = "Walk";
                /// <summary>ステート名接頭語:停止</summary>
                public const string stateIdol = "Idol";

                /// <summary>パラメータ名:X方向</summary>
                public const string paramDirecitonX = "DirectionX";
                /// <summary>パラメータ名:Y方向</summary>
                public const string paramDirecitonY = "DirectionY";
                /// <summary>パラメータ名:歩行中フラグ</summary>
                public const string paramWalking = "Walking";
            }

            /// <summary>方向名とBlendTreeにおける座標ペア</summary>
            public static List<Direction> directions =
                new List<Direction>()
                {
                   new Direction("Down",new Vector2(0,-1) ),    //テクスチャ1行目
                   new Direction("Left",new Vector2(-1,0) ),    //2行目
                   new Direction("Right",new Vector2(1,0) ),    //3行目
                   new Direction("Up",new Vector2(0,1) ),       //4行目
                };
        }

        /// <summary>
        /// 方向名とBlendTreeにおける座標ペアを保持するクラス
        /// </summary>
        private class Direction
        {
            public string Name { get; set; }
            public Vector2 TreePosition { get; set; }

            public Direction(string name, Vector2 treePosition)
            {
                Name = name;
                TreePosition = treePosition;
            }
        }

        /// <summary>AnimatorControllerアセット拡張子</summary>
        private const string extensionController = ".controller";
        /// <summary>AnimationClipアセット拡張子</summary>
        private const string extensionClip = ".anim";

        /// <summary>アセット保存フォルダパス</summary>
        private static string folderPath = "Assets/Animations/";
        /// <summary>AnimationClipをAnimatorControllerの子アセットにするフラグ</summary>
        private static bool subassetFlag = true;

        /// <summary>歩行パターン(インデックスのList)</summary>
        private static List<int> walkIndexes = new List<int>() { 0, 1, 2, 1 };
        /// <summary>静止画インデックス</summary>
        private static int idolIndex = 1;

        /// <summary>Inspector折りたたみフラグ</summary>
        private static bool foldout = false;

        /// <summary>
        /// Inspectorのカスタム
        /// </summary>
        public override void OnInspectorGUI()
        {
            var t = (Character)target;

            //デフォルトInspector
            DrawDefaultInspector();

            //歩行アニメーション自動生成ボタン&実装
            EGL.Separator();
            foldout = EGL.Foldout(foldout, "歩行アニメーション自動生成");
            if(!foldout)
                return;

            //設定
            folderPath = EGL.TextField("フォルダパス", folderPath);
            subassetFlag = EGL.Toggle("Clipサブアセット化", subassetFlag);
            if(subassetFlag)
                EGL.HelpBox(
                    "AnimatorControllerアセットの子アセットとしてAnimationClipを保存",
                    MessageType.Info);
            else
                EGL.HelpBox(
                    "AnimatorControllerアセットと同じフォルダにAnimationClipアセットを保存",
                    MessageType.Info);

            if(GUILayout.Button("歩行アニメーション生成"))
            {
                if(!folderPath.EndsWith("/"))
                    folderPath = folderPath + "/";

                var sr = t.GetComponent<SpriteRenderer>();
                if(sr == null || sr.sprite == null)
                {
                    Debug.LogError("SpriteRenderer.sprite が見つかりません。");
                    return;
                }

                var allasset =
                    new List<Object>(
                        AD.LoadAllAssetsAtPath(AD.GetAssetPath(sr.sprite)));
                var texture =
                    (Texture2D)allasset.
                    Single(x => x.GetType() == typeof(Texture2D));
                var sprites =
                    allasset.
                    Where(x => x.GetType() == typeof(Sprite)).
                    Select(x => (Sprite)x).ToList();

                var assetPath = folderPath + texture.name + extensionController;

                //ファイル存在確認
                if(AD.LoadAssetAtPath<Object>(assetPath))
                {
                    Debug.LogError("既に " + assetPath + " が存在します。");
                    return;
                }

                //Animatorアタッチ
                var animator = t.GetComponent<Animator>();
                if(animator == null)
                    animator = t.gameObject.AddComponent<Animator>();

                //AnimatorController生成
                var controller = new AnimatorController();
                controller.name = texture.name;
                controller.AddLayer("Base Layer");
                animator.runtimeAnimatorController = controller;

                //Parameterを登録
                controller.AddParameter(
                    Settings.Names.paramDirecitonX,
                    AnimatorControllerParameterType.Float);
                controller.AddParameter(
                    Settings.Names.paramDirecitonY,
                    AnimatorControllerParameterType.Float);
                controller.AddParameter(
                    Settings.Names.paramWalking,
                    AnimatorControllerParameterType.Bool);
                controller.parameters[0].defaultFloat = 0;
                controller.parameters[1].defaultFloat = -1;
                controller.parameters[2].defaultBool = false;

                //AnimationClip生成
                var walkClipsWithPos = new List<KeyValuePair<AnimationClip, Vector2>>();
                var idolClipsWithPos = new List<KeyValuePair<AnimationClip, Vector2>>();
                for(int i = 0; i < Settings.directions.Count; ++i)
                {
                    ////静止アニメーション
                    idolClipsWithPos.Add(new KeyValuePair<AnimationClip, Vector2>(
                        MakeAnimacionCrip(
                            texture.name + "_" + Settings.Names.stateIdol + Settings.directions[i].Name,
                            sprites.Skip(i * 3).Take(3).ToList(),
                            new List<int>() { idolIndex }),
                        Settings.directions[i].TreePosition));

                    ////歩行アニメーション
                    walkClipsWithPos.Add(new KeyValuePair<AnimationClip, Vector2>(
                        MakeAnimacionCrip(
                            texture.name + "_" + Settings.Names.stateWalk + Settings.directions[i].Name,
                            sprites.Skip(i * 3).Take(3).ToList(),
                            walkIndexes),
                        Settings.directions[i].TreePosition));
                }

                //BlendTree生成・登録
                var stateMachine = controller.layers[0].stateMachine;
                var idolTree = MakeBlendTree(Settings.Names.stateIdol + "Tree", idolClipsWithPos);
                var walkTree = MakeBlendTree(Settings.Names.stateWalk + "Tree", walkClipsWithPos);
                var idolState = stateMachine.AddState(Settings.Names.stateIdol + "Tree");
                var walkState = stateMachine.AddState(Settings.Names.stateWalk + "Tree");
                idolState.motion = idolTree;
                walkState.motion = walkTree;

                //BlendTree間のTransition生成
                var idolWalkTransition = idolState.AddTransition(walkState);
                var walkIdolTransition = walkState.AddTransition(idolState);
                idolWalkTransition.AddCondition(
                    AnimatorConditionMode.If,
                    0,
                    Settings.Names.paramWalking);
                walkIdolTransition.AddCondition(
                    AnimatorConditionMode.IfNot,
                    0,
                    Settings.Names.paramWalking);

                //デフォルトスプライトを設定
                sr.sprite = sprites[1];

                //アセットファイル出力
                AD.CreateAsset(controller, assetPath);
                AD.AddObjectToAsset(stateMachine, controller);
                foreach(var state in stateMachine.states)
                {
                    AD.AddObjectToAsset(state.state, controller);
                    if(!(state.state.motion is AnimationClip))
                        AD.AddObjectToAsset(state.state.motion, controller);
                    foreach(var transition in state.state.transitions)
                        AD.AddObjectToAsset(transition, controller);
                }

                //AnimationClip出力
                if(subassetFlag)
                {
                    //サブアセットとして登録
                    foreach(var clip in controller.animationClips)
                        AD.AddObjectToAsset(clip, controller);
                    AD.ImportAsset(AD.GetAssetPath(controller));
                }
                else
                {
                    //アセットとして保存
                    foreach(var clip in controller.animationClips)
                        AD.CreateAsset(clip, folderPath + clip.name + extensionClip);
                }

                AD.SaveAssets();
                AD.Refresh();
            }
        }

        /// <summary>
        /// AnimationClip生成
        /// </summary>
        /// <param name="name">名前</param>
        /// <param name="sprites">スプライトList</param>
        /// <param name="animationIndexes">アニメーションのインデックス</param>
        /// <returns></returns>
        private AnimationClip MakeAnimacionCrip(
            string name,
            IList<Sprite> sprites,
            IList<int> animationIndexes)
        {
            var clip = new AnimationClip();
            var binding = new EditorCurveBinding();

            binding.path = "";
            binding.type = typeof(SpriteRenderer);
            binding.propertyName = "m_Sprite";

            //キーフレーム設定
            var keyFrames = new List<ObjectReferenceKeyframe>();
            for(int i = 0; i < animationIndexes.Count; i++)
            {
                if(animationIndexes[i] >= sprites.Count)
                {
                    Debug.LogWarning(
                        "スプライト " +
                        sprites.Count +
                        "枚 に対してインデックス " +
                        animationIndexes[i] +
                        " が指定されたのでスキップします");
                    continue;
                }

                keyFrames.Add(new ObjectReferenceKeyframe()
                {
                    time = (float)i / animationIndexes.Count,
                    value = sprites[animationIndexes[i]]
                });
            }

            //名前、フレームレート、ループ設定
            clip.name = name;
            clip.frameRate = animationIndexes.Count;
            var settings = AnimationUtility.GetAnimationClipSettings(clip);
            settings.loopTime = true;
            AnimationUtility.SetAnimationClipSettings(clip, settings);

            //登録
            AnimationUtility.SetObjectReferenceCurve(
                clip,
                binding,
                keyFrames.ToArray());

            return clip;
        }

        /// <summary>
        /// BlencTree生成
        /// </summary>
        /// <param name="name">名前</param>
        /// <param name="clipWithPositions">Clipと座標のペア</param>
        /// <returns></returns>
        private BlendTree MakeBlendTree(
            string name,
            ICollection<KeyValuePair<AnimationClip, Vector2>> clipWithPositions)
        {
            var tree = new BlendTree();
            tree.name = name;
            tree.blendType = BlendTreeType.SimpleDirectional2D;
            tree.blendParameter = Settings.Names.paramDirecitonX;
            tree.blendParameterY = Settings.Names.paramDirecitonY;

            foreach(var clip in clipWithPositions)
                tree.AddChild(clip.Key, clip.Value);

            return tree;
        }
    }

#endif
}