2023-04-03 11:04:31 +08:00

464 lines
17 KiB
C#

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<GameObject>(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, string>();
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<AnimatorController>(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<AnimationClip> GetFBXAnimationClips(string fbxPath)
{
var list = new List<AnimationClip>();
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<AnimatorController>(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<AnimationClip> (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<AnimationClip>(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();
}
}
}
}
}