Unity3d读取FBX自动生成AnimatorController的方法

 由于自身的懒惰,一年多没写博客了。。十分愧疚,回想来还是写博客才能将知识更好的牢记并发现问题

前几天工作需要做一个自动生成动画组件的工具类,美术一般只会将做好的动画模型发给你,如果每个模型都手动自己新建Animator再绑定的话 是十分浪费时间的,所以必须得实现脚本自动化。

 根据近几天参考的几篇博客,自己写了一个工具类,下面进入正题

  1. 将模型的动作模型(一般是FBX文件)放进一个文件夹中,并规定命名。                                                                        (例如:model_1为待机动画,model_2为行走动画)Unity3d读取FBX自动生成AnimatorController的方法_第1张图片
  2. 创建一个脚本用作工具类起名为AnimatorTool,脚本中定义一个工具类函数
[MenuItem("Tools/自动生成动画控制器")]
public static void AutoAllCreateAnimatorControll()

  在该函数中,将要进行三步操作

  1.  从FBX文件中导出AnimationClip组件

  2.  压缩AnimationClip

  3.  生成AnimatorController组件

首先我们来实现第一步,新建一个函数名为ExportAllAnimationClips

 public static void ExportAllAnimationClips()
    {
       //检查是否存在animationClips存放的目录
        if (Directory.Exists(Application.dataPath + PathTools._AnimationClipsDir) == false) //此处的PathTools._AnimationClipsDir为你自己想存放AnimationClip的路径
        {
            Directory.CreateDirectory(Application.dataPath + PathTools._AnimationClipsDir);
        }
        DirectoryInfo directoryInfo = new DirectoryInfo(PathTools._FBXDir);//PathTools._FBXDir是之前存放了模型动画的文件夹
        FileInfo[] fileInfos = directoryInfo.GetFiles();
        foreach (FileInfo fileInfo in fileInfos)
        {
            if (fileInfo.Extension == ".fbx")
            {
                string fileName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf("."));//获取模型文件的名称

                string pathName = fileInfo.FullName.Substring(fileInfo.FullName.IndexOf("Assets\\"));//获取模型文件的Unity的路径

                AnimationClip animationClip = AssetDatabase.LoadAssetAtPath(pathName);//获取模型文件中的AnimationClip组件
                if (animationClip != null)
                {
                    //以下是验证文件命名的方法 可以根据自己项目来定
                    string[] names = fileName.Split('_');
                    if (names.Length != 2)
                    {
                        Debug.LogError(fileName + "文件命名有问题,请检查");
                        continue;
                    }
                    int param1, param2, param3 = 0;
                    if (names[0] != "Model" || int.TryParse(names[1], out param1) == false)
                    {
                        Debug.LogError(fileName + "文件命名有问题,请检查");
                        continue;
                    }
                  

                    //创建动画片段
                    AnimationClip clipTemp = new AnimationClip();
                    EditorUtility.CopySerialized(animationClip, clipTemp);

                    var targetDir = "Assets" + PathTools._AnimationClipsDir + "/";
                    AssetDatabase.CreateAsset(clipTemp, targetDir + fileName + ".anim"); //统一命名
                }
            }
        }
        AssetDatabase.Refresh();
    }

代码中的路径你可以自己定义一个枚举或者类来编写

public class PathTools
{
    //最后生成的AnimatorController目录
    public static string _AnimaControlDir =  @"Assets/FightingUnityChan_FreeAsset/FightingUnityChan_FreeAsset/Animator/";
    //FBX目录
    public static string _FBXDir = Application.dataPath + @"/FightingUnityChan_FreeAsset/FightingUnityChan_FreeAsset/Animations";
    //clips目录
    public static string _AnimationClipsDir = @"/FightingUnityChan_FreeAsset/FightingUnityChan_FreeAsset/NewAnimations";
}

这样第一部就完成了,如果直接执行代码的话 会发现你的项目中已经生成了包含animationClip的文件夹

Unity3d读取FBX自动生成AnimatorController的方法_第2张图片

第一步完成之后,我们的第二步是对AnimationClip文件进行压缩,这一步是否需要可以根据你的项目具体情况而定,如果动画文件过多导致内存紧缺的话不妨尝试,压缩的主要目的是2点

1.去除骨骼动画中的Scale信息(如果你用到了scale数据则不要进行这一步 否则会出问题)

2.减小骨骼点信息的float存储精度

