464 lines
17 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|