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<string, List<ClipAnimData>> allDatas = new Dictionary<string, List<ClipAnimData>>(); //所有正常的数据
|
|
static Dictionary<string, List<ClipAnimData>> allSpecialDatas = new Dictionary<string, List<ClipAnimData>>(); //所有异常数据目录,比如同一个目录下有多个action
|
|
static Dictionary<string, string> allTempPaths = new Dictionary<string, string>(); //所有动画文件,路径缓存
|
|
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<string> paths = new List<string>();
|
|
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<ClipAnimData> clipDatas = new List<ClipAnimData>();
|
|
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<string> paths = new List<string>();
|
|
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<ClipAnimData> list)
|
|
{
|
|
bool result = true;
|
|
AnimatorController animatorController = AssetDatabase.LoadAssetAtPath(path, typeof(AnimatorController)) as AnimatorController;
|
|
if (animatorController != null && list != null && list.Count > 0)
|
|
{
|
|
List<ReplaceClipAnimData> replaceData = new List<ReplaceClipAnimData>();
|
|
//取得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<string> paths = new List<string>();
|
|
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<string, List<ClipAnimData>> data)
|
|
{
|
|
if (data == null || data.Count == 0) return;
|
|
foreach (var item in data)
|
|
{
|
|
List<ClipAnimData> 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<string> new_list = new List<string>();
|
|
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();
|
|
}
|
|
}
|