using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using BF; using UnityEditor; using UnityEditor.Animations; using UnityEngine; namespace BFEditor.Resource { public class AnimationTools { private const string ANI_DIR_PATH = "Assets/arts/models/characters"; private const string FBX_DIR_PATH = "Assets/arts/models/characters/"; private const float POS_ERROR = 0.1f; private const float ROT_ERROR = 0.1f; private const float SAL_ERROR = 0.1f; private const float OFFSET_SCALE = 0.1f; private static float Min_Scale = 1 - OFFSET_SCALE; private static float Max_Scale = 1 + OFFSET_SCALE; static public void SeparateAndOptimizeAnimationAll(bool onlyCompress) { var fileInfos = Directory.GetFiles(FBX_DIR_PATH, "*.fbx", SearchOption.AllDirectories) .Union(Directory.GetFiles(FBX_DIR_PATH, "*.FBX", SearchOption.AllDirectories)); var progress = .0f; var total = fileInfos.Count(); foreach (var fileInfo in fileInfos) { var fullPath = Path.GetFullPath(fileInfo); var assetPath = Utils.GetAssetPathByFullPath(fullPath); var gameObject = AssetDatabase.LoadAssetAtPath(assetPath); EditorUtility.DisplayProgressBar("优化动画资源", string.Format("正在优化{0}", Path.GetFileName(fileInfo)), ++progress / total); SeparateAndOptimizeAnimationFromFBX(gameObject, onlyCompress); } EditorUtility.ClearProgressBar(); } static public void SeparateAndOptimizeAnimationFromSelectFBX(bool onlyCompress) { SeparateAndOptimizeAnimationFromFBX(Selection.activeGameObject, onlyCompress); } static public void OptimizeAnimationAll(bool onlyCompress = true) { Debug.Log("[bfinfo]优化动画资源..."); try { var fileInfos = Directory.GetFiles(ANI_DIR_PATH, "*.anim", SearchOption.AllDirectories); var progress = .0f; var total = fileInfos.Count(); foreach (var fileInfo in fileInfos) { EditorUtility.DisplayProgressBar("优化动画资源", string.Format("正在优化{0}", Path.GetFileName(fileInfo)), ++progress / total); OptimizeClip(fileInfo, onlyCompress); } EditorUtility.ClearProgressBar(); AssetDatabase.Refresh(); } catch (Exception e) { } } static public void FixErroAnims() { var tmpDics = new Dictionary(); string[] errorSubAnimDirs = { "1013_connie", "1017_zoey", "1021_ken", "1025_rezo", "1029_daire", "1076_bella", "1081_carol", "1086_dora", "1091_marcia", "1097_sylvia", "1103_hina", "1109_august", "1115_alex", "1121_mallory", "1127_farina", "1133_margaret", "1187_lee", "1193_chan", "1199_grindan", "1175_saruman", "1181_risell", "1205_malcom", "1283_orris", "1290_heras", "1297_muspel", "1339_olaf", "1346_hercules", "1353_genji", "1360_musashi", "1367_michael", "1374_sariel", "11001_dryad", "11002_dryad", "12005_boarcacique", "23007_cyclops", "32006_flowerelf", "33007_giantbeast", "41004_crocodilearcher", "51002_ogre", "51003_ogre", "53007_ogreking", "61003_fishman", "61004_fishman", "62005_fishmanleader", "63007_sirenqueen", "73007_pitfiend", "83007_crystallord", "103007_castlegolem" }; var animDir = ANI_DIR_PATH + "/characters/"; var modelDir = FBX_DIR_PATH; var count = errorSubAnimDirs.Length; for (int i = 0; i < count; i++) { var subDir = errorSubAnimDirs[i]; var originDir = animDir + subDir; var targetDir = modelDir + subDir; var animInfo = new DirectoryInfo(originDir); var modelInfo = new DirectoryInfo(targetDir); var allFiles = animInfo.GetFiles("*", SearchOption.AllDirectories); foreach (var file in allFiles) { if (file.Name.Contains(".meta")) continue; var originPath = originDir + "/" + file.Name; if (file.Name.Contains(".controller")) { var targetPath = targetDir + "/" + file.Name; tmpDics.TryAdd(file.Name.Replace(".controller", ""), targetPath); AssetDatabase.MoveAsset(originPath, targetPath); } else { //AssetDatabase.DeleteAsset(originPath); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); allFiles = modelInfo.GetFiles("*", SearchOption.AllDirectories); foreach (var file in allFiles) { if (file.Name.Contains(".meta")) continue; if (file.Name.ToLower().Contains(".fbx")) { var originPath = targetDir + "/" + file.Name; var modelImporter = AssetImporter.GetAtPath(originPath) as ModelImporter; if (modelImporter != null && !modelImporter.importAnimation) { modelImporter.importAnimation = true; modelImporter.SaveAndReimport(); string contrlPath; if (tmpDics.TryGetValue(file.Name.ToLower().Replace(".fbx", ""), out contrlPath)) { var animatorContrl = AssetDatabase.LoadAssetAtPath(contrlPath); var animationClips = GetFBXAnimationClips(originPath); foreach (var clip in animationClips) { var clipName = clip.name; var sm = (animatorContrl != null ? animatorContrl.layers[0].stateMachine : null); if (sm) { int len = (sm != null ? sm.states.Length : 0); for (int j = 0; j < len; j++) { Motion mt = sm.states[j].state.motion; if (mt != null) { if (clipName == mt.name) { sm.states[j].state.motion = clip; break; } } } } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } } } } allFiles = animInfo.GetFiles("*", SearchOption.AllDirectories); foreach (var file in allFiles) { var originPath = originDir + "/" + file.Name; if (!file.Name.Contains(".controller")) { AssetDatabase.DeleteAsset(originPath); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); allFiles = modelInfo.GetFiles("*", SearchOption.AllDirectories); foreach (var file in allFiles) { if (file.Name.Contains(".meta")) continue; if (file.Name.ToLower().Contains(".fbx")) { var originPath = targetDir + "/" + file.Name; PreProcessModel(originPath); Optimize(originPath, true); PostProcessModel(originPath); } } } } static public void SeparateAndOptimizeAnimationFromFBX(GameObject gameObject, bool onlyCompress) { if (gameObject) { var fbxPath = AssetDatabase.GetAssetPath(gameObject); PreProcessModel(fbxPath); Optimize(fbxPath, onlyCompress); PostProcessModel(fbxPath); } } //移除scale static private void RemoveAnimationCurve(AnimationClip animClip, bool onlyCompress = false) { foreach (EditorCurveBinding curveBinding in AnimationUtility.GetCurveBindings(animClip)) { string name = curveBinding.propertyName.ToLower(); if (name.Contains("scale")) { var removeCurve = false; var curve = AnimationUtility.GetEditorCurve(animClip, curveBinding); var frameCount = curve.keys.Length; if (frameCount > 0) { foreach (var key in curve.keys) { if (key.value < Min_Scale || key.value > Max_Scale) { removeCurve = true; break; } } } if (removeCurve && !onlyCompress) AnimationUtility.SetEditorCurve(animClip, curveBinding, null); } } CompressAnimationClip(animClip); } //压缩精度 static private void CompressAnimationClip(AnimationClip clip) { AnimationClipCurveData[] curveArr = AnimationUtility.GetAllCurves(clip); Keyframe key; Keyframe[] keyFrameArr; for (int i = 0; i < curveArr.Length; ++i) { AnimationClipCurveData curveData = curveArr[i]; if (curveData.curve == null || curveData.curve.keys == null) { continue; } keyFrameArr = curveData.curve.keys; for (int j = 0; j < keyFrameArr.Length; j++) { key = keyFrameArr[j]; key.value = float.Parse(key.value.ToString("f3")); key.inTangent = float.Parse(key.inTangent.ToString("f3")); key.outTangent = float.Parse(key.outTangent.ToString("f3")); keyFrameArr[j] = key; } curveData.curve.keys = keyFrameArr; clip.SetCurve(curveData.path, curveData.type, curveData.propertyName, curveData.curve); } } static private List GetFBXAnimationClips(string fbxPath) { var list = new List(); var objs = AssetDatabase.LoadAllAssetsAtPath(fbxPath); foreach (var obj in objs) { if (obj is AnimationClip) list.TryAdd(obj as AnimationClip); } return list; } //重新绑定动画 static private string ReBindingAnimationController(string fbxPath, AnimationClip clip, ref string saveName) { var clipName = clip.name; var dir = Path.GetDirectoryName(fbxPath); var info = new DirectoryInfo(dir); var controllers = info.GetFiles("*.controller", SearchOption.AllDirectories); if (null != controllers && controllers.Length > 0) { var controller = controllers[0]; var controllerPath = dir + "/" + controller.Name; var animatorController = AssetDatabase.LoadAssetAtPath(controllerPath); var sm = (animatorController != null ? animatorController.layers[0].stateMachine : null); if (sm) { int count = (sm != null ? sm.states.Length : 0); for (int i = 0; i < count; i++) { Motion mt = sm.states[i].state.motion; if (mt != null) { if (clipName == mt.name) sm.states[i].state.motion = clip; } } } saveName = controller.Name; return controllerPath; } return string.Empty; } static private void PreProcessModel(string fbxPath) { var modelImporter = AssetImporter.GetAtPath(fbxPath) as ModelImporter; if (modelImporter != null) { var isChange = false; if (modelImporter.animationCompression != ModelImporterAnimationCompression.Optimal) { isChange = true; modelImporter.animationCompression = ModelImporterAnimationCompression.Optimal; modelImporter.animationPositionError = POS_ERROR; modelImporter.animationRotationError = ROT_ERROR; modelImporter.animationScaleError = SAL_ERROR; modelImporter.resampleCurves = false; } else { if (!Mathf.Approximately(modelImporter.animationPositionError, POS_ERROR)) { isChange = true; modelImporter.animationPositionError = POS_ERROR; } if (!Mathf.Approximately(modelImporter.animationRotationError, ROT_ERROR)) { isChange = true; modelImporter.animationRotationError = ROT_ERROR; } if (!Mathf.Approximately(modelImporter.animationScaleError, SAL_ERROR)) { isChange = true; modelImporter.animationScaleError = SAL_ERROR; } if (modelImporter.resampleCurves) { isChange = true; modelImporter.resampleCurves = false; } } if (isChange) { modelImporter.SaveAndReimport(); AssetDatabase.Refresh(); } } } static private void Optimize(string fbxPath, bool onlyCompress) { var animationClipList = GetFBXAnimationClips(fbxPath); //new List (AnimationUtility.GetAnimationClips(gameObject)); var idx = fbxPath.LastIndexOf("/"); var animPathDir = fbxPath.Substring(0, idx + 1).Replace("models", "animations/models"); if (!Directory.Exists(animPathDir)) Directory.CreateDirectory(animPathDir); int count = animationClipList.Count; string controllerPath = string.Empty; string controllerTarget = string.Empty; for (int i = 0; i < count; i++) { var clip = animationClipList[i]; if (clip.name.Contains("__preview")) continue; AnimationClip target = new AnimationClip(); EditorUtility.CopySerialized(clip, target); if (target) { RemoveAnimationCurve(target, onlyCompress); var animPath = animPathDir + clip.name + ".anim"; AssetDatabase.CreateAsset(target, animPath); controllerPath = ReBindingAnimationController(fbxPath, target, ref controllerTarget); } } if (!string.IsNullOrEmpty(controllerPath)) { AssetDatabase.MoveAsset(controllerPath, animPathDir + "/" + controllerTarget); AssetDatabase.SaveAssets(); } } static private void OptimizeClip(string animPath, bool onlyCompress) { var clip = AssetDatabase.LoadAssetAtPath(animPath); if (null != clip) { RemoveAnimationCurve(clip, onlyCompress); } } static private void PostProcessModel(string fbxPath) { var modelImporter = AssetImporter.GetAtPath(fbxPath) as ModelImporter; if (modelImporter != null) { if (modelImporter.importAnimation) { modelImporter.importAnimation = false; modelImporter.SaveAndReimport(); AssetDatabase.Refresh(); } } } } }