Unity中的动画自动生成工具介绍

前言

美术做一个模型,附带很多动画,然后就创建和管理动画状态机,然后类似的模型可能有很多,也就是我们所理解的皮肤,低级的是换贴图,高级一点的换模型,如果模型比较多的话,美术要创建和管理很多相同的动画,重复性的劳动,这会就需要有个动画生成器了。

思路

我们可以根据美术的要求通过代码创建一个AnimatorController,但一旦美术修改什么需求我们就要跟着修改会比较麻烦,比较简便的是美术先创建一个动画控制器模板,然后接下来重复性的劳动我们就通过程序工具来解决。思路是copy模板,修改模板里面动画状态的Motion,指向当前模型的动画clip,Motion有的是简单的动画clip,这个直接替换没啥好说的,有的是混合树BlendTree,这个替换会碰到小坑,如果我们获取到混合树的Motion然后遍历里面的Children,替换每个child的Motion是替换不了的,这个我也是查看了BlendTree的源码才知道,源码里面Children的管理(增加和删除)都是通过覆盖的方式替换Children数组,但我也有通过替换Children数组的方式来实现还是没有替换成功,最后还是通过代码创建BlendTree的方式来替换BlendTree才成功了。

效果

Unity动画生成工具_动画生成器

Unity动画生成工具_ide_02

Unity动画生成工具_ide_03

Unity动画生成工具_Unity动画生成器_04

Unity动画生成工具_ide_05

Unity动画生成工具_ide_06

代码

基于Odin插件的模式

登录后复制


using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using System;
using System.IO;
using System.Windows.Forms;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

public class AnimatorGeneratorWindow : OdinEditorWindow
{
    string fileDirectory;
    bool recursion = false;//是否是递归模式

    [UnityEditor.MenuItem("Tools/AnimatorGenerator")]
    private static void Open()
    {
        var window = GetWindow<AnimatorGeneratorWindow>();
        window.position = GUIHelper.GetEditorWindowRect().AlignCenter(450, 500);
    }
    [InlineEditor(InlineEditorModes.LargePreview)] //对材质和mesh有效,可以预览
    [LabelText("动画模板")]
    public AnimatorController AnimatorControllerTemplate;

