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

Last-modified: 2016-05-18 (水) 11:51:50
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))]
[RequireComponent(typeof(Animator))]
public class Character2 : MonoBehaviour
{
#if
    /// <summary>画像</summary>
    public Texture2D texture;

    /// <summary>
    /// エディタ拡張
    /// </summary>
    [CustomEditor(typeof(Character2))]
    [CanEditMultipleObjects]
    public class CharacterEditor : Editor
    {
        /// <summary>
        /// 設定
        /// </summary>
        private static class Settings
        {
            /// <summary>アニメーション設定</summary>
            public static List<AnimationSetting> animations =
                new List<AnimationSetting>()
                {
                    new AnimationSetting("IdolDown",new List<int>() {1}, 1, false, "IdolTree", 0, -1),
                    new AnimationSetting("IdolLeft",new List<int>() {4}, 1, false, "IdolTree", -1, 0),
                    new AnimationSetting("IdolRight",new List<int>() {7}, 1, false, "IdolTree", 1, 0),
                    new AnimationSetting("IdolUp",new List<int>() {10}, 1, false, "IdolTree", 0, 1),

                    new AnimationSetting("WalkDown",new List<int>() {0,1,2,1}, 4, true, "WalkTree", 0, -1),
                    new AnimationSetting("WalkLeft",new List<int>() {3,4,5,4}, 4, true, "WalkTree", -1, 0),
                    new AnimationSetting("WalkRight",new List<int>() {6,7,8,7}, 4, true, "WalkTree", 1, 0),
                    new AnimationSetting("WalkUp",new List<int>() {9,10,11,10}, 4, true, "WalkTree", 0, 1),
                };

            /// <summary>パラメータ</summary>
            public static List<AnimatorControllerParameter> parameters =
                new List<AnimatorControllerParameter>()
                {
                    new AnimatorControllerParameter()
                    {
                        name = "DirectionX",
                        type = AnimatorControllerParameterType.Float,
                        defaultFloat = 0f
                    },
                    new AnimatorControllerParameter()
                    {
                        name = "DirectionY",
                        type = AnimatorControllerParameterType.Float,
                        defaultFloat = 0f
                    },
                    new AnimatorControllerParameter()
                    {
                        name = "Walking",
                        type = AnimatorControllerParameterType.Bool,
                        defaultBool = false
                    },
                };

            /// <summary>BlendTree</summary>
            public static List<BlendTreeSetting> blendTrees =
                new List<BlendTreeSetting>()
                {
                    new BlendTreeSetting(
                        "IdolTree",
                         BlendTreeType.SimpleDirectional2D,
                         parameters[0].name,
                         parameters[1].name),
                    new BlendTreeSetting(
                        "WalkTree",
                         BlendTreeType.SimpleDirectional2D,
                         parameters[0].name,
                         parameters[1].name),
                };

            /// <summary>遷移</summary>
            public static List<TransitionSetting> transitions =
                new List<TransitionSetting>()
                {
                    new TransitionSetting(
                        "IdolTree",
                        "WalkTree",
                        parameters[2].name,
                        AnimatorConditionMode.If,
                        0),
                    new TransitionSetting(
                        "WalkTree",
                        "IdolTree",
                        parameters[2].name,
                        AnimatorConditionMode.IfNot,
                        0),
                };
        }

        /// <summary>
        /// アニメーションの設定
        /// </summary>
        private class AnimationSetting
        {
            /// <summary>名前</summary>
            public string Name { get; set; }
            /// <summary>スプライトのインデックスリスト</summary>
            public List<int> SpriteIndexes { get; set; }
            /// <summary>フレームレート</summary>
            public int FrameRate { get; set; }
            /// <summary>ループフラグ</summary>
            public bool Loop { get; set; }
            /// <summary>登録するBlendTree名</summary>
            public string BlendTreeName { get; set; }
            /// <summary>登録するBlendTree座標</summary>
            public Vector2 BlendTreePosition { get; set; }

            public AnimationSetting(
                string name,
                List<int> spriteIndexes,
                int frameRate,
                bool loop,
                string blendTreeName,
                Vector2 blendTreePosition)
            {
                Name = name;
                SpriteIndexes = spriteIndexes;
                FrameRate = frameRate;
                Loop = loop;
                BlendTreeName = blendTreeName;
                BlendTreePosition = blendTreePosition;
            }

            public AnimationSetting(
                string name,
                List<int> spriteIndexes,
                int frameRate,
                bool loop,
                string blendTreeName,
                float blendParameterX,
                float blendParameterY)
                : this(name, spriteIndexes, frameRate, loop, blendTreeName, new Vector2(blendParameterX, blendParameterY))
            { }

            public AnimationSetting(
                string name,
                List<int> spriteIndexes,
                int frameRate,
                bool loop)
                : this(name, spriteIndexes, frameRate, loop, "", Vector2.zero)
            { }
        }

        /// <summary>
        /// フレーム情報
        /// </summary>
        private class AnimationFrame
        {
            /// <summary>スプライト</summary>
            public Sprite Sprite { get; set; }
            /// <summary>フレーム番号</summary>
            public int FrameNo { get; set; }

            public AnimationFrame(Sprite sprite, int frameNo)
            {
                Sprite = sprite;
                FrameNo = frameNo;
            }
        }

        /// <summary>
        /// BlendTree設定
        /// </summary>
        private class BlendTreeSetting
        {
            /// <summary>名前</summary>
            public string Name { get; set; }
            /// <summary>種別</summary>
            public BlendTreeType BlendType { get; set; }
            /// <summary>X軸パラメータ</summary>
            public string BlendParameterX { get; set; }
            /// <summary>Y軸パラメータ</summary>
            public string BlendParameterY { get; set; }

