Unity Animator Controller相关脚本集

作者: Mars_JianShu | 来源:发表于2017-10-08 11:56 被阅读166次

    新项目中使用了unity的动画控制器animator,写了以下几个小脚本。
    1.导入fbx并拆分其中的动画,修改fbx导入设置。
    2.导出fbx中的动画到指定目录,生成独立的Animation Clip.
    3.动态创建及修改Animator Controller.

    1.美术把fbx提交svn的同时配上一个txt,里面指定哪一帧到哪一帧为哪个动作。脚本根据这个配置去拆分动作,并保存fbx.这样就不需要美术同学手动去拆分动作,而且可以在unity中方便地预览指定的动作。

    code:

    fbx_bone_1001.txt

    idle               80   140       loop
    fire               330  340       loop
    walk               1850 1886      loop
    run                430  446       loop
    dead               680  724
    attack             1290 1320
    

    FbxAnimListPostprocessor.cs

    // FbxAnimListPostprocessor.cs : Use an external text file to import a list of 
    // splitted animations for FBX 3D models.
    //
    // Put this script in your "Assets/Editor" directory. When Importing or 
    // Reimporting a FBX file, the script will search a text file with the 
    // same name and the ".txt" extension.
    // File format: one line per animation clip "firstFrame-lastFrame loopFlag animationName"
    // The keyworks "loop" or "noloop" are optional.
    // Example:
    // idle               80   140       loop
    // dead               680  724
    
    using UnityEngine;
    using UnityEditor;
    using System.Collections;
    using System.IO;
    using System.Text.RegularExpressions;
    using System;
    using UnityEditor.Animations;
    
    public class FbxAnimListPostprocessor : AssetPostprocessor
    {
        public void OnPreprocessModel()
        {
            if (Path.GetExtension(assetPath).ToLower() == ".fbx"
                && !assetPath.Contains("@"))
            {
                try
                {
                    // Remove 6 chars because dataPath and assetPath both contain "assets" directory
                    string fileAnim = Application.dataPath + Path.ChangeExtension(assetPath, ".txt").Substring(6);
                    StreamReader file = new StreamReader(fileAnim);
    
                    string sAnimList = file.ReadToEnd();
                    file.Close();
    
                    //if (EditorUtility.DisplayDialog("FBX Animation Import from file",
                        //fileAnim, "Import", "Cancel"))
                    {
                        System.Collections.ArrayList List = new ArrayList();
                        ParseAnimFile(sAnimList, ref List);
    
                        ModelImporter modelImporter = assetImporter as ModelImporter;
                        modelImporter.splitAnimations = true;
                        modelImporter.clipAnimations = (ModelImporterClipAnimation[])
                            List.ToArray(typeof(ModelImporterClipAnimation));
                            
                            // 根据项目需要可选
                        /* modelImporter.motionNodeName = "<Root Transform>";
    
                        modelImporter.importMaterials = false;
    
                        modelImporter.animationRotationError = 0.5f;
                        modelImporter.animationPositionError = 0.1f;
                        modelImporter.animationScaleError = 0.5f;
                        modelImporter.animationType = ModelImporterAnimationType.Generic; */
    
                        //EditorUtility.DisplayDialog("Imported animations",
                        //    "Number of imported clips: "
                        //    + modelImporter.clipAnimations.GetLength(0).ToString(), "OK");
                    }
                }
                catch { }
                // (Exception e) { EditorUtility.DisplayDialog("Imported animations", e.Message, "OK"); }
            }
    
    
        }
        public static void ParseAnimFile(string sAnimList, ref System.Collections.ArrayList List)
        {
            Regex regexString = new Regex(" *(?<name>\\w+) *(?<firstFrame>[0-9]+) *(?<lastFrame>[0-9]+) *(?<loop>(loop|noloop|.))",
                RegexOptions.Compiled | RegexOptions.ExplicitCapture);
            Match match = regexString.Match(sAnimList, 0);
            while (match.Success)
            {
                ModelImporterClipAnimation clip = new ModelImporterClipAnimation();
    
                if (match.Groups["firstFrame"].Success)
                {
                    clip.firstFrame = System.Convert.ToInt32(match.Groups["firstFrame"].Value, 10);
                }
                if (match.Groups["lastFrame"].Success)
                {
                    clip.lastFrame = System.Convert.ToInt32(match.Groups["lastFrame"].Value, 10);
                }
                if (match.Groups["loop"].Success)
                {
                    clip.loop = match.Groups["loop"].Value == "loop";
                    clip.loopTime = match.Groups["loop"].Value == "loop";
                    clip.loopPose = match.Groups["loop"].Value == "loop";
                }
                if (match.Groups["name"].Success)
                {
                    clip.name = match.Groups["name"].Value;
                }
    
                List.Add(clip);
    
                match = regexString.Match(sAnimList, match.Index + match.Length);
            }
        }
    }
    

    引用网址:http://wiki.unity3d.com/index.php/FbxAnimListPostprocessor
    改了一下正则,是由于美术3dmax中导出的txt是 idle 80 140 这种格式的。

    2.把在fbx中拆分好的动画导出到指定目录下,生成一个个独立的Animation Clip,每个clip单独打assetbundle,这样可以使得更新某个动作时可以尽可能少地更新资源。

    code

    AnimClipExtract.cs

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using UnityEngine.UI;
    using UnityEditor;
    using System.Linq;
    
    public class AnimClipExtract
    {
        [MenuItem("Tools/ExtractAnim")]
        static void ExtractAnimClipTool()
        {
            Object[] objs = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
    
            var fbxPaths = objs.Select(x => AssetDatabase.GetAssetPath(x))
                                .Where(x => x.ToLower().EndsWith("fbx"));
    
            if(fbxPaths.Count() == 0)
                Debug.LogError("未选择fbx文件;请至少选中一个fbx文件!");
    
            fbxPaths.ToList().ForEach(ExtractAnimClip);
        }
    
        public static void ExtractAnimClip(string fbxPath)
        {
            if(!fbxPath.ToLower().EndsWith("fbx"))
            {
                Debug.LogError(fbxPath + " 不是有效的FBX文件");
                return;
            }
    
            if (fbxPath.Contains(Application.dataPath))
                fbxPath = "Assets" + "/" + fbxPath.Substring(Application.dataPath.Length+1);
    
            Object[] assets = AssetDatabase.LoadAllAssetsAtPath(fbxPath);
            if(assets.Length == 0)
            {
                Debug.LogError(fbxPath + " 读取FBX文件失败;");
                return;
            }
    
            try
            {
                string fbxName = Path.GetFileNameWithoutExtension(fbxPath);
                string caption1 = "AnimClipExtractTool - " + fbxName + ".fbx";
                EditorUtility.DisplayProgressBar(caption1, fbxPath + "分析中", 0);
    
                int start = fbxName.ToLower().IndexOf("fbx_bone_");
                start = start == -1 ? 0 : start + "fbx_bone_".Length;
                string Dir1 = "Art/AnimClip";
                Directory.CreateDirectory(Application.dataPath + "/" + Dir1);
                string Dir2 = Dir1 + "/" + fbxName.Substring(start);
                string extractDirectory = Application.dataPath + "/" + Dir2;
                Directory.CreateDirectory(extractDirectory);
                string[] fileArray = Directory.GetFiles(extractDirectory, "*.anim", SearchOption.AllDirectories);
                foreach(string filePath in fileArray)
                {
                    File.Delete(filePath);
                }
                string extractDir = "Assets" + "/" + Dir2;
                //这里先生成到tmp目录再拷贝覆盖,是因为直接生成到目标目录meta文件的guid会变
                string tmpDir = "Assets/Art/AnimClip/tmp/";
                Directory.CreateDirectory(tmpDir);
    
                var clipAssets = assets.Where(a => a is AnimationClip);
    
                int i = 0;
                foreach (var item in clipAssets)
                {
                    var clone = Object.Instantiate(item);
                    //AssetDatabase.CreateAsset(clone, extractDir + "/" + item.name + ".anim");
                    AssetDatabase.CreateAsset(clone, tmpDir + item.name + ".anim");
                    string srcFile = Application.dataPath + "/" + "Art/AnimClip/tmp/" + item.name + ".anim";
                    string destFile = Application.dataPath + "/" + Dir2 + "/" + item.name + ".anim";
                    File.Copy(srcFile, destFile, true);
    
                    EditorUtility.DisplayProgressBar(caption1, "导出" + item.name + ".anim", i++ / clipAssets.Count());
                }
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
    
                EditorUtility.ClearProgressBar();
                Debug.Log("导出位置:" + extractDir + " 导出数量:" + i + "  anim导出完成 " + fbxPath);
    
            }
            finally
            {
                EditorUtility.ClearProgressBar();
            }
        }
    }
    

    例:选中一个名叫fbx_bone_1001.fbx的文件,使用Tools\ExtractAnim,会在Assets/Art/AnimClip下生成1001的文件夹,并生成Animation Clip生成到该目录下。

    3.做好第一个单位的Animator Controller之后,后面的单位以这个为模版生成自己的controller.对于已经存在的controller,会使用第2步生成的Animation Clip根据名字替换controller中每一个节点的motion

    code

    UpdateAnimatorController.cs

    using System.Collections.Generic;
    using UnityEditor;
    using UnityEngine;
    using UnityEditor.Animations;
    using System.IO;
    using System.Collections;
    
    public class UpdateAnimatorController
    {
        static void GenerateController(string fbxPath)
        {
            string fbxName = Path.GetFileNameWithoutExtension(fbxPath);
            string startStr = "fbx_bone_";
            if (fbxName.StartsWith(startStr))
            {
                int start = startStr.Length;
                string unitName = fbxName.Substring(start);
                string unitId = unitName.Substring(0, unitName.IndexOf('_'));
                string controllerName = unitId + ".controller";
                string controllerPath = "Art/AnimController/" + controllerName;
                string controllerAssetPath = "Asset/" + controllerPath;
                string controllerFullPath = Application.dataPath + "/" + controllerPath;
    
                if (!File.Exists(controllerFullPath))
                {
                    File.Copy(Application.dataPath + "/" + "Art/AnimController/1001.controller", controllerFullPath);
                }
    
                if (File.Exists(controllerFullPath))
                {
                    List<AnimationClip> clipList = new List<AnimationClip>();
                    clipList.Clear();
    
    
                    string animClipFolder = Application.dataPath + "/" + "Art/AnimClip/" + unitId;
                    if (Directory.Exists(animClipFolder))
                    {
                        DirectoryInfo directory = new DirectoryInfo(animClipFolder);
                        FileInfo[] files = directory.GetFiles("*.anim", SearchOption.AllDirectories);
    
                        foreach (FileInfo file in files)
                        {
                            string animPath = file.ToString();
                            string animAssetPath = animPath.Substring(animPath.IndexOf("Assets"));
                            AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(animAssetPath);
                            if (clip != null)
                            {
                                clipList.Add(clip);
                            }
                        }
                    }
                    AnimatorController animController = AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerAssetPath);
                    for (int i = 0; i < animController.layers.Length; i++)
                    {
                        AnimatorStateMachine stateMachine = animController.layers[i].stateMachine;
                        UpdateAnimator(clipList, stateMachine);
                    }
                }
            }
            else
            {
                Debug.Log("fbx name not valid. ");
            }
        }
    
        static void UpdateAnimator(List<AnimationClip> newClips, AnimatorStateMachine stateMachine)
        {
            for (int i = 0; i < stateMachine.states.Length; i++)
            {
                ChildAnimatorState childState = stateMachine.states[i];
                if (childState.state.motion == null)
                {
                    if (childState.state.name.CompareTo("New State") == 0 || childState.state.name.CompareTo("empty") == 0)
                        continue;
    
                    Debug.LogWarning(" UpdateAnimatorController Null : " + childState.state.name + ",layer name: " + stateMachine.name);
                    continue;
                }
                if (childState.state.motion.GetType() == typeof(AnimationClip))
                {
                    for (int j = 0; j < newClips.Count; j++)
                    {
                        if (newClips[j].name.CompareTo(childState.state.motion.name) == 0)
                        {
                            childState.state.motion = (Motion)newClips[j];
                            break;
                        }
                    }
                }
                else if (childState.state.motion.GetType() == typeof(UnityEditor.Animations.BlendTree))
                {
    
                    UnityEditor.Animations.BlendTree tree = (UnityEditor.Animations.BlendTree)childState.state.motion;
                    BlendTreeType treeType = tree.blendType;
    
                    ChildMotion[] childMotionArray = tree.children;
    
                    for (int k = 0; k < childMotionArray.Length; k++)
                    {
                        if (childMotionArray[k].motion.GetType() == typeof(AnimationClip))
                        {
                            for (int j = 0; j < newClips.Count; j++)
                            {
                                if (newClips[j].name.CompareTo(childMotionArray[k].motion.name) == 0)
                                {
                                    childMotionArray[k].motion = (Motion)newClips[j];
                                    break;
                                }
                            }
                        }
                        else if (childMotionArray[k].motion.GetType() == typeof(UnityEditor.Animations.BlendTree))
                        {
                            Debug.LogError("You need to change it!");
                        }
                    }
                    tree.children = childMotionArray;
                }
            }
    
            for (int i = 0; i < stateMachine.stateMachines.Length; i++)
            {
                UpdateAnimator(newClips, stateMachine.stateMachines[i].stateMachine);
            }
        }
    }
    

    感谢浏览,有问题请留言。

    相关文章

      网友评论

        本文标题:Unity Animator Controller相关脚本集

        本文链接:https://www.haomeiwen.com/subject/yjztyxtx.html