自动切割fbx中的动画

自动切割FBX中的动画

最近写了一个有意思的工具,能够自动切割fbx中的动画信息,如下图所示:

本来不想造轮子,从晚上查找一番,发现并没有自己满意的工具,只好自己出手啦。

核心代码如下:

  1. [MenuItem("liubo/自动切割骨骼动画2")]
  2. public static void AutoClip2()
  3. {
  4. AutoClip2Impl("");
  5. }
  6. static void AutoClip2Impl(string spec)
  7. {
  8. /*
  9. * 读取excel表,获取动画的信息:名字,起始帧,结束帧
  10. * 再读取fbx的动画信息,对名字相同的进行信息merge
  11. * merge:起始帧,结束帧
  12. * 保留:event信息,mask信息,curve信息
  13. * 如果有新加的,那么加入新的信息
  14. * **/
  15. // 匿名函数
  16. System.Func<bool, ConfigLine, SerializedProperty, int> SetPropertyFunction = delegate(bool IsHumanClip, ConfigLine csv_line, SerializedProperty sp)
  17. {
  18. sp.FindPropertyRelative("firstFrame").floatValue = csv_line.GetColumnUnsafe<float>("firstFrame");
  19. sp.FindPropertyRelative("lastFrame").floatValue = csv_line.GetColumnUnsafe<float>("lastFrame");
  20. // LoopTime, LoopPose
  21. sp.FindPropertyRelative("loopTime").boolValue = csv_line.GetColumnUnsafe<int>("loopTime") > 0;
  22. sp.FindPropertyRelative("loopBlend").boolValue = csv_line.GetColumnUnsafe<int>("loopBlend") > 0;
  23. // Rotation_BakeIntoPose, Rotation_BaseUpon
  24. sp.FindPropertyRelative("loopBlendOrientation").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendOrientation") > 0;
  25. sp.FindPropertyRelative("keepOriginalOrientation").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalOrientation") == 0;
  26. // PositionY_BakeIntoPose, PositionY_BaseUpon
  27. sp.FindPropertyRelative("loopBlendPositionY").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendPositionY") > 0;
  28. if (IsHumanClip)
  29. {
  30. // 如果是humanoid类型的动画,那么keepOriginalPositionY的取值为0,1,2三种。
  31. sp.FindPropertyRelative("keepOriginalPositionY").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionY") == 0;
  32. sp.FindPropertyRelative("heightFromFeet").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionY") == 2;
  33. }
  34. else
  35. {
  36. sp.FindPropertyRelative("keepOriginalPositionY").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionY") == 0;
  37. }
  38. // PositionXZ_BakeIntoPose, PositionXZ_BaseUpon
  39. sp.FindPropertyRelative("loopBlendPositionXZ").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendPositionXZ") > 0;
  40. sp.FindPropertyRelative("keepOriginalPositionXZ").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionXZ") == 0;
  41. return 0;
  42. };
  43. // 读取excel
  44. string[] files = Directory.GetFiles(Application.dataPath, "animations*.xls", SearchOption.AllDirectories);
  45. foreach (var fileName in files)
  46. {
  47. DateTime excelTime = File.GetLastWriteTime(fileName);
  48. Debug.Log("读取xls文件:" + fileName + ", file-write-time=" + excelTime.ToString());
  49. Dictionary<string, ConfigFile> excels = ReadXML(fileName);// ReadXML(Application.dataPath + "/Art/animations.xls");
  50. foreach (var it in excels)
  51. {
  52. if (string.IsNullOrEmpty(spec) || spec == it.Key)
  53. {
  54. }
  55. else
  56. {
  57. // 不处理啦。
  58. continue;
  59. }
  60. Debug.Log("处理:" + it.Key);
  61. ConfigFile csv = it.Value;
  62. string[] assets = AssetDatabase.FindAssets(it.Key, new string[] { "Assets/Art" });
  63. if (assets.Length > 0)
  64. {
  65. string asset = AssetDatabase.GUIDToAssetPath(assets[0]);
  66. // 如果.meta时间和excel时间不一样,说明有变化,否则没变化的就直接跳过
  67. DateTime assetMetaTime = File.GetLastWriteTime(asset + ".meta");
  68. if (DateTime.Equals(assetMetaTime, excelTime))
  69. {
  70. // 文件没有变化,不处理
  71. Debug.Log("文件没有变化,不处理啦:" + asset);
  72. continue;
  73. }
  74. File.SetLastWriteTime(asset + ".meta", excelTime);
  75. ModelImporter modelImporter = (ModelImporter)AssetImporter.GetAtPath(asset);
  76. SerializedObject serializedObject = modelImporter == null ? null : new SerializedObject(modelImporter);
  77. ModelImporterAnimationType m_AnimationType = (ModelImporterAnimationType)serializedObject.FindProperty("m_AnimationType").intValue;
  78. bool IsHumanClip = m_AnimationType == ModelImporterAnimationType.Human;
  79. HashSet<string> processed_clip = new HashSet<string>();
  80. if (serializedObject == null)
  81. {
  82. Debug.Log("无法处理该文件:" + asset);
  83. }
  84. else
  85. {
  86. // 压缩方式
  87. serializedObject.FindProperty("m_AnimationCompression").intValue = (int)ModelImporterAnimationCompression.Optimal;
  88. // 切割或者merge动画
  89. SerializedProperty m_ClipAnimations = serializedObject.FindProperty("m_ClipAnimations");
  90. for (int i = 0; i < m_ClipAnimations.arraySize; i++)
  91. {
  92. SerializedProperty sp = m_ClipAnimations.GetArrayElementAtIndex(i);
  93. string clip_name = sp.FindPropertyRelative("name").stringValue;
  94. if (clip_name.Contains("@"))
  95. {
  96. clip_name = clip_name.Substring(clip_name.LastIndexOf("@") + 1);
  97. }
  98. ConfigLine csv_line = csv.FindData(clip_name);
  99. if (csv_line != null)
  100. {
  101. processed_clip.Add(clip_name);
  102. // merge基本属性
  103. SetPropertyFunction(IsHumanClip, csv_line, sp);
  104. // 其他属性,如event,mask,都不处理,保持原来信息
  105. }
  106. }
  107. // 把剩下没处理的内容,作为新的动画,分割到fbx中
  108. Dictionary<string, ConfigLine> csv_data = csv.GetLines();
  109. foreach (var kt in csv_data)
  110. {
  111. if (processed_clip.Contains(kt.Key))
  112. {
  113. continue;
  114. }
  115. processed_clip.Add(kt.Key);
  116. ConfigLine csv_line = kt.Value;
  117. m_ClipAnimations.InsertArrayElementAtIndex(m_ClipAnimations.arraySize);
  118. SerializedProperty sp = m_ClipAnimations.GetArrayElementAtIndex(m_ClipAnimations.arraySize - 1);
  119. sp.FindPropertyRelative("name").stringValue = it.Key + "@" + kt.Key;
  120. sp.FindPropertyRelative("takeName").stringValue = "Take 001";
  121. // 处理基本属性
  122. SetPropertyFunction(IsHumanClip, csv_line, sp);
  123. // 设置mask
  124. UnityEditorInternal.AvatarMask mask = new UnityEditorInternal.AvatarMask();
  125. mask.transformCount = modelImporter.transformPaths.Length;
  126. for (int i = 0; i < modelImporter.transformPaths.Length; i++)
  127. {
  128. mask.SetTransformPath(i, modelImporter.transformPaths[i]);
  129. mask.SetTransformActive(i, true);
  130. }
  131. SerializedProperty bodyMask = sp.FindPropertyRelative("bodyMask");
  132. if (bodyMask != null && bodyMask.isArray)
  133. {
  134. for (int i = 0; i < mask.humanoidBodyPartCount; i++)
  135. {
  136. if (i >= bodyMask.arraySize) bodyMask.InsertArrayElementAtIndex(i);
  137. bodyMask.GetArrayElementAtIndex(i).intValue = mask.GetHumanoidBodyPartActive(i) ? 1 : 0;
  138. }
  139. }
  140. SerializedProperty transformMask = sp.FindPropertyRelative("transformMask");
  141. //ModelImporter.UpdateTransformMask(mask, transformMask);
  142. Type ty = typeof(ModelImporter);
  143. MethodInfo mi = ty.GetMethod("UpdateTransformMask", BindingFlags.Static | BindingFlags.NonPublic);
  144. if (mi != null)
  145. {
  146. mi.Invoke(null, new object[] { mask, transformMask });
  147. }
  148. else
  149. {
  150. Debug.LogError("无法找到此方法!");
  151. }
  152. }
  153. serializedObject.ApplyModifiedProperties();
  154. AssetDatabase.WriteImportSettingsIfDirty(asset);
  155. AssetDatabase.ImportAsset(asset);
  156. }
  157. }
  158. else
  159. {
  160. Debug.LogError("无法找到这个FBX文件:" + it.Key);
  161. }
  162. }
  163. }
  164. }
  165. static void ShowProperties(SerializedProperty sp)
  166. {
  167. SerializedProperty backup = sp.Copy();
  168. {
  169. sp = backup.Copy();
  170. Debug.Log("[显示属性] sp.name=" + sp.displayName + ", " + sp.name);
  171. sp.Next(true);
  172. do
  173. {
  174. Debug.Log("[显示属性] sp.name=" + sp.displayName + ", " + sp.name);
  175. } while (sp.Next(false));
  176. }
  177. }