            public BlendTreeSetting(
                string name,
                BlendTreeType blendType,
                string blendParameterX,
                string blendParameterY)
            {
                Name = name;
                BlendType = blendType;
                BlendParameterX = blendParameterX;
                BlendParameterY = blendParameterY;
            }
        }

        /// <summary>
        /// 遷移情報
        /// </summary>
        private class TransitionSetting
        {
            /// <summary>遷移元</summary>
            public string Source { get; set; }
            /// <summary>遷移先</summary>
            public string Description { get; set; }

            /// <summary>パラメータ名</summary>
            public string ConditionParameter { get; set; }
            /// <summary>モード</summary>
            public AnimatorConditionMode ConditionMode { get; set; }
            /// <summary>閾値</summary>
            public float ConditionThreshold { get; set; }

            public TransitionSetting(
                string source,
                string description,
                string parameter,
                AnimatorConditionMode mode,
                float threshold)
            {
                Source = source;
                Description = description;
                ConditionParameter = parameter;
                ConditionMode = mode;
                ConditionThreshold = threshold;
            }
        }

        /// <summary>
        /// AnimationClip生成
        /// </summary>
        /// <param name="sprites">スプライト</param>
        /// <param name="setting">アニメーション設定</param>
        /// <returns></returns>
        private AnimationClip MakeAnimacionCrip(IList<Sprite> sprites, AnimationSetting setting)
        {
            //スプライトとフレーム番号のペアを生成していく
            var animationFrames = new List<AnimationFrame>();
            for(int i = 0; i < setting.SpriteIndexes.Count; ++i)
            {
                var index = setting.SpriteIndexes[i];
                if(index < 0 || index >= sprites.Count)
                    continue;

                animationFrames.Add(new AnimationFrame(sprites[index], i));
            }

            return MakeAnimacionCrip(
                setting.Name,
                animationFrames,
                setting.FrameRate,
                setting.Loop);
        }

        /// <summary>
        /// AnimationClip生成
        /// </summary>
        /// <param name="name">名前</param>
        /// <param name="frames">フレームのコレクション</param>
        /// <param name="frameRate">フレームレート</param>
        /// <param name="loop">ループ</param>
        /// <returns></returns>
        private AnimationClip MakeAnimacionCrip(
            string name,
            ICollection<AnimationFrame> frames,
            float frameRate,
            bool loop)
        {
            var clip = new AnimationClip();
            var binding = new EditorCurveBinding();

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

            //キーフレーム設定
            var keyFrames = new List<ObjectReferenceKeyframe>();
            foreach(var frame in frames)
            {
                keyFrames.Add(new ObjectReferenceKeyframe()
                {
                    time = frame.FrameNo / frameRate,
                    value = frame.Sprite
                });
            }

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

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

            return clip;
        }

        /// <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>Inspector折りたたみフラグ</summary>
        private static bool foldout = true;

        /// <summary>
        /// Inspectorのカスタム
        /// </summary>
        public override void OnInspectorGUI()
        {
            var t = (Character2)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 + "/";

                if(t.texture == null)
                {
                    Debug.LogError("Textureが設定されていません。");
                    return;
                }

                //アセットパス
                var assetPath = folderPath + t.texture.name + extensionController;

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

                //スプライト取得
                var sprites = AD.LoadAllAssetsAtPath(AD.GetAssetPath(t.texture)).
                    Where(x => x.GetType() == typeof(Sprite)).
                    Select(x => (Sprite)x).
                    ToList();
                if(sprites == null || sprites.Count <= 0)
                {
                    Debug.LogError("スプライトを取得できませんでした");
                    return;
                }

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

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

                //Parameter登録
                foreach(var param in Settings.parameters)
                    controller.AddParameter(param);

                //BlendTree登録
                var blendTrees = new List<BlendTree>();
                foreach(var blendTree in Settings.blendTrees)
                {
                    //BlendTreeをClone (削除されると困るので)
                    var tree = new BlendTree()
                    {
                        name = blendTree.Name,
                        blendType = blendTree.BlendType,
                        blendParameter = blendTree.BlendParameterX,
                        blendParameterY = blendTree.BlendParameterY
                    };

                    blendTrees.Add(tree);
                    controller.AddMotion(tree);
                }

                //AnimationClip生成、登録
                foreach(var animSetting in Settings.animations)
                {
                    var clip = MakeAnimacionCrip(sprites, animSetting);

                    if(animSetting.BlendTreeName != "")
                        blendTrees.Single(x => x.name == animSetting.BlendTreeName).AddChild(
                            clip,
                            animSetting.BlendTreePosition);
                    else
                        controller.AddMotion(clip);
                }

                //Transition生成、登録
                var stateMachine = controller.layers[0].stateMachine;
                foreach(var tranSetting in Settings.transitions)
                {
                    var src = stateMachine.states.Single(x => x.state.name == tranSetting.Source);
                    var des = stateMachine.states.Single(x => x.state.name == tranSetting.Description);

                    var transition = src.state.AddTransition(des.state);
                    transition.AddCondition(
                        tranSetting.ConditionMode,
                        tranSetting.ConditionThreshold,
                        tranSetting.ConditionParameter);
                }

                //デフォルトスプライトを設定
                t.GetComponent<SpriteRenderer>().sprite =
                    sprites[Settings.animations[0].SpriteIndexes[0]];

                //アセットファイル出力
                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)
                    {
                        //重複防止のリネーム
                        clip.name = t.texture.name + "_" + clip.name;
                        AD.CreateAsset(clip, folderPath + clip.name + extensionClip);
                    }
                }

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