前几天接到一个任务,做一个导入、控制模型动画的工具类,没有太具体的要求,于是就自行思考实际需求,最终根据宣雨松老师的一篇博客,自己规范了一下写了一个工具类。相关工具代码及测试用例已上传至Github。
https://github.com/hcy12321/UnityAnimatorControllerMaker
该demo需在导入Unity后执行菜单Tools/CreateAnimator项后再执行。
1.需求及规范
需求是指实际使用时需要实现的地方,规范是最终使用这套工具需要遵守的规则。
1.1 实际需求
1. 能自动遍历fbx文件,且生成对应的AnimatorController文件。
2. 能获取fbx文件中所有的动画片段(AnimationClip),并存入第一步生成的AnimatorController的状态机中。
3.(个人假设需求)状态机默认指向一个空的动画。
1.2 使用规范
1. 所有fbx文件需放置在Assets/Resources/fbx目录(该目录可在代码中更改)中的子目录中,该子目录以fbx名称(不可包含中文)命名,将fbx文件和贴图放到该子目录中,然后将fbx文件重命名为原名_model。
如:wukong.fbx。因和其贴图一起放置在 Assets\Resources\fbx\wukong目录中,然后改名为wukong_model.fbx。
2.功能实现
该部分主要介绍逻辑功能代码
2.1 生成菜单方法
在Editor目录下添加类文件AnimatorTool.cs,该类共有三个方法:
void CreateAnimationAssets(): 工具菜单方法,内有遍历目录生成动画控制器、生成预设的逻辑
using System; using UnityEngine; using System.Collections; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.Animations; public class AnimatorTool : MonoBehaviour { /// <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)); // 得到其layer var layer = aController.layers[0]; // 绑定动画文件 AddStateTranstion(string.Format("{0}/{1}_model.fbx", folder, folderName), 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; } // 先添加一个默认的空状态 var emptyState = sm.AddState("empty"); sm.AddAnyStateTransition(emptyState); // 遍历模型中包含的动画片段,将其加入状态机中 foreach (var data in datas) { if (!(data is AnimationClip)) continue; var newClip = data as AnimationClip; if (newClip.name.StartsWith("__")) continue; // 取出动画名字,添加到state里面 var state = sm.AddState(newClip.name); state.motion = newClip; // 把State添加在Layer里面 sm.AddAnyStateTransition(state); } } /// <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; } }
2.2 测试用例
测试用例中主要包含如何调用播放动画、暂停动画、重播动画等功能。
using UnityEngine; using System.Collections; public class AnimatorTest : MonoBehaviour { private Animator animator; public string animationName = "run"; public float Speed = 1.0f; // Use this for initialization void Start() { animator = GetComponent<Animator>(); } /// <summary> /// 添加一些测试功能按钮 /// </summary> void OnGUI() { #if UNITY_EDITOR if (GUILayout.Button("Play")) { Play(animationName); } if (GUILayout.Button("Replay")) { RePlay(animationName); } if (GUILayout.Button("Pause")) { Pause(); } #endif } /// <summary> /// 设置速度 /// </summary> /// <param name="speed"></param> public void SetSpeed(float speed) { Speed = speed; } /// <summary> /// 重新播放指定名称动画 /// </summary> /// <param name="name"></param> public void RePlay(string name) { animator.speed = Speed; animator.Play(name, 0, 0.0f); } /// <summary> /// 播放指定名称动画 /// </summary> /// <param name="name"></param> public void Play(string name) { animator.speed = Speed; animator.Play(name); } /// <summary> /// 暂停动画 /// </summary> public void Pause() { animator.speed = 0.0f; } }
3.总结
该工具在项目实际使用中还有许多待优化的地方,如只考虑了一个模型,如果是一个人物有多个模型那么还要根据需求再重新设计。还有状态机中还没有根据模型直接生成结构动画、序列动画的功能,需要在以后继续改进。
本工具参考了宣雨松老师的一篇博客:http://www.xuanyusong.com/archives/2811
如有问题请指正,谢谢!