using System.Collections; using System.Collections.Generic; using UnityEditor; using System.IO; using UnityEngine; using UnityEditor.Animations; using System.Linq; using System.Text; public class AnimationHelper { static string[] animations_path = { "", // @"Assets/LuaFramework/AssetBundleRes/scene/object/role", //@"Assets/LuaFramework/AssetBundleRes/scene/object/npc", //@"Assets/LuaFramework/AssetBundleRes/scene/object/monster", //@"Assets/LuaFramework/AssetBundleRes/scene/object/mount", //@"Assets/LuaFramework/AssetBundleRes/scene/object/wing", //@"Assets/LuaFramework/AssetBundleRes/scene/object/headwear", }; //特殊路径过滤 static string[] mask_path = { }; static private string ErrorString = ""; static private string LogString = ""; static private string SpeicalLogString = ""; static Dictionary> allDatas = new Dictionary>(); //所有正常的数据 static Dictionary> allSpecialDatas = new Dictionary>(); //所有异常数据目录,比如同一个目录下有多个action static Dictionary allTempPaths = new Dictionary(); //所有动画文件,路径缓存 static bool needScleProcess = true; public class ClipAnimData { public string _name; //fbx中动画的名字 public AnimationClip _clip; //生成的新的动画文件 public string _path; //原fbx文件路径 public string _clip_file_path; //chip文件路径 public ClipAnimData(string name, string path, AnimationClip clip, string clip_file_path) { _name = name; _clip = clip; _path = path; _clip_file_path = clip_file_path; } }; public class ReplaceClipAnimData { public string _stateName; //状态机的名字 public Motion _motion; //状态机的动画 public ClipAnimData _clip; //状态机的新动画数据 public ReplaceClipAnimData(string name, Motion motion, ClipAnimData clip) { _stateName = name; _motion = motion; _clip = clip; } }; //将目标路径下的fbx转为anim,并挂到action controller // [MenuItem("AnimationHelper/Format Animations By Config")] [MenuItem("AnimationHelper/步骤1. 格式化选中路径动作(剔除曲线)")] public static void FormatSelectPath() { needScleProcess = true; FormatAllAnimations(); } [MenuItem("AnimationHelper/步骤1. 格式化选中路径动作(保留曲线)")] public static void FormatSelectPathWithoutScale() { needScleProcess = false; FormatAllAnimations(); } public static void FormatAllAnimations() { if (!EditorUtility.DisplayDialog("提示", "首次格式化动作文件,需要持续十几分钟,是否继续?", "继续", "取消")) return; Object[] objs = Selection.objects; if(objs.Length > 0) { if(objs.Length > 1) { LogString = "只能操作一个文件夹"; EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定"); return; } animations_path[0] = AssetDatabase.GetAssetPath(objs[0]); // LogString = animations_path[0]; // EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定"); // return; } else{ LogString = "未选中需要压缩的文件夹,如:role等"; Debug.Log(LogString); EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定"); return; } //查找指定路径下指定类型的所有资源,返回的是资源GUID string[] guids = AssetDatabase.FindAssets("action", animations_path); //从GUID获得资源所在路径 List paths = new List(); guids.ToList().ForEach(m => paths.Add(AssetDatabase.GUIDToAssetPath(m))); ErrorString = "异常处理的文件日志\n"; LogString = "需要处理的文件日志\n"; SpeicalLogString = "特殊文件日志\n"; allDatas.Clear(); allSpecialDatas.Clear(); allTempPaths.Clear(); for (int i = 0; i < paths.Count; i++) { AnimatorController controller = AssetDatabase.LoadAssetAtPath(paths[i], typeof(AnimatorController)) as AnimatorController; if (controller != null) { List clipDatas = new List(); var path = paths[i]; var goName = controller.name + ".controller"; path = path.Replace(@"/" + goName, ""); //过滤的路径不处理 if (IsMakPath(path)) continue; string[] fbxGuids = AssetDatabase.FindAssets("t:GameObject", new string[] { path }); if(fbxGuids.Length > 0) { foreach (var guid in fbxGuids) { string file = AssetDatabase.GUIDToAssetPath(guid); CheckFBXAnimCompression(file); ClipAnimData clip = CreateNewAnimationClip(file); if (clip != null) { clipDatas.Add(clip); } if (File.Exists(file)) DeleteFile(file); } } if (clipDatas.Count > 0) { AppendLog("============> action path = " + path + " ================= > actionName = " + goName); if (allTempPaths.ContainsKey(path)) { AppendLog(" 同目录下存在2个动画控制器 [" + path + "]"); allSpecialDatas.Add(paths[i], clipDatas); var value = allTempPaths[path]; if (allDatas.ContainsKey(value)) { allSpecialDatas.Add(value, allDatas[value]); allDatas.Remove(value); } } else { allTempPaths.Add(path, paths[i]); allDatas.Add(paths[i], clipDatas); } } } } if (allDatas.Count > 0) { foreach (var item in allDatas) { RePlaceMotion(item.Key, item.Value); } //去掉所有anim文件不需要的数据 DeleteAllAnimUnuseData(allDatas); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log(LogString); Debug.Log(ErrorString); Debug.Log(SpeicalLogString); string show_tips = "FormatAllAnimations完成, 共生成替换了"+guids.Length+"个action"; EditorUtility.DisplayDialog("提示", show_tips, "确定"); } //将选中的fbx转为anim // [MenuItem("AnimationHelper/格式化选中路径动作")] // public static void FormatTargetAnimation() // { // LogString = "动作格式化日志:\n"; // Object[] objs = Selection.objects; // if(objs.Length > 0) // { // foreach (var item in objs) // { // string path = AssetDatabase.GetAssetPath(item); // AppendLog("path = " + path ); // // var clip = CreateNewAnimationClip(path); // // if(clip != null) // // AppendLog("path = " + path + ", actionName = " + clip._name); // } // } // else{ // LogString = "未选中需要压缩的文件夹,如:role等"; // } // AssetDatabase.SaveAssets(); // AssetDatabase.Refresh(); // Debug.Log(LogString); // EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定"); // } //对目标路径下的所有anim执行一遍精度优化 // [MenuItem("AnimationHelper/Process all Animations")] public static void ProcessAnimation() { //查找指定路径下指定类型的所有资源,返回的是资源GUID string[] guids = AssetDatabase.FindAssets("t:animationClip", animations_path); //从GUID获得资源所在路径 List paths = new List(); guids.ToList().ForEach(m => paths.Add(AssetDatabase.GUIDToAssetPath(m))); foreach (var p in paths) { AnimationClip tempClip = AssetDatabase.LoadAssetAtPath(p, typeof(AnimationClip)) as AnimationClip; if (tempClip != null) { OnProcessAnimationClip(tempClip, p); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); string show_tips = "Process Animation 完成"; EditorUtility.DisplayDialog("提示", show_tips, "确定"); } //重新关联AnimatorController中的动画 static bool RePlaceMotion(string path, List list) { bool result = true; AnimatorController animatorController = AssetDatabase.LoadAssetAtPath(path, typeof(AnimatorController)) as AnimatorController; if (animatorController != null && list != null && list.Count > 0) { List replaceData = new List(); //取得AnimatorController中的动画状态机 AnimatorStateMachine stateMachine = animatorController.layers[0].stateMachine; for (int i = 0; i < stateMachine.states.Length; i++) { if (stateMachine.states[i].state == null) { AppendErrorLog(" not find statue, path = [ " + path + "]"); result = false; continue; } if (stateMachine.states[i].state.motion == null) { AppendLog(" not find statue motion, state = [" + stateMachine.states[i].state.name + " ]" + " + path = [ " + path + "]"); //result = false; //continue; } string motionName = stateMachine.states[i].state.name; ClipAnimData t = null; //原动画状态机的名字和生成的动画片段名字相匹配,才会替换 for (int j = 0; j < list.Count; j++) { if (list[j]._name == motionName) { t = list[j]; break; } } if (t != null) { replaceData.Add(new ReplaceClipAnimData(stateMachine.states[i].state.name, stateMachine.states[i].state.motion, t)); } else { AppendErrorLog(" not find AnimationClip motionName = [" + motionName + "]"); } } if (replaceData.Count > 0) { foreach (var item in replaceData) { var stateName = item._stateName; var motion = item._motion; var clip = item._clip; for (int i = 0; i < stateMachine.states.Length; i++) { if (stateName == stateMachine.states[i].state.name) { stateMachine.states[i].state.motion = clip._clip; //替换之后,删除原来的FBX文件 DeleteFile(clip._path); break; } } } } } return result; } //根据原来的FBx文件生成新的动画片段 static ClipAnimData CreateNewAnimationClip(string path) { Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(Object)) as Object; AnimationClip oldClip = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip; if (obj != null && oldClip != null) { AnimationClip newClip = new AnimationClip(); EditorUtility.CopySerialized(oldClip, newClip); string newPath = path; newPath = newPath.Replace(obj.name + ".FBX", ""); string newName = obj.name; newName = newName.Replace(@"@", @"_new@"); OnProcessAnimationClip(newClip, path); //若生成的文件以及存在,删除掉 if (File.Exists(newPath + newName + ".anim")) { DeleteFile(newPath + newName + ".anim"); } //生成新的动画片段 AssetDatabase.CreateAsset(newClip, newPath + newName + ".anim"); return new ClipAnimData(oldClip.name, path, newClip, newPath + newName + ".anim"); } return null; } //动画片段精度压缩 static void OnProcessAnimationClip(AnimationClip clip, string path) { // bool needScleProcess = true; // if (path.Contains("object/role") && path.Contains("show")) // { // needScleProcess = false; // } // else if (path.Contains("object/pet")) // { // needScleProcess = false; // } // Debug.Log("path" + path + "; needScleProcess"+needScleProcess); if (needScleProcess) { foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(clip)) { string name = theCurveBinding.propertyName.ToLower(); if (name.Contains("scale")) { AnimationUtility.SetEditorCurve(clip, theCurveBinding, null); } } } //浮点数精度压缩到f3 AnimationClipCurveData[] curves = null; curves = AnimationUtility.GetAllCurves(clip); Keyframe key; Keyframe[] keyFrames; string formatType = "f3"; if (needScleProcess == false) { formatType = "f5"; } for (int ii = 0; ii < curves.Length; ++ii) { AnimationClipCurveData curveDate = curves[ii]; if (curveDate.curve == null || curveDate.curve.keys == null) { continue; } keyFrames = curveDate.curve.keys; for (int i = 0; i < keyFrames.Length; i++) { key = keyFrames[i]; key.value = float.Parse(key.value.ToString(formatType)); key.inTangent = float.Parse(key.inTangent.ToString(formatType)); key.outTangent = float.Parse(key.outTangent.ToString(formatType)); keyFrames[i] = key; } curveDate.curve.keys = keyFrames; clip.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve); } /* Keyframe[] keyFrames; Keyframe key; foreach (var binding in AnimationUtility.GetCurveBindings(clip)) { AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding); keyFrames = curve.keys; for (int i = 0; i < keyFrames.Length; i++) { 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; } curve.keys = keyFrames; clip.SetCurve(binding.path, binding.type, binding.propertyName, curve); } */ } //删除文件 public static void DeleteFile(string path) { AssetDatabase.DeleteAsset(path); } //拼接错误log public static void AppendErrorLog(string log) { ErrorString = ErrorString + log + "\n"; } //拼接log public static void AppendLog(string log, int logLevel = 0) { if (logLevel == 0) LogString = LogString + log + "\n"; else SpeicalLogString = SpeicalLogString + log + "\n"; } public static bool IsMakPath(string path) { if(mask_path.Length > 0) { foreach (var p in mask_path) { if (p == path) return true; } } return false; } //去除所选文件下中anim的不需要的参数 [MenuItem("AnimationHelper/步骤2. 去除anim的冗余参数")] public static void FormatAllAnimationChilp() { if (!EditorUtility.DisplayDialog("提示", "首次格式化动作文件,需要持续十几分钟,是否继续?", "继续", "取消")) return; Object[] objs = Selection.objects; if (objs.Length > 0) { if (objs.Length > 1) { LogString = "只能操作一个文件夹"; EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定"); return; } animations_path[0] = AssetDatabase.GetAssetPath(objs[0]); } else { LogString = "未选中需要压缩的文件夹,如:role等"; Debug.Log(LogString); EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定"); return; } //查找指定路径下指定类型的所有资源,返回的是资源GUID string[] guids = AssetDatabase.FindAssets("action", animations_path); //从GUID获得资源所在路径 List paths = new List(); guids.ToList().ForEach(m => paths.Add(AssetDatabase.GUIDToAssetPath(m))); ErrorString = "异常处理的文件日志\n"; LogString = "需要处理的文件日志\n"; SpeicalLogString = "特殊文件日志\n"; allDatas.Clear(); allSpecialDatas.Clear(); allTempPaths.Clear(); string[] chipGuids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { animations_path[0]}); if (chipGuids.Length > 0) { foreach (var guid in chipGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); ProressDeleteAnimUnuseData(path); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log(LogString); Debug.Log(ErrorString); Debug.Log(SpeicalLogString); string show_tips = "FormatAllAnimationChilp完成, 共生成替换了" + guids.Length + "个action"; EditorUtility.DisplayDialog("提示", show_tips, "确定"); } //去掉anim文件中的 intWeight 和 outWeight public static void DeleteAllAnimUnuseData(Dictionary> data) { if (data == null || data.Count == 0) return; foreach (var item in data) { List list = item.Value; if(list.Count > 0) { for(int i = 0; i< list.Count; i++) { ProressDeleteAnimUnuseData(list[i]._clip_file_path); } } RePlaceMotion(item.Key, item.Value); } } //去掉anim文件中的 intWeight 和 outWeight public static void ProressDeleteAnimUnuseData(string path) { string[] result = File.ReadAllLines(path, Encoding.UTF8); if (result.Length > 0) { List new_list = new List(); for (int k = 0; k < result.Length; k++) { string content = result[k]; if (!content.Contains("inWeight:") && !content.Contains("outWeight:") && !content.Contains("weightedMode:") ) { new_list.Add( content); } } File.WriteAllLines(path, new_list); } } //修改压缩格式 public static void CheckFBXAnimCompression(string path) { ModelImporter mi = AssetImporter.GetAtPath(path) as ModelImporter; mi.animationCompression = ModelImporterAnimationCompression.Optimal; AssetDatabase.SaveAssets(); } }