    [Button("选择要生成的目录(递归遍历)", ButtonSizes.Medium), GUIColor(1, 1, 0)]
    [HorizontalGroup("ChooseMenu")]
    [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
    private void ChooseMenus()
    {
        Debug.Log("选择目录");
        FolderBrowserDialog fbd = new FolderBrowserDialog();
        fbd.RootFolder = Environment.SpecialFolder.MyComputer;
        if (fbd.ShowDialog() == DialogResult.OK)
        {
            fileDirectory = fbd.SelectedPath;
            recursion = true;
        }
        Debug.Log("选择目录:" + fileDirectory);
    }

    [Button("选择要生成的目录(单文件目录)", ButtonSizes.Medium), GUIColor(1, 1, 0)]
    [HorizontalGroup("ChooseMenu")]
    [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
    private void ChooseMenu()
    {
        Debug.Log("选择目录");
        FolderBrowserDialog fbd = new FolderBrowserDialog();
        fbd.RootFolder = Environment.SpecialFolder.MyComputer;
        if (fbd.ShowDialog() == DialogResult.OK)
        {
            fileDirectory = fbd.SelectedPath;
            recursion = false;
        }
        Debug.Log("选择目录:" + fileDirectory);
    }

    [Button("生成动画控制器", ButtonSizes.Medium), GUIColor(0, 1, 1)]
    [HorizontalGroup("Buttons")]
    [EnableIf("AnimatorControllerTemplate", InfoMessageType.Error)]
    private void GeneratorAnimatorButton()
    {
        Debug.Log("生成动画控制器");
        CreateAnimatorAssets();
    }


    private void CreateAnimatorAssets()
    {
        if (!Directory.Exists(fileDirectory))
        {
            throw new Exception("目录不存在或者路径不存在");
        }
        if (AnimatorControllerTemplate == null)
        {
            Debug.LogError("没有选择动画模板");
            return;
        }

        var animatorFilePath = AssetDatabase.GetAssetPath(AnimatorControllerTemplate);
        var dirArray = fileDirectory.Split('\\');
        var pathLastDirectoryName = dirArray[dirArray.Length - 1];
        var animatorExtension = Path.GetExtension(animatorFilePath);

        if (recursion)
        {
            var folders = Directory.GetDirectories(fileDirectory);
            foreach (var folder in folders)
            {
                SingleFolderDispose(folder, animatorFilePath);
            }
        }
        else
        {
            SingleFolderDispose(fileDirectory, animatorFilePath);
        }
    }


    private void SingleFolderDispose(string folder, string animatorFilePath)
    {
        DirectoryInfo info = new DirectoryInfo(folder);
        string folderName = info.Name;
        var newAnimatorFilePath = Path.Combine(folder, folderName + Path.GetExtension(animatorFilePath));
        File.Copy(animatorFilePath, newAnimatorFilePath, true);
        AssetDatabase.Refresh();
        AnalyzeAnimController(folder, newAnimatorFilePath);
        AssetDatabase.Refresh();
        var obj = LoadFbx(folder, newAnimatorFilePath);
        PrefabUtility.SaveAsPrefabAsset(obj, string.Format("{0}/{1}.prefab", folder, folderName));
        DestroyImmediate(obj);
    }

    private GameObject LoadFbx(string folder, string animatorFilePath)
    {
        //找到fbx,找到当前目录下名字不带@符号的fbx 进行实例化
        FileInfo tempFile = null;
        DirectoryInfo folderDirectoryInfo = new DirectoryInfo(folder);
        var files = folderDirectoryInfo.GetFiles();
        foreach (var fileInfo in files)
        {
            if (!fileInfo.Name.Contains("@") && fileInfo.Name.Contains(".fbx") && !fileInfo.Name.Contains(".meta"))   //TODO:也可以根据floder名去找对应的fbx
            {
                tempFile = fileInfo;
                break;
            }
        }
        if (tempFile == null)
        {
            throw new Exception(string.Format("目录:{0} 没有找到不带@的fbx", folder));
        }

        var obj = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(GetAssetPath(tempFile.FullName))) as GameObject;
        //找到controller

        var controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(GetAssetPath(animatorFilePath));
        obj.GetComponent<Animator>().runtimeAnimatorController = controller;
        return obj;
    }

    private void AnalyzeAnimController(string floder, string controllerPath)
    {
        var assetPath = GetAssetPath(controllerPath);
        var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
        string animationFolder = Path.GetDirectoryName(assetPath) + "\\animations";
        animationFolder.Replace("\\", "/");
        //animatorController的Parameters不需要修改
        //遍历所有的layer
        for (int i = 0; i < animatorController.layers.Length; i++)
        {
            var layer = animatorController.layers[i];
            AnimatorStateMachine sm = layer.stateMachine;
            RecursionAnalyzeAnimatorStateMachine(sm, animationFolder);
        }
    }

    private string GetAssetPath(string fullPath)
    {
        var strs = fullPath.Split(new string[] { "Assets" }, StringSplitOptions.None);
        var assetPath = "Assets" + strs[strs.Length - 1];
        assetPath.Replace("\\", "/");
        return assetPath;
    }

    private void RecursionAnalyzeAnimatorStateMachine(AnimatorStateMachine stateMachine, string animationFlolder)
    {
        //遍历states
        for (int i = 0; i < stateMachine.states.Length; i++)
        {
            var animatorState = stateMachine.states[i];
            var motion = animatorState.state.motion;
            if (motion != null)
            {
                if (motion is BlendTree)
                {
                    BlendTree bt = motion as BlendTree;

                    ChildMotion[] childMotions = new ChildMotion[bt.children.Length];


                    for (int j = 0; j < bt.children.Length; j++)
                    {
                        var childMotion = bt.children[j];
                        var motionClip = GetAnimationClip(childMotion.motion.name, animationFlolder);

                        if (motionClip == null)
                        {
                            Debug.LogError("没有找到" + motion.name + "的动画控制器");
                        }
                        else
                        {
                            Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, childMotion.motion));   //根据名字找到对应的prefab 然后找出里面的动画文件加载
                            //childMotion.motion = (Motion)motionClip;

                            //var newChildMotion = new ChildMotion() { motion = motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                            //childMotion = newChildMotion;

                            childMotions[j] = new ChildMotion() { motion = (Motion)motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                        }
                    }
                    //bt.children = childMotions;
                    BlendTree newBt = new BlendTree()
                    {
                        blendParameter = bt.blendParameter,
                        blendParameterY = bt.blendParameterY,
                        blendType = bt.blendType,
                        hideFlags = bt.hideFlags,
                        maxThreshold = bt.maxThreshold,
                        minThreshold = bt.minThreshold,
                        name = bt.name,
                        useAutomaticThresholds = bt.useAutomaticThresholds,
                        children = childMotions,
                    };
                    animatorState.state.motion = newBt;
                }
                else
                {
                    animatorState.state.motion = null;
                    var motionClip = GetAnimationClip(motion.name, animationFlolder);
                    if (motionClip == null)
                    {
                        Debug.LogError("没有找到" + motion.name + "的动画控制器");
                    }
                    else
                    {
                        animatorState.state.motion = (Motion)motionClip;
                        Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, motion));
                    }
                }
            }
        }
        //遍历substatemachine
        for (int j = 0; j < stateMachine.stateMachines.Length; j++)
        {
            var stateMachines = stateMachine.stateMachines[j];
            RecursionAnalyzeAnimatorStateMachine(stateMachines.stateMachine, animationFlolder);
        }
    }

    private AnimationClip GetAnimationClip(string motionName, string animationFolder)
    {
        var motionNameExt = motionName.Substring(motionName.IndexOf("_"));
        DirectoryInfo directoryInfo = new DirectoryInfo(animationFolder);
        FileInfo tempFileInfo = null;
        var files = directoryInfo.GetFiles("*.FBX", SearchOption.AllDirectories);
        for (int i = 0; i < files.Length; i++)
        {
            if (files[i].Name.EndsWith(motionNameExt + ".FBX"))    //有可能是Robert01_gun_jump_start  对应的Robert01@Robert01_gun_jump
            {
                tempFileInfo = files[i];
                break;
            }
        }
        if (tempFileInfo != null)
        {
            var datas = AssetDatabase.LoadAllAssetsAtPath(GetAssetPath(tempFileInfo.FullName));
            if (datas.Length == 0)
            {
                Debug.Log(string.Format("Can't find clip in {0}", tempFileInfo.FullName));
                return null;
            }
            foreach (var data in datas)
            {
                if (!(data is AnimationClip))//如果不是动画文件则跳过
                    continue;
                var newClip = data as AnimationClip;
                return newClip;
            }
        }
        else
        {
            Debug.LogError("没有找到对应的动画FBX:" + motionName);
        }
        return null;
    }
}

