源战役客户端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

562 lines
21 KiB

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();
}
}