源战役客户端
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

562 行
21 KiB

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEditor;
  4. using System.IO;
  5. using UnityEngine;
  6. using UnityEditor.Animations;
  7. using System.Linq;
  8. using System.Text;
  9. public class AnimationHelper
  10. {
  11. static string[] animations_path =
  12. {
  13. "",
  14. // @"Assets/LuaFramework/AssetBundleRes/scene/object/role",
  15. //@"Assets/LuaFramework/AssetBundleRes/scene/object/npc",
  16. //@"Assets/LuaFramework/AssetBundleRes/scene/object/monster",
  17. //@"Assets/LuaFramework/AssetBundleRes/scene/object/mount",
  18. //@"Assets/LuaFramework/AssetBundleRes/scene/object/wing",
  19. //@"Assets/LuaFramework/AssetBundleRes/scene/object/headwear",
  20. };
  21. //特殊路径过滤
  22. static string[] mask_path =
  23. {
  24. };
  25. static private string ErrorString = "";
  26. static private string LogString = "";
  27. static private string SpeicalLogString = "";
  28. static Dictionary<string, List<ClipAnimData>> allDatas = new Dictionary<string, List<ClipAnimData>>(); //所有正常的数据
  29. static Dictionary<string, List<ClipAnimData>> allSpecialDatas = new Dictionary<string, List<ClipAnimData>>(); //所有异常数据目录,比如同一个目录下有多个action
  30. static Dictionary<string, string> allTempPaths = new Dictionary<string, string>(); //所有动画文件,路径缓存
  31. static bool needScleProcess = true;
  32. public class ClipAnimData
  33. {
  34. public string _name; //fbx中动画的名字
  35. public AnimationClip _clip; //生成的新的动画文件
  36. public string _path; //原fbx文件路径
  37. public string _clip_file_path; //chip文件路径
  38. public ClipAnimData(string name, string path, AnimationClip clip, string clip_file_path)
  39. {
  40. _name = name;
  41. _clip = clip;
  42. _path = path;
  43. _clip_file_path = clip_file_path;
  44. }
  45. };
  46. public class ReplaceClipAnimData
  47. {
  48. public string _stateName; //状态机的名字
  49. public Motion _motion; //状态机的动画
  50. public ClipAnimData _clip; //状态机的新动画数据
  51. public ReplaceClipAnimData(string name, Motion motion, ClipAnimData clip)
  52. {
  53. _stateName = name;
  54. _motion = motion;
  55. _clip = clip;
  56. }
  57. };
  58. //将目标路径下的fbx转为anim,并挂到action controller
  59. // [MenuItem("AnimationHelper/Format Animations By Config")]
  60. [MenuItem("AnimationHelper/步骤1. 格式化选中路径动作(剔除曲线)")]
  61. public static void FormatSelectPath()
  62. {
  63. needScleProcess = true;
  64. FormatAllAnimations();
  65. }
  66. [MenuItem("AnimationHelper/步骤1. 格式化选中路径动作(保留曲线)")]
  67. public static void FormatSelectPathWithoutScale()
  68. {
  69. needScleProcess = false;
  70. FormatAllAnimations();
  71. }
  72. public static void FormatAllAnimations()
  73. {
  74. if (!EditorUtility.DisplayDialog("提示", "首次格式化动作文件,需要持续十几分钟,是否继续?", "继续", "取消"))
  75. return;
  76. Object[] objs = Selection.objects;
  77. if(objs.Length > 0)
  78. {
  79. if(objs.Length > 1)
  80. {
  81. LogString = "只能操作一个文件夹";
  82. EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定");
  83. return;
  84. }
  85. animations_path[0] = AssetDatabase.GetAssetPath(objs[0]);
  86. // LogString = animations_path[0];
  87. // EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定");
  88. // return;
  89. }
  90. else{
  91. LogString = "未选中需要压缩的文件夹,如:role等";
  92. Debug.Log(LogString);
  93. EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定");
  94. return;
  95. }
  96. //查找指定路径下指定类型的所有资源,返回的是资源GUID
  97. string[] guids = AssetDatabase.FindAssets("action", animations_path);
  98. //从GUID获得资源所在路径
  99. List<string> paths = new List<string>();
  100. guids.ToList().ForEach(m => paths.Add(AssetDatabase.GUIDToAssetPath(m)));
  101. ErrorString = "异常处理的文件日志\n";
  102. LogString = "需要处理的文件日志\n";
  103. SpeicalLogString = "特殊文件日志\n";
  104. allDatas.Clear();
  105. allSpecialDatas.Clear();
  106. allTempPaths.Clear();
  107. for (int i = 0; i < paths.Count; i++)
  108. {
  109. AnimatorController controller = AssetDatabase.LoadAssetAtPath(paths[i], typeof(AnimatorController)) as AnimatorController;
  110. if (controller != null)
  111. {
  112. List<ClipAnimData> clipDatas = new List<ClipAnimData>();
  113. var path = paths[i];
  114. var goName = controller.name + ".controller";
  115. path = path.Replace(@"/" + goName, "");
  116. //过滤的路径不处理
  117. if (IsMakPath(path))
  118. continue;
  119. string[] fbxGuids = AssetDatabase.FindAssets("t:GameObject", new string[] { path });
  120. if(fbxGuids.Length > 0)
  121. {
  122. foreach (var guid in fbxGuids)
  123. {
  124. string file = AssetDatabase.GUIDToAssetPath(guid);
  125. CheckFBXAnimCompression(file);
  126. ClipAnimData clip = CreateNewAnimationClip(file);
  127. if (clip != null)
  128. {
  129. clipDatas.Add(clip);
  130. }
  131. if (File.Exists(file))
  132. DeleteFile(file);
  133. }
  134. }
  135. if (clipDatas.Count > 0)
  136. {
  137. AppendLog("============> action path = " + path + " ================= > actionName = " + goName);
  138. if (allTempPaths.ContainsKey(path))
  139. {
  140. AppendLog(" 同目录下存在2个动画控制器 [" + path + "]");
  141. allSpecialDatas.Add(paths[i], clipDatas);
  142. var value = allTempPaths[path];
  143. if (allDatas.ContainsKey(value))
  144. {
  145. allSpecialDatas.Add(value, allDatas[value]);
  146. allDatas.Remove(value);
  147. }
  148. }
  149. else
  150. {
  151. allTempPaths.Add(path, paths[i]);
  152. allDatas.Add(paths[i], clipDatas);
  153. }
  154. }
  155. }
  156. }
  157. if (allDatas.Count > 0)
  158. {
  159. foreach (var item in allDatas)
  160. {
  161. RePlaceMotion(item.Key, item.Value);
  162. }
  163. //去掉所有anim文件不需要的数据
  164. DeleteAllAnimUnuseData(allDatas);
  165. }
  166. AssetDatabase.SaveAssets();
  167. AssetDatabase.Refresh();
  168. Debug.Log(LogString);
  169. Debug.Log(ErrorString);
  170. Debug.Log(SpeicalLogString);
  171. string show_tips = "FormatAllAnimations完成, 共生成替换了"+guids.Length+"个action";
  172. EditorUtility.DisplayDialog("提示", show_tips, "确定");
  173. }
  174. //将选中的fbx转为anim
  175. // [MenuItem("AnimationHelper/格式化选中路径动作")]
  176. // public static void FormatTargetAnimation()
  177. // {
  178. // LogString = "动作格式化日志:\n";
  179. // Object[] objs = Selection.objects;
  180. // if(objs.Length > 0)
  181. // {
  182. // foreach (var item in objs)
  183. // {
  184. // string path = AssetDatabase.GetAssetPath(item);
  185. // AppendLog("path = " + path );
  186. // // var clip = CreateNewAnimationClip(path);
  187. // // if(clip != null)
  188. // // AppendLog("path = " + path + ", actionName = " + clip._name);
  189. // }
  190. // }
  191. // else{
  192. // LogString = "未选中需要压缩的文件夹,如:role等";
  193. // }
  194. // AssetDatabase.SaveAssets();
  195. // AssetDatabase.Refresh();
  196. // Debug.Log(LogString);
  197. // EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定");
  198. // }
  199. //对目标路径下的所有anim执行一遍精度优化
  200. // [MenuItem("AnimationHelper/Process all Animations")]
  201. public static void ProcessAnimation()
  202. {
  203. //查找指定路径下指定类型的所有资源,返回的是资源GUID
  204. string[] guids = AssetDatabase.FindAssets("t:animationClip", animations_path);
  205. //从GUID获得资源所在路径
  206. List<string> paths = new List<string>();
  207. guids.ToList().ForEach(m => paths.Add(AssetDatabase.GUIDToAssetPath(m)));
  208. foreach (var p in paths)
  209. {
  210. AnimationClip tempClip = AssetDatabase.LoadAssetAtPath(p, typeof(AnimationClip)) as AnimationClip;
  211. if (tempClip != null)
  212. {
  213. OnProcessAnimationClip(tempClip, p);
  214. }
  215. }
  216. AssetDatabase.SaveAssets();
  217. AssetDatabase.Refresh();
  218. string show_tips = "Process Animation 完成";
  219. EditorUtility.DisplayDialog("提示", show_tips, "确定");
  220. }
  221. //重新关联AnimatorController中的动画
  222. static bool RePlaceMotion(string path, List<ClipAnimData> list)
  223. {
  224. bool result = true;
  225. AnimatorController animatorController = AssetDatabase.LoadAssetAtPath(path, typeof(AnimatorController)) as AnimatorController;
  226. if (animatorController != null && list != null && list.Count > 0)
  227. {
  228. List<ReplaceClipAnimData> replaceData = new List<ReplaceClipAnimData>();
  229. //取得AnimatorController中的动画状态机
  230. AnimatorStateMachine stateMachine = animatorController.layers[0].stateMachine;
  231. for (int i = 0; i < stateMachine.states.Length; i++)
  232. {
  233. if (stateMachine.states[i].state == null)
  234. {
  235. AppendErrorLog(" not find statue, path = [ " + path + "]");
  236. result = false;
  237. continue;
  238. }
  239. if (stateMachine.states[i].state.motion == null)
  240. {
  241. AppendLog(" not find statue motion, state = [" + stateMachine.states[i].state.name + " ]" + " + path = [ " + path + "]");
  242. //result = false;
  243. //continue;
  244. }
  245. string motionName = stateMachine.states[i].state.name;
  246. ClipAnimData t = null;
  247. //原动画状态机的名字和生成的动画片段名字相匹配,才会替换
  248. for (int j = 0; j < list.Count; j++)
  249. {
  250. if (list[j]._name == motionName)
  251. {
  252. t = list[j];
  253. break;
  254. }
  255. }
  256. if (t != null)
  257. {
  258. replaceData.Add(new ReplaceClipAnimData(stateMachine.states[i].state.name, stateMachine.states[i].state.motion, t));
  259. }
  260. else
  261. {
  262. AppendErrorLog(" not find AnimationClip motionName = [" + motionName + "]");
  263. }
  264. }
  265. if (replaceData.Count > 0)
  266. {
  267. foreach (var item in replaceData)
  268. {
  269. var stateName = item._stateName;
  270. var motion = item._motion;
  271. var clip = item._clip;
  272. for (int i = 0; i < stateMachine.states.Length; i++)
  273. {
  274. if (stateName == stateMachine.states[i].state.name)
  275. {
  276. stateMachine.states[i].state.motion = clip._clip;
  277. //替换之后,删除原来的FBX文件
  278. DeleteFile(clip._path);
  279. break;
  280. }
  281. }
  282. }
  283. }
  284. }
  285. return result;
  286. }
  287. //根据原来的FBx文件生成新的动画片段
  288. static ClipAnimData CreateNewAnimationClip(string path)
  289. {
  290. Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(Object)) as Object;
  291. AnimationClip oldClip = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip;
  292. if (obj != null && oldClip != null)
  293. {
  294. AnimationClip newClip = new AnimationClip();
  295. EditorUtility.CopySerialized(oldClip, newClip);
  296. string newPath = path;
  297. newPath = newPath.Replace(obj.name + ".FBX", "");
  298. string newName = obj.name;
  299. newName = newName.Replace(@"@", @"_new@");
  300. OnProcessAnimationClip(newClip, path);
  301. //若生成的文件以及存在,删除掉
  302. if (File.Exists(newPath + newName + ".anim"))
  303. {
  304. DeleteFile(newPath + newName + ".anim");
  305. }
  306. //生成新的动画片段
  307. AssetDatabase.CreateAsset(newClip, newPath + newName + ".anim");
  308. return new ClipAnimData(oldClip.name, path, newClip, newPath + newName + ".anim");
  309. }
  310. return null;
  311. }
  312. //动画片段精度压缩
  313. static void OnProcessAnimationClip(AnimationClip clip, string path)
  314. {
  315. // bool needScleProcess = true;
  316. // if (path.Contains("object/role") && path.Contains("show"))
  317. // {
  318. // needScleProcess = false;
  319. // }
  320. // else if (path.Contains("object/pet"))
  321. // {
  322. // needScleProcess = false;
  323. // }
  324. // Debug.Log("path" + path + "; needScleProcess"+needScleProcess);
  325. if (needScleProcess)
  326. {
  327. foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(clip))
  328. {
  329. string name = theCurveBinding.propertyName.ToLower();
  330. if (name.Contains("scale"))
  331. {
  332. AnimationUtility.SetEditorCurve(clip, theCurveBinding, null);
  333. }
  334. }
  335. }
  336. //浮点数精度压缩到f3
  337. AnimationClipCurveData[] curves = null;
  338. curves = AnimationUtility.GetAllCurves(clip);
  339. Keyframe key;
  340. Keyframe[] keyFrames;
  341. string formatType = "f3";
  342. if (needScleProcess == false)
  343. {
  344. formatType = "f5";
  345. }
  346. for (int ii = 0; ii < curves.Length; ++ii)
  347. {
  348. AnimationClipCurveData curveDate = curves[ii];
  349. if (curveDate.curve == null || curveDate.curve.keys == null)
  350. {
  351. continue;
  352. }
  353. keyFrames = curveDate.curve.keys;
  354. for (int i = 0; i < keyFrames.Length; i++)
  355. {
  356. key = keyFrames[i];
  357. key.value = float.Parse(key.value.ToString(formatType));
  358. key.inTangent = float.Parse(key.inTangent.ToString(formatType));
  359. key.outTangent = float.Parse(key.outTangent.ToString(formatType));
  360. keyFrames[i] = key;
  361. }
  362. curveDate.curve.keys = keyFrames;
  363. clip.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);
  364. }
  365. /*
  366. Keyframe[] keyFrames;
  367. Keyframe key;
  368. foreach (var binding in AnimationUtility.GetCurveBindings(clip))
  369. {
  370. AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
  371. keyFrames = curve.keys;
  372. for (int i = 0; i < keyFrames.Length; i++)
  373. {
  374. key = keyFrames[i];
  375. key.value = float.Parse(key.value.ToString("f3"));
  376. key.inTangent = float.Parse(key.inTangent.ToString("f3"));
  377. key.outTangent = float.Parse(key.outTangent.ToString("f3"));
  378. keyFrames[i] = key;
  379. }
  380. curve.keys = keyFrames;
  381. clip.SetCurve(binding.path, binding.type, binding.propertyName, curve);
  382. }
  383. */
  384. }
  385. //删除文件
  386. public static void DeleteFile(string path)
  387. {
  388. AssetDatabase.DeleteAsset(path);
  389. }
  390. //拼接错误log
  391. public static void AppendErrorLog(string log)
  392. {
  393. ErrorString = ErrorString + log + "\n";
  394. }
  395. //拼接log
  396. public static void AppendLog(string log, int logLevel = 0)
  397. {
  398. if (logLevel == 0)
  399. LogString = LogString + log + "\n";
  400. else
  401. SpeicalLogString = SpeicalLogString + log + "\n";
  402. }
  403. public static bool IsMakPath(string path)
  404. {
  405. if(mask_path.Length > 0)
  406. {
  407. foreach (var p in mask_path)
  408. {
  409. if (p == path)
  410. return true;
  411. }
  412. }
  413. return false;
  414. }
  415. //去除所选文件下中anim的不需要的参数
  416. [MenuItem("AnimationHelper/步骤2. 去除anim的冗余参数")]
  417. public static void FormatAllAnimationChilp()
  418. {
  419. if (!EditorUtility.DisplayDialog("提示", "首次格式化动作文件,需要持续十几分钟,是否继续?", "继续", "取消"))
  420. return;
  421. Object[] objs = Selection.objects;
  422. if (objs.Length > 0)
  423. {
  424. if (objs.Length > 1)
  425. {
  426. LogString = "只能操作一个文件夹";
  427. EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定");
  428. return;
  429. }
  430. animations_path[0] = AssetDatabase.GetAssetPath(objs[0]);
  431. }
  432. else
  433. {
  434. LogString = "未选中需要压缩的文件夹,如:role等";
  435. Debug.Log(LogString);
  436. EditorUtility.DisplayDialog("Format Target Animation", LogString, "确定");
  437. return;
  438. }
  439. //查找指定路径下指定类型的所有资源,返回的是资源GUID
  440. string[] guids = AssetDatabase.FindAssets("action", animations_path);
  441. //从GUID获得资源所在路径
  442. List<string> paths = new List<string>();
  443. guids.ToList().ForEach(m => paths.Add(AssetDatabase.GUIDToAssetPath(m)));
  444. ErrorString = "异常处理的文件日志\n";
  445. LogString = "需要处理的文件日志\n";
  446. SpeicalLogString = "特殊文件日志\n";
  447. allDatas.Clear();
  448. allSpecialDatas.Clear();
  449. allTempPaths.Clear();
  450. string[] chipGuids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { animations_path[0]});
  451. if (chipGuids.Length > 0)
  452. {
  453. foreach (var guid in chipGuids)
  454. {
  455. string path = AssetDatabase.GUIDToAssetPath(guid);
  456. ProressDeleteAnimUnuseData(path);
  457. }
  458. }
  459. AssetDatabase.SaveAssets();
  460. AssetDatabase.Refresh();
  461. Debug.Log(LogString);
  462. Debug.Log(ErrorString);
  463. Debug.Log(SpeicalLogString);
  464. string show_tips = "FormatAllAnimationChilp完成, 共生成替换了" + guids.Length + "个action";
  465. EditorUtility.DisplayDialog("提示", show_tips, "确定");
  466. }
  467. //去掉anim文件中的 intWeight 和 outWeight
  468. public static void DeleteAllAnimUnuseData(Dictionary<string, List<ClipAnimData>> data)
  469. {
  470. if (data == null || data.Count == 0) return;
  471. foreach (var item in data)
  472. {
  473. List<ClipAnimData> list = item.Value;
  474. if(list.Count > 0)
  475. {
  476. for(int i = 0; i< list.Count; i++)
  477. {
  478. ProressDeleteAnimUnuseData(list[i]._clip_file_path);
  479. }
  480. }
  481. RePlaceMotion(item.Key, item.Value);
  482. }
  483. }
  484. //去掉anim文件中的 intWeight 和 outWeight
  485. public static void ProressDeleteAnimUnuseData(string path)
  486. {
  487. string[] result = File.ReadAllLines(path, Encoding.UTF8);
  488. if (result.Length > 0)
  489. {
  490. List<string> new_list = new List<string>();
  491. for (int k = 0; k < result.Length; k++)
  492. {
  493. string content = result[k];
  494. if (!content.Contains("inWeight:") && !content.Contains("outWeight:") && !content.Contains("weightedMode:") )
  495. {
  496. new_list.Add( content);
  497. }
  498. }
  499. File.WriteAllLines(path, new_list);
  500. }
  501. }
  502. //修改压缩格式
  503. public static void CheckFBXAnimCompression(string path)
  504. {
  505. ModelImporter mi = AssetImporter.GetAtPath(path) as ModelImporter;
  506. mi.animationCompression = ModelImporterAnimationCompression.Optimal;
  507. AssetDatabase.SaveAssets();
  508. }
  509. }