实现原理

    简单说明一下,需要攻克的问题就是,找到ModelImporter的属性信息,我是咋找到的呢,看对应的meta文件,以及...(次数省略三个字)。不过话说回来,meta文件中的信息很丰富,再加上自己写的ShowProperties函数,基本上都能猜到,有点站着说话不腰疼的感觉,哈哈。

核心的属性

其实,这些属性在meta中都能找到

  • name
  • firstFrame
  • lastFrame
  • loopTime
  • loopBlend
  • loopBlendOrientation
  • keepOriginalOrientation
  • loopBlendPositionY
  • keepOriginalPositionY
  • loopBlendPositionXZ
  • keepOriginalPositionXZ
  • bodyMask
  • transformMask
  • event

有人可能会问ModelImporter已经包含了很多属性,比如animationCompression,为什么不直接设置,非得用修改Property的形式呢?

我只能说,我设置过属性,但是不知道怎么,他并没有导出到meta中,所以就用了这段代码
  1. serializedObject.FindProperty("m_AnimationCompression").intValue = (int)ModelImporterAnimationCompression.Optimal;

错误的方法

最开始,没有研究Property的时候,采用了很直接的方法,如下所示。这段代码带来的危害就是,无法进行merge旧的meta文件,会导致event和mask丢失。
  1. [MenuItem("liubo/自测/自动切割骨骼动画")]
  2. public static void AutoClip()
  3. {
  4. string path = Selection.activeObject == null ? "" : AssetDatabase.GetAssetPath(Selection.activeObject);
  5. Debug.Log("[切割动画] path=" + path);
  6. ModelImporter mi = AssetImporter.GetAtPath(path) as ModelImporter;
  7. if (mi == null)
  8. {
  9. Debug.Log("[切割动画] 没找到FBX文件! path=" + path);
  10. }
  11. else
  12. {
  13. Debug.Log("[切割动画] 开始切割:path=" + path);
  14. // 注意保留就动画的一些数据,比如绑定的event
  15. List<ModelImporterClipAnimation> oldClips = new List<ModelImporterClipAnimation>();
  16. List<ModelImporterClipAnimation> newClips = new List<ModelImporterClipAnimation>();
  17. if (mi.clipAnimations != null)
  18. {
  19. oldClips.AddRange(mi.clipAnimations);
  20. }
  21. #if true//TEST
  22. //AnimationUtility.GetAnimationClipSettings(null);
  23. ModelImporterClipAnimation test = new ModelImporterClipAnimation();
  24. test.name = "test";
  25. test.takeName = "auto";
  26. test.firstFrame = 0;
  27. test.lastFrame = 30;
  28. test.wrapMode = WrapMode.Default;
  29. test.keepOriginalPositionY = true;
  30. test.loop = false;
  31. test.loopTime = false;
  32. test.loopPose = false;
  33. test.keepOriginalOrientation = false;
  34. test.keepOriginalPositionY = false;
  35. test.keepOriginalPositionXZ = false;
  36. test.cycleOffset = 0;
  37. test.heightOffset = 0;
  38. test.rotationOffset = 0;
  39. newClips.Add(test);
  40. newClips.AddRange(oldClips);
  41. #endif
  42. mi.clipAnimations = newClips.ToArray();
  43. EditorUtility.SetDirty(Selection.activeObject);
  44. }
  45. }