基于OnGUI的原生模式

OnGUI的模式是采用的Selection方式选择模板和目录。

登录后复制


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Windows.Forms;
using System;
using System.IO;
using UnityEditor.Animations;

public class AnimatorEditor : EditorWindow
{
    string fileDirectory = string.Empty;
    string assetDirectory = string.Empty;
    bool recursion = false; //是否是递归模式
    AnimatorController animatorControllerTemplate = null;

    [UnityEditor.MenuItem("Tools/AnimatorEditor")]
    private static void Open()
    {
        var window = EditorWindow.GetWindow(typeof(AnimatorEditor), true, "动画生成器", true);
        window.Show();
    }

    void OnSelectionChange()
    {
        if (Selection.activeObject != null)
        {
            Debug.Log("选择物体:" + Selection.activeObject);
            if (Selection.activeObject is AnimatorController)
            {
                animatorControllerTemplate = Selection.activeObject as AnimatorController;
                Debug.Log("选择的物体是:" + animatorControllerTemplate.name);
            }
            if (Selection.activeObject is DefaultAsset) //选择目录
            {
                var asset = Selection.activeObject as DefaultAsset;
                string[] strs = Selection.assetGUIDs;
                string path = AssetDatabase.GUIDToAssetPath(strs[0]);
                assetDirectory = path;
                fileDirectory = Path.Combine(Environment.CurrentDirectory, path);
                Debug.Log("选择的路径:" + path);
            }
        }
    }

    void OnGUI()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("选择Controller模板:");
        GUILayout.Label(animatorControllerTemplate == null ? "" : animatorControllerTemplate.name);
        GUILayout.EndHorizontal();
        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("选择生成的目录:");
        GUILayout.Label(assetDirectory);
        GUILayout.EndHorizontal();
        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("是否批量生成:");
        recursion = EditorGUILayout.Toggle(recursion);
        GUILayout.EndHorizontal();
        GUILayout.Space(30);
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("选择要生成的目录(递归遍历)"))
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.RootFolder = Environment.SpecialFolder.MyComputer;
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                fileDirectory = fbd.SelectedPath;
                recursion = true;
            }
            Debug.Log("选择目录:" + fileDirectory);
        }
        if (GUILayout.Button("选择要生成的目录(单文件目录)"))
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.RootFolder = Environment.SpecialFolder.MyComputer;
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                fileDirectory = fbd.SelectedPath;
                recursion = false;
            }
            Debug.Log("选择目录:" + fileDirectory);
        }
        GUILayout.EndHorizontal();
        GUILayout.Space(30);
        if (GUILayout.Button("生成AnimatorController"))
        {
            Debug.Log("生成动画控制器");
            CreateAnimatorAssets();
        }
    }


    private void CreateAnimatorAssets()
    {
        if (!Directory.Exists(fileDirectory))
        {
            throw new Exception("目录不存在或者路径不存在");
        }
        if (animatorControllerTemplate == null)
        {
            Debug.LogError("没有选择动画模板");
            return;
        }

        var animatorFilePath = AssetDatabase.GetAssetPath(animatorControllerTemplate);
        var dirArray = fileDirectory.Split('\\');
        var pathLastDirectoryName = dirArray[dirArray.Length - 1];
        var animatorExtension = Path.GetExtension(animatorFilePath);

        if (recursion)
        {
            var folders = Directory.GetDirectories(fileDirectory);
            foreach (var folder in folders)
            {
                SingleFolderDispose(folder, animatorFilePath);
            }
        }
        else
        {
            SingleFolderDispose(fileDirectory, animatorFilePath);
        }
    }


    private void SingleFolderDispose(string folder, string animatorFilePath)
    {
        DirectoryInfo info = new DirectoryInfo(folder);
        string folderName = info.Name;
        var newAnimatorFilePath = Path.Combine(folder, folderName + Path.GetExtension(animatorFilePath));
        File.Copy(animatorFilePath, newAnimatorFilePath, true);
        AssetDatabase.Refresh();
        AnalyzeAnimController(folder, newAnimatorFilePath);
        AssetDatabase.Refresh();
        var obj = LoadFbx(folder, newAnimatorFilePath);
        PrefabUtility.SaveAsPrefabAsset(obj, string.Format("{0}/{1}.prefab", folder, folderName));
        DestroyImmediate(obj);
    }

    private GameObject LoadFbx(string folder, string animatorFilePath)
    {
        //找到fbx,找到当前目录下名字不带@符号的fbx 进行实例化
        FileInfo tempFile = null;
        DirectoryInfo folderDirectoryInfo = new DirectoryInfo(folder);
        var files = folderDirectoryInfo.GetFiles();
        foreach (var fileInfo in files)
        {
            if (!fileInfo.Name.Contains("@") && fileInfo.Name.Contains(".fbx") && !fileInfo.Name.Contains(".meta"))   //TODO:也可以根据floder名去找对应的fbx
            {
                tempFile = fileInfo;
                break;
            }
        }
        if (tempFile == null)
        {
            throw new Exception(string.Format("目录:{0} 没有找到不带@的fbx", folder));
        }

        var obj = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(GetAssetPath(tempFile.FullName))) as GameObject;
        //找到controller

        var controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(GetAssetPath(animatorFilePath));
        obj.GetComponent<Animator>().runtimeAnimatorController = controller;
        return obj;
    }

    private void AnalyzeAnimController(string floder, string controllerPath)
    {
        var assetPath = GetAssetPath(controllerPath);
        var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
        string animationFolder = Path.GetDirectoryName(assetPath) + "\\animations";
        animationFolder.Replace("\\", "/");
        //animatorController的Parameters不需要修改
        //遍历所有的layer
        for (int i = 0; i < animatorController.layers.Length; i++)
        {
            var layer = animatorController.layers[i];
            AnimatorStateMachine sm = layer.stateMachine;
            RecursionAnalyzeAnimatorStateMachine(sm, animationFolder);
        }
    }

    private string GetAssetPath(string fullPath)
    {
        var strs = fullPath.Split(new string[] { "Assets" }, StringSplitOptions.None);
        var assetPath = "Assets" + strs[strs.Length - 1];
        assetPath.Replace("\\", "/");
        return assetPath;
    }

    private void RecursionAnalyzeAnimatorStateMachine(AnimatorStateMachine stateMachine, string animationFlolder)
    {
        //遍历states
        for (int i = 0; i < stateMachine.states.Length; i++)
        {
            var animatorState = stateMachine.states[i];
            var motion = animatorState.state.motion;
            if (motion != null)
            {
                if (motion is BlendTree)
                {
                    BlendTree bt = motion as BlendTree;

                    ChildMotion[] childMotions = new ChildMotion[bt.children.Length];


                    for (int j = 0; j < bt.children.Length; j++)
                    {
                        var childMotion = bt.children[j];
                        var motionClip = GetAnimationClip(childMotion.motion.name, animationFlolder);

                        if (motionClip == null)
                        {
                            Debug.LogError("没有找到" + motion.name + "的动画控制器");
                        }
                        else
                        {
                            Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, childMotion.motion));   //根据名字找到对应的prefab 然后找出里面的动画文件加载
                            //childMotion.motion = (Motion)motionClip;

                            //var newChildMotion = new ChildMotion() { motion = motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                            //childMotion = newChildMotion;

                            childMotions[j] = new ChildMotion() { motion = (Motion)motionClip, cycleOffset = childMotion.cycleOffset, mirror = childMotion.mirror, directBlendParameter = childMotion.directBlendParameter, position = childMotion.position, threshold = childMotion.threshold, timeScale = childMotion.timeScale };
                        }
                    }
                    //bt.children = childMotions;
                    BlendTree newBt = new BlendTree()
                    {
                        blendParameter = bt.blendParameter,
                        blendParameterY = bt.blendParameterY,
                        blendType = bt.blendType,
                        hideFlags = bt.hideFlags,
                        maxThreshold = bt.maxThreshold,
                        minThreshold = bt.minThreshold,
                        name = bt.name,
                        useAutomaticThresholds = bt.useAutomaticThresholds,
                        children = childMotions,
                    };
                    animatorState.state.motion = newBt;
                }
                else
                {
                    animatorState.state.motion = null;
                    var motionClip = GetAnimationClip(motion.name, animationFlolder);
                    if (motionClip == null)
                    {
                        Debug.LogError("没有找到" + motion.name + "的动画控制器");
                    }
                    else
                    {
                        animatorState.state.motion = (Motion)motionClip;
                        Debug.Log(string.Format("Name:{0}  Motion:{1}", animatorState.state.name, motion));
                    }
                }
            }
        }
        //遍历substatemachine
        for (int j = 0; j < stateMachine.stateMachines.Length; j++)
        {
            var stateMachines = stateMachine.stateMachines[j];
            RecursionAnalyzeAnimatorStateMachine(stateMachines.stateMachine, animationFlolder);
        }
    }

    private AnimationClip GetAnimationClip(string motionName, string animationFolder)
    {
        var motionNameExt = motionName.Substring(motionName.IndexOf("_"));
        DirectoryInfo directoryInfo = new DirectoryInfo(animationFolder);
        FileInfo tempFileInfo = null;
        var files = directoryInfo.GetFiles("*.FBX", SearchOption.AllDirectories);
        for (int i = 0; i < files.Length; i++)
        {
            if (files[i].Name.EndsWith(motionNameExt + ".FBX"))    //有可能是Robert01_gun_jump_start  对应的Robert01@Robert01_gun_jump
            {
                tempFileInfo = files[i];
                break;
            }
        }
        if (tempFileInfo != null)
        {
            var datas = AssetDatabase.LoadAllAssetsAtPath(GetAssetPath(tempFileInfo.FullName));
            if (datas.Length == 0)
            {
                Debug.Log(string.Format("Can't find clip in {0}", tempFileInfo.FullName));
                return null;
            }
            foreach (var data in datas)
            {
                if (!(data is AnimationClip))//如果不是动画文件则跳过
                    continue;
                var newClip = data as AnimationClip;
                return newClip;
            }
        }
        else
        {
            Debug.LogError("没有找到对应的动画FBX:" + motionName);
        }
        return null;
    }
}

打开弹框

打开弹框可以用Unity内置的System.Windows.Forms.dll的api来打开,将其放在Plugins下,打开方法:

登录后复制


public void OpenFile()
{
    OpenFileDialog dialog = new OpenFileDialog();
    dialog.Filter = "exe files (*.exe)|*.exe";  //过滤文件类型
    dialog.InitialDirectory = "D:\\";  //定义打开的默认文件夹位置,可以在显示对话框之前设置好各种属性
    if (dialog.ShowDialog() == DialogResult.OK)
    {
        Debug.Log(dialog.FileName);
    }
}

纯代码创建控制器

登录后复制


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

public class AnimatorTool : MonoBehaviour
{
    private static List<AnimatorState> stateList = new List<AnimatorState>();
    /// <summary>
    /// 菜单方法,遍历文件夹创建Animation Controller
    /// </summary>
    [MenuItem("Tools/CreateAnimator")]
    static void CreateAnimationAssets()
    {
        string rootFolder = "Assets/Resources/Fbx/";
        if (!Directory.Exists(rootFolder))
        {
            Directory.CreateDirectory(rootFolder);
            return;
        }
        // 遍历目录,查找生成controller文件
        var folders = Directory.GetDirectories(rootFolder);
        foreach (var folder in folders)
        {
            DirectoryInfo info = new DirectoryInfo(folder);
            string folderName = info.Name;
            // 创建animationController文件
            AnimatorController aController =
                AnimatorController.CreateAnimatorControllerAtPath(string.Format("{0}/animation.controller", folder));  //在对应目录生成AnimatorController文件

            //添加参数
            aController.AddParameter("run", AnimatorControllerParameterType.Bool);
            aController.AddParameter("attack01", AnimatorControllerParameterType.Bool);

            // 得到其layer
            var layer = aController.layers[0];//Base Layer
            // 绑定动画文件
            AddStateTranstion(string.Format("{0}/{1}_model.fbx", folder, folderName), layer);
            Debug.Log(string.Format("<color=yellow>{0}</color>", layer));
            // 创建预设
            GameObject go = LoadFbx(folderName);
            PrefabUtility.CreatePrefab(string.Format("{0}/{1}.prefab", folder, folderName), go);
            DestroyImmediate(go);
        }

    }

    /// <summary>
    /// 添加动画状态机状态
    /// </summary>
    /// <param name="path"></param>
    /// <param name="layer"></param>
    private static void AddStateTranstion(string path, AnimatorControllerLayer layer)
    {
        AnimatorStateMachine sm = layer.stateMachine;  //状态机
        // 根据动画文件读取它的AnimationClip对象
        var datas = AssetDatabase.LoadAllAssetsAtPath(path);
        if (datas.Length == 0)
        {
            Debug.Log(string.Format("Can't find clip in {0}", path));
            return;
        }
        /*
        //创建默认state  
        AnimatorState defaultState = sm.AddState("default", new Vector3(300, 0, 0));
        //defaultState.motion=  
        sm.defaultState = defaultState;
        AnimatorStateTransition defaultTransition = sm.AddAnyStateTransition(defaultState);
        defaultTransition.AddCondition(AnimatorConditionMode.If, 0, "default");
        */
        // 先添加一个默认的空状态
        var emptyState = sm.AddState("empty", new Vector3(500, 0, 0));
        sm.AddAnyStateTransition(emptyState);

        //遍历模型中包含的动画片段,将其加入状态机中
        foreach (var data in datas)
        {
            int index = 0;
            if (!(data is AnimationClip)) //如果不是动画文件则跳过
                continue;
            var newClip = data as AnimationClip; //如果是的话则转化

            if (newClip.name.StartsWith("__"))
                continue;
            // 取出动画名字,添加到state里面
            AnimatorState state = sm.AddState(newClip.name, new Vector3(500, sm.states.Length * 60, 0)); //将动画添加到动画控制器
            stateList.Add(state);
            if (state.name == "walk")
            {
                sm.defaultState = state;   //将walk设置为默认动画
            }
            Debug.Log(string.Format("<color=red>{0}</color>", state));
            index++;
            state.motion = newClip; //设置动画状态指定到自己的动画文件
            // 把State添加在Layer里面
            sm.AddAnyStateTransition(state); //将动画状态连线到AnyState
        }

        AddTransition(sm, "walk", "run", 1);
        AddTransition(sm, "run", "walk", 0);

        AddTransition(sm, "walk", "attack01", 1);
        AddTransition(sm, "attack01", "walk", 0);

        AddSuMechie(sm, 2, path, layer, "sub2Machine");
    }

    static void AddSuMechie(AnimatorStateMachine machine, int index1, string path, AnimatorControllerLayer layer, string sunStateMachine)
    {
        创建子状态机  
        //for (int k = 1; k < index1; k++)
        //{
        //    AnimatorStateMachine sub2Machine = machine.AddStateMachine("sub2Machine", new Vector3(100, 300, 0));
        //}
        AnimatorStateMachine sub2Machine = machine.AddStateMachine(sunStateMachine, new Vector3(100, 300, 0));

        // 根据动画文件读取它的AnimationClip对象
        var datas = AssetDatabase.LoadAllAssetsAtPath(path);
        if (datas.Length == 0)
        {
            Debug.Log(string.Format("Can't find clip in {0}", path));
            return;
        }
        foreach (var data in datas)
        {
            int index = 0;
            if (!(data is AnimationClip))
                continue;
            var newClip = data as AnimationClip;

            if (newClip.name.StartsWith("__"))
                continue;
            // 取出动画名字,添加到state里面
            AnimatorState state = sub2Machine.AddState(newClip.name, new Vector3(500, sub2Machine.states.Length * 60, 0));
            stateList.Add(state);
            if (state.name == "walk")
            {
                sub2Machine.defaultState = state;
            }
            Debug.Log(string.Format("<color=red>{0}</color>", state));
            index++;
            state.motion = newClip;
            // 把State添加在Layer里面
            sub2Machine.AddAnyStateTransition(state);
        }
    }

    /// <summary>
    /// 添加状态之间的连线
    /// </summary>
    /// <param name="stateM">状态</param>
    /// <param name="ani_name"></param>
    /// <param name="ani_des"></param>
    /// <param name="flag"></param>
    static void AddTransition(AnimatorStateMachine stateM, string ani_name, string ani_des, int flag)
    {
        foreach (var item in stateM.states)
        {
            if (item.state.name == ani_name)
            {
                foreach (var des in stateM.states)
                {
                    if (des.state.name == ani_des)
                    {
                        AnimatorStateTransition transition = item.state.AddTransition(des.state); //添加连线
                        transition.hasExitTime = true;
                        transition.exitTime = 0.8f;
                        if (flag == 1)
                            transition.AddCondition(AnimatorConditionMode.If, flag, ani_des); //添加连线状态
                        else
                        {
                            transition.AddCondition(AnimatorConditionMode.IfNot, flag, ani_name);
                        }
                    }
                }
            }
        }
        Resources.UnloadUnusedAssets(); //卸载资源
    }


    /// <summary>
    /// 生成带动画控制器的对象
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public static GameObject LoadFbx(string name)
    {
        var obj = Instantiate(Resources.Load(string.Format("Fbx/{0}/{0}_model", name))) as GameObject;
        obj.GetComponent<Animator>().runtimeAnimatorController =
            Resources.Load<RuntimeAnimatorController>(string.Format("fbx/{0}/animation", name));
        return obj;
    }
}

碰到的坑

据反馈,新生成的动画控制器在关闭Unity之后,重新打开会发现新生成的Animaor出问题了,motion丢失,但测试下来直接将Clip赋给Motin没有问题,子状态机这种情况也没有问题,唯独BlendTree有问题,我对比新生成的Animator文件跟模板Animator文件对比发现FileID=0,也就是说BlendTree文件并没有保存下来,但BlendTree又不像动画Clip那样我们能直接看到,经过查看Animator文件的数据会发现BlendTree信息写在Animator中,也就是BT并没有保存下来,那么如何保存代码修改的Animation的BlendTree呢?我看到Unity论坛有人碰到类似的问题, https://forum.unity.com/threads/how-to-save-the-animation-blend-tree-created-by-script.480320/,感谢题主!解决方法就是:

Unity动画生成工具_System_07


经过这段代码,会将BlendTree的信息写入到Animator中,

Unity动画生成工具_UnityEditor_08


这样问题就解决了!

工程下载

https://github.com/dingxiaowei/AnimatorGenerator




免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空