【Unity】把美術(shù)導(dǎo)入的動(dòng)畫和模型自動(dòng)生成為程序所需要的預(yù)制體

美術(shù)導(dǎo)入的資源格式千奇百怪,我們程序需要的預(yù)制體一般為標(biāo)準(zhǔn)格式,比如模型放一層,物理放一層,腳本放一層。所以我們需要手動(dòng)設(shè)置一次結(jié)構(gòu),我做了個(gè)自動(dòng)化工具就可以一鍵設(shè)置好結(jié)構(gòu)了

美術(shù)所給的動(dòng)畫文件

image.png

自動(dòng)生成工具

image.png

生成一個(gè)程序預(yù)制體和一個(gè)Animator文件

image.png

自動(dòng)生成的controller文件

image.png

自動(dòng)生成的預(yù)制體結(jié)構(gòu)

image.png

下面是源碼

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace XFramework.AutoHandleAnimator
{
    public class AutoHandleAnimatorEditorWindow : EditorWindow
    {
        //動(dòng)畫文件
        public static UnityEngine.Object aniObj;
        //Prefab
        public static UnityEngine.Object prefabObj;

        [MenuItem("美術(shù)工具/生成程序預(yù)制體")]
        public static void OpenWindow()
        {
            GetWindowWithRect<AutoHandleAnimatorEditorWindow>(new Rect(500, 500, 360, 360), false, "生成程序預(yù)制體", true);
        }

        private void OnGUI()
        {
            GUILayout.BeginVertical();

            aniObj = EditorGUILayout.ObjectField("動(dòng)畫文件", aniObj, typeof(UnityEngine.GameObject), false);
            prefabObj = EditorGUILayout.ObjectField("預(yù)制體文件", prefabObj, typeof(UnityEngine.GameObject), false);

            if (GUILayout.Button("生成"))
            {
                if (aniObj != null && prefabObj != null)
                {
                    Generate();
                    base.Close();
                }
            }

            GUILayout.EndVertical();
        }

        private void Generate()
        {
            if (!aniObj.name.EndsWith("_Ani"))
            {
                Debug.LogError("動(dòng)畫文件不以 _Ani 結(jié)尾");
                return;
            }

            if (!prefabObj.name.EndsWith("_Skin"))
            {
                Debug.LogError("預(yù)制體文件不以 _Skin 結(jié)尾");
                return;
            }

            new AutoHandleAnimatorRequest().Generate(aniObj, prefabObj);
        }
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

namespace XFramework.AutoHandleAnimator
{
    public class AutoHandleAnimatorRequest
    {

        public void Generate(Object aniObj, Object prefabObj)
        {
            var aniPath = AssetDatabase.GetAssetPath(aniObj);

            Debug.Log("正在處理:" + aniPath);

            Object[] objs = AssetDatabase.LoadAllAssetsAtPath(aniPath);

            List<AnimationClip> clipsList = new List<AnimationClip>();
            for (int i = 0; i < objs.Length; i++)
            {
                var obj = objs[i];

                if (obj is AnimationClip)
                {
                    AnimationClip ac = obj as AnimationClip;
                    if (ac.name.Contains("Take 001")) continue;//去除預(yù)覽片段
                    if (ac.name.Contains("__preview__")) continue;//去除預(yù)覽片段

                    clipsList.Add(ac);
                }
            }

            var prefabName = GetModelName(aniPath);

            var aniController = CreateAnimatorController(prefabName, clipsList);

            CreatePrefab(prefabName, aniController, prefabObj);
        }

        private void CreatePrefab(string prefabName, AnimatorController controller, Object prefabObj)
        {
            GameObject prefabRoot = new GameObject(prefabName);

            GameObject modelGo = GameObject.Instantiate(prefabObj as GameObject);
            modelGo.name = prefabObj.name;
            modelGo.transform.parent = prefabRoot.transform;

            var animator = modelGo.AddComponent<Animator>();
            animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
            animator.runtimeAnimatorController = controller;

            var targetPath = AutoHandleAnimatorPathSetting.ModelExportPath + prefabName + ".prefab";

            //保存角色預(yù)制體
            PrefabUtility.SaveAsPrefabAssetAndConnect(prefabRoot, targetPath, InteractionMode.UserAction);

            GameObject.DestroyImmediate(prefabRoot);

            Debug.Log("create gameobject: " + targetPath);
        }

        private AnimatorController CreateAnimatorController(string prefabName, List<AnimationClip> clips)
        {
            var path = AutoHandleAnimatorPathSetting.ModelExportPath + prefabName + "_ani.controller";

            AssetDatabase.DeleteAsset(path);

            AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(path);

            int rowCount = 8;//一列幾個(gè) 
            int currentRow = 0;//當(dāng)前列數(shù)
            int currentRowCount = 0;//當(dāng)前列clip個(gè)數(shù)
            AnimatorStateMachine baseLayerMachine = animatorController.layers[0].stateMachine;

            //空狀態(tài)
            baseLayerMachine.entryPosition = Vector3.zero;
            baseLayerMachine.anyStatePosition = new Vector3(0f, 200f);
            baseLayerMachine.AddState("null", new Vector3(0, 100f));
            baseLayerMachine.exitPosition = new Vector3(0f, 300f);

            //添加片段
            for (int i = 0; i < clips.Count; i++)
            {
                if (i % rowCount == 0)
                {
                    currentRow += 1;
                    currentRowCount = 0;
                }
                var x = currentRow * 300;
                var y = currentRowCount * 100;
                currentRowCount++;

                AnimationClip clip = clips[i];
                AnimatorState tempState = baseLayerMachine.AddState(clip.name, new Vector3(x, y, 0));
                tempState.motion = clip;
                EditorUtility.SetDirty(tempState);
            }

            EditorUtility.SetDirty(baseLayerMachine);
            EditorUtility.SetDirty(animatorController);

            Debug.Log("create controller: " + path);

            return animatorController;
        }

        private string GetModelName(string path)
        {
            //女主角_Ani.FBX
            var strs = path.Split('/');
            strs = strs[strs.Length - 1].Split('.');
            strs = strs[0].Split('_');
            var modelName = strs[0];
            return modelName;
        }
    }
}

路徑設(shè)置

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace XFramework.AutoHandleAnimator
{
    public static class AutoHandleAnimatorPathSetting
    {
        /// <summary>
        /// 需要處理的模型根目錄
        /// </summary>
        public const string ModelRootPath = "Assets/Arts/模型/角色/";

        /// <summary>
        /// 模型動(dòng)畫導(dǎo)出路徑
        /// </summary>
        public const string ModelExportPath = "Assets/HotUpdateResources/Prefab/AutoCreateModel/";
    }
}

最后再附帶一個(gè)動(dòng)畫預(yù)覽小工具

image.png
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;

[ExecuteAlways]
[CustomEditor(typeof(Animator), true)]//bool 子類是否可用
public class AniEditor : Editor
{
    //目標(biāo)組件
    private Animator animator;
    //運(yùn)行時(shí)狀態(tài)機(jī)控制器
    private RuntimeAnimatorController controller;
    //動(dòng)畫片段
    private AnimationClip[] clips;
    //選中的片段下標(biāo)
    private int curIndex;
    //模擬時(shí)間
    private float timer;
    //上一幀時(shí)間
    private float lastFrameTime;
    //是在播放中
    private bool isPlaying;

    public void OnEnable()
    {
        Init();
    }

    /// <summary>
    /// 初始化
    /// </summary>
    public void Init()
    {
        isPlaying = false;
        curIndex = 0;
        timer = 0f;
        animator = target as Animator;
        controller = animator.runtimeAnimatorController;

        if (controller)
        {
            clips = controller.animationClips;
        }
        else
        {
            clips = new AnimationClip[0];
        }
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if (clips.Length == 0)
        {
            EditorGUILayout.HelpBox("沒有動(dòng)畫片段!", MessageType.Warning);
            return;
        };

        //檢查代碼塊中是否有任何控件被更改
        EditorGUI.BeginChangeCheck();

        GUILayout.BeginHorizontal();
        GUILayout.Space(10);
        GUILayout.Label("===========");
        GUILayout.Label("<color=#00F5FF>動(dòng)畫預(yù)覽</color>", new GUIStyle() { richText = true, fontStyle = FontStyle.Normal, fontSize = 16 });
        GUILayout.Label("===========");
        GUILayout.Space(10);
        GUILayout.EndHorizontal();


        GUILayout.Label("當(dāng)前片段:");
        //創(chuàng)建一個(gè)通用的彈出式選擇字段。
        curIndex = EditorGUILayout.Popup(curIndex, clips.Select(p => p.name).ToArray()); //還原clip狀態(tài)
        AnimationClip clip = clips[curIndex];

        GUILayout.Label("播放長(zhǎng)度:");
        timer = EditorGUILayout.Slider(timer, 0, clip.length);
        string playAniBtnStr = isPlaying ? "暫停" : "播放動(dòng)畫";

        if (GUILayout.Button(playAniBtnStr))
        {
            SetPlayAni(timer >= clip.length);
        }

        if (GUILayout.Button("返回到當(dāng)前片段第一幀"))
        {
            isPlaying = false;
            timer = 0;
            clip.SampleAnimation(animator.gameObject, timer);
        }

        if (isPlaying)
        {
            timer += Time.realtimeSinceStartup - lastFrameTime;
            if (timer >= clip.length)
            {
                if (clip.isLooping)
                {
                    timer = 0;
                }
                else
                {
                    isPlaying = false;
                    timer = clip.length;
                }
            }
        }

        //重新繪制顯示此編輯器的檢查器。
        Repaint();
        lastFrameTime = Time.realtimeSinceStartup;
        if (EditorGUI.EndChangeCheck() || isPlaying)
        {
            clip.SampleAnimation(animator.gameObject, timer);
        }
    }

    /// <summary>
    /// 播放動(dòng)畫
    /// </summary>
    /// <param name="rePlay"></param>
    private void SetPlayAni(bool rePlay)
    {
        if (rePlay)
        {
            timer = 0f;
            isPlaying = true;
        }
        else
        {
            isPlaying = !isPlaying;
        }
    }


}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容