如果想更详细了解的话可以浏览这篇文章,我这里就不多做讲解了,直接贴上代码

    public static void CompressAllAnimationClips()
    {
        //逻辑跟之前差不多
        DirectoryInfo directoryInfo = new DirectoryInfo(Application.dataPath + PathTools._AnimationClipsDir);
        FileInfo[] fileInfos = directoryInfo.GetFiles();
        foreach (FileInfo fileInfo in fileInfos)
        {
            if (fileInfo.Extension == ".anim")
            {
                //string fileName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf(".")); ;
                string pathName = fileInfo.FullName.Substring(fileInfo.FullName.IndexOf("Assets\\"));
                //获取之前导出的animationClip
                AnimationClip animationClip = AssetDatabase.LoadAssetAtPath(pathName); 
                if (animationClip != null)
                {
                    //复制一份进行压缩动画
                    var clip = UnityEngine.Object.Instantiate(animationClip) as AnimationClip;
                    CompressAnim(clip);

                    EditorUtility.CopySerialized(clip, animationClip);
                    EditorUtility.SetDirty(animationClip);
                }
            }
        }
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    //压缩单个animation 创建新的animation
    public static void CompressAnim(AnimationClip clip)
    {
        ReduceScaleKey(clip);
        ReduceFloatPrecision(clip);
    }

    //去掉骨骼动画scale
    private static void ReduceScaleKey(AnimationClip clip)
    {
        EditorCurveBinding[] curves = AnimationUtility.GetCurveBindings(clip);

        for (int j = 0; j < curves.Length; j++)
        {
            EditorCurveBinding curveBinding = curves[j];

            if (curveBinding.propertyName.ToLower().Contains("scale"))
                AnimationUtility.SetEditorCurve(clip, curveBinding, null);
        }

    }

    //浮点精度保留3位
    private static void ReduceFloatPrecision(AnimationClip clip)
    {
        EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
        AnimationClipCurveData[] curves = new AnimationClipCurveData[curveBindings.Length];
        for (int index = 0; index < curves.Length; ++index)
        {
            curves[index] = new AnimationClipCurveData(curveBindings[index]);
            curves[index].curve = AnimationUtility.GetEditorCurve(clip, curveBindings[index]);
        }
        foreach (AnimationClipCurveData curveDate in curves)
        {
            var keyFrames = curveDate.curve.keys;
            for (int i = 0; i < keyFrames.Length; i++)
            {
                var key = keyFrames[i];
                key.value = float.Parse(key.value.ToString("f3"));
                key.inTangent = float.Parse(key.inTangent.ToString("f3"));
                key.outTangent = float.Parse(key.outTangent.ToString("f3"));
                keyFrames[i] = key;
            }
            curveDate.curve.keys = keyFrames;
            clip.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);
        }
    }

压缩过后可以发现animationClip的大小减少了,最后我们可以进行AnimatorController的生成了

    //根据压缩过的clip,创建AnimatorController
    public static void CreateAnimatorControllerByClip()
    {
        //遍历压缩过的所有animtionClip
        DirectoryInfo directoryInfo = new DirectoryInfo(Application.dataPath + PathTools._AnimationClipsDir);
        FileInfo[] files = directoryInfo.GetFiles("*.anim", SearchOption.AllDirectories);
        string animatorName = "";
        List tempList = new List();//这里用到一个数据结构,里面包含一个string和一个int类型
        foreach (var file in files)
        {
            string fileName = file.Name.Substring(0, file.Name.IndexOf("."));
            string pathName = file.FullName.Substring(file.FullName.IndexOf("Assets\\"));
            string[] names = fileName.Split('_');
            if (names.Length != 2 || names[0] != "Model")
            {
                continue;
            }
            if (animatorName.Equals(""))
            {
                animatorName = names[0];
            }   
            int conditionValue = int.Parse(names[1]);
            AnimationNameAndCondition temp = new AnimationNameAndCondition(pathName, conditionValue); //将名字和序列存放进list中
            tempList.Add(temp);
        }
        if (tempList.Count == 0)
        {
            Debug.LogError("Animation获取失败");
            return;
        }
        CreateAnimatorController(PathTools._AnimaControlDir, animatorName, tempList);

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        Debug.Log("Animator已生成");
    }
    /// 
    /// 创建动画控制器
    /// 
    /// 
    /// 
    /// 
    private static AnimatorController CreateAnimatorController(string animationControlDir, string animatorName, List animationList)
    {
        //创建animator
        var animatorPath = animationControlDir + animatorName + ".controller";
        AnimatorController animatorController = AssetDatabase.LoadAssetAtPath(animatorPath);
        if (animatorController == null)
        {
            animatorController = AnimatorController.CreateAnimatorControllerAtPath(animationControlDir + animatorName + ".controller");
        }

        //设定一个名为Action的Int类型Parameter用作动画切换的标志
        bool isExist = false;
        var parameters = animatorController.parameters;
        foreach (var param in parameters)
        {
            if (param.name == "Action")
            {
                isExist = true;
            }
        }
        if (isExist == false)
        {
            animatorController.AddParameter("Action", AnimatorControllerParameterType.Int);
        }

        //获取base layer
        AnimatorControllerLayer animatorControllerLayer = animatorController.layers[0];
        AnimatorStateMachine animatorStateMachine = animatorControllerLayer.stateMachine;

        //按condition排序
        animationList.Sort((left, right) =>
        {
            if (left.Condition > right.Condition)
                return 1;
            else if (left.Condition == right.Condition)
                return 0;
            else
                return -1;
        });

        //开始添加状态
        bool enterState = false;
        foreach (var animationPath in animationList)
        {
            AnimationClip animationClip = AssetDatabase.LoadAssetAtPath(animationPath.Path);
            if (!animationClip)
                continue;

            //添加一个状态
            AnimatorState state = null;
            string stateName = animatorName + "_" + animationPath.Condition;
            var childState = animatorStateMachine.states;
            foreach (var animatorState in childState)//遍历是否之前存在状态
            {
                if (animatorState.state.name == stateName)
                {
                    state = animatorState.state;
                    break;
                }
            }
            //不存在状态,则添加一个
            if (state == null)
            {
                state = animatorStateMachine.AddState(stateName);
            }
            state.motion = animationClip;//加入状态机中

            //第一个状态默认连接enter
            if (enterState == false)
            {
                var enterStates = animatorStateMachine.entryTransitions;
                if (enterStates.Length == 0)
                {
                    animatorStateMachine.AddEntryTransition(state);
                }
                enterState = true;
            }
            //所有状态连接anystate
            AnimatorStateTransition stateTransition = null;
            var anyTransitions = animatorStateMachine.anyStateTransitions;
            foreach (var transitions in anyTransitions)//同上的遍历
            {
                if (transitions.destinationState.name == stateName)
                {
                    stateTransition = transitions;
                    break;
                }
            }
            if (stateTransition == null)
            {
                stateTransition = animatorStateMachine.AddAnyStateTransition(state);
                //将之前命名下划线后的序列作为动作切换的值
                stateTransition.AddCondition(AnimatorConditionMode.Equals, animationPath.Condition, "Action");
            }

            stateTransition.hasExitTime = false;
            stateTransition.canTransitionToSelf = false;
        }

        EditorUtility.SetDirty(animatorController);

        return animatorController;
    }

这段代码中用到的数据结构主要是存放condition的值,根据我们之前的命名,下划线后的数字就对应了动画切换的值

public class AnimationNameAndCondition
{
    public string Path;
    public int Condition;

    public AnimationNameAndCondition(string path, int condition)
    {
        Path = path;
        Condition = condition;
    }
}

最终我们运行脚本,就可以得到一个AnimatorController了

Unity3d读取FBX自动生成AnimatorController的方法_第3张图片

此时你就可以在运行时代码中获取到Animator组件并调用setinteger切换动画了!

总结

  该工具在项目实际运用中还有很多待优化的地方。比如这里只针对了一个文件夹的动画模型,事实项目中是有很多动画模型需要生成的,所以还需要再文件夹下面进行遍历,然后根据项目的需求进行命名。而且根据项目需求也可以不用SetInteger这种切换方法,使用SetTrigger或者SetBool再加入对应的命名枚举同样可以做到。

 这里只讲到了生成AnimatorController的过程,之后还需要将组建绑定到prefab上,这个过程也是需要自动化的,目前只想到这么多了,之后有新的改进或者想法会继续补充!如果有问题请指正,谢谢!

参考:http://www.xuanyusong.com/archives/2811

        https://www.cnblogs.com/yaoh/p/5149568.html

你可能感兴趣的:(Unity3d,C#,工具开发)