链接

unity工程链接:https://github.com/badforlabor/test2 中的auto_split_animation

来自为知笔记(Wiz)

时间: 2024-11-07 03:57:30

自动切割fbx中的动画的相关文章

FBX BlendShape/Morph动画解析

目前fbx 2015.1中支持三种变形器:skinDeformer,blendShapeDeformer,vertexCacheDeformer.定义在fbxdeformer.h中: enum EDeformerType { eUnknown, //!< Unknown deformer type eSkin, //!< Type FbxSkin eBlendShape, //!< Type FbxBlendShape eVertexCache //!< Type FbxVerte

Android 中通过切割图片创建人物行走动画

以前一直使用序列图片来实现动画效果,造成空间的极大浪费,所以想要尝试下切割图片来实现动画. 如图所示,是由66rpg纸娃娃系统生成的角色行走图.本程序必须实现将人物的整体图片切割后存入4x4的数组来动态加载. 在主布局文件中设立4个ImageView,分别用来显示角色在不同角度下的行走动作.然后在主活动文件中: public class MainActivity extends ActionBarActivity {private ImageView myImageView,myImageVie

iOS中Animation 动画 UI_22

1.iOS中我们能看到的控件都是UIView的子类,比如UIButton UILabel UITextField UIImageView等等 2.UIView能够在屏幕的显示是因为在创建它的时候内部自动添加一个CALayer图层,通过这个图层在屏幕上显示的时候会调用一个drawRect: 的方法,完成绘图,才能在屏幕上显示 3.CALayer 本身就具有显示功能,但是它不能响应用户的交互事件,如果只是单纯的显示一个图形,此时你可以使用CALayer创建或者是使用UIView创建,但是如果这个图形

初识android中的动画

动画效果可以大大提高界面的交互效果,因此,动画在移动开发中的应用场景较为普遍.掌握基本的动画效果在成熟的软件开发中不可或缺.除此之外,用户对于动画的接受程度远高于文字和图片,利用动画效果可以加深用户对于产品的印象.因此本文给出安卓设计中几种常见的动画效果. 基础知识 在介绍安卓中的动画效果之前,有必要介绍一下安卓中的图片处理机制.图片的特效包括图形的缩放.镜面.倒影.旋转.平移等.图片的特效处理方式是将原图的图形矩阵乘以一个特效矩阵,形成一个新的图形矩阵来实现的.矩阵Matrix 类,维护了一个

Android中属性动画的基本用法

在开发中属性动画是很常用的功能,下面我把属性动画的基本用法记录一下,供他人学习,也逐渐积累自己的知识. 单个动画效果: //创建动画对象,后面的参数依次为:动画效果的目标组件,需要改变的该组建的属性(必须有对应的get和set方法就可以),后面三个参数写变化过程对应数值. ObjectAnimator animator= ObjectAnimator.ofFloat(textView, "TextSize", 15, 50, 15); //动画过程所用时间,会按这个世界自动平滑执行 a

android动画详解六 XML中定义动画

动画View 属性动画系统允许动画View对象并提供很多比view动画系统更高级的功能.view动画系统通过改变绘制方式来变换View对象,view动画是被view的容器所处理的,因为View本身没有要操控的属性.结果就是View被动画了,但View对象本身并没有变化.在Android3.0中,新的属性和相应的getter和setter方法被加入以克服此缺点. 属性动画系统可以通过改变View对象的真实属性来动画Views.而且,View也会在其属性改变时自动调用invalidate()方法来刷

android中的动画全解析

Android为我们提供了2中动画 一: Tween Animation 渐变动画 通过对特定对象做图像的变换,例如: 平移, 缩放,旋转, 淡入淡出 等. 二: Frame Animation 帧动画 创建一个淡入阿瓦不了可以按照指定的时间间隔一个一个显示.顺序播放 下面我们就详细介绍一下这两中动画: 首先我们介绍 渐变动画: 1. Tween Animation 1. 渐变动画有四种样式: alpha: 渐变透明度 scale: 缩放尺寸伸缩动画 translate: 移动动画效果 rota

在Flash CS6中安装动画辅助制作插件DragonBones

提示:本文后面提供的附件是我从网络上搜索到的一个相当不错的针对Flash CS 6.0的扩展插件,它是在原有的Dragonbone 2.0的基础上作了适当修改,以便导致更多类型的符合COCOS2D-X开发的动画文件.当然,有的动画文件也可以为CocoStudio(例如我讲课中使用的1.4.0.1)中的动画编辑器所导入作进一步修改使用. 有关Dragonbones(http://dragonbones.effecthub.com/)这个东西,相信我不需要再费过多的口舌了.这是一个世界著名的Flas

linux服务器自动切割日志

需求 由于nginx的日志会不停地增大,所以需要我们自己去切割日志,方便管理,需要达到以下的效果: 按日期自动切割日志,最小单位是天. 当日志总量超过一定量时,自动直接清理日志,限定总量不能超过1000MB. 写入crontab定时任务里. 分析 nginx日志目录下分别有access.log和error.log,按照日期自动切割日志则需要将每天的日志以"yyyymmdd_access/error.log"的格式保存下来,用mv重命名每一天的日志文件即可. 清理日志就简单了,只需要判断