Unity Animator Controller相关脚本集

新项目中使用了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 = "";

                    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(" *(?\\w+) *(?[0-9]+) *(?[0-9]+) *(?(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 clipList = new List();
                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(animAssetPath);
                        if (clip != null)
                        {
                            clipList.Add(clip);
                        }
                    }
                }
                AnimatorController animController = AssetDatabase.LoadAssetAtPath(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 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相关脚本集)