天天看點

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 = "<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);
        }
    }
}
           

感謝浏覽,有問題請留言。