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

本来不想造轮子,从晚上查找一番,发现并没有自己满意的工具,只好自己出手啦。
核心代码如下:
[MenuItem("liubo/自动切割骨骼动画2")]public static void AutoClip2(){AutoClip2Impl("");}static void AutoClip2Impl(string spec){/** 读取excel表,获取动画的信息:名字,起始帧,结束帧* 再读取fbx的动画信息,对名字相同的进行信息merge* merge:起始帧,结束帧* 保留:event信息,mask信息,curve信息* 如果有新加的,那么加入新的信息* **/// 匿名函数System.Func<bool, ConfigLine, SerializedProperty, int> SetPropertyFunction = delegate(bool IsHumanClip, ConfigLine csv_line, SerializedProperty sp){sp.FindPropertyRelative("firstFrame").floatValue = csv_line.GetColumnUnsafe<float>("firstFrame");sp.FindPropertyRelative("lastFrame").floatValue = csv_line.GetColumnUnsafe<float>("lastFrame");// LoopTime, LoopPosesp.FindPropertyRelative("loopTime").boolValue = csv_line.GetColumnUnsafe<int>("loopTime") > 0;sp.FindPropertyRelative("loopBlend").boolValue = csv_line.GetColumnUnsafe<int>("loopBlend") > 0;// Rotation_BakeIntoPose, Rotation_BaseUponsp.FindPropertyRelative("loopBlendOrientation").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendOrientation") > 0;sp.FindPropertyRelative("keepOriginalOrientation").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalOrientation") == 0;// PositionY_BakeIntoPose, PositionY_BaseUponsp.FindPropertyRelative("loopBlendPositionY").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendPositionY") > 0;if (IsHumanClip){// 如果是humanoid类型的动画,那么keepOriginalPositionY的取值为0,1,2三种。sp.FindPropertyRelative("keepOriginalPositionY").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionY") == 0;sp.FindPropertyRelative("heightFromFeet").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionY") == 2;}else{sp.FindPropertyRelative("keepOriginalPositionY").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionY") == 0;}// PositionXZ_BakeIntoPose, PositionXZ_BaseUponsp.FindPropertyRelative("loopBlendPositionXZ").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendPositionXZ") > 0;sp.FindPropertyRelative("keepOriginalPositionXZ").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionXZ") == 0;return 0;};// 读取excelstring[] files = Directory.GetFiles(Application.dataPath, "animations*.xls", SearchOption.AllDirectories);foreach (var fileName in files){DateTime excelTime = File.GetLastWriteTime(fileName);Debug.Log("读取xls文件:" + fileName + ", file-write-time=" + excelTime.ToString());Dictionary<string, ConfigFile> excels = ReadXML(fileName);// ReadXML(Application.dataPath + "/Art/animations.xls");foreach (var it in excels){if (string.IsNullOrEmpty(spec) || spec == it.Key){}else{// 不处理啦。continue;}Debug.Log("处理:" + it.Key);ConfigFile csv = it.Value;string[] assets = AssetDatabase.FindAssets(it.Key, new string[] { "Assets/Art" });if (assets.Length > 0){string asset = AssetDatabase.GUIDToAssetPath(assets[0]);// 如果.meta时间和excel时间不一样,说明有变化,否则没变化的就直接跳过DateTime assetMetaTime = File.GetLastWriteTime(asset + ".meta");if (DateTime.Equals(assetMetaTime, excelTime)){// 文件没有变化,不处理Debug.Log("文件没有变化,不处理啦:" + asset);continue;}File.SetLastWriteTime(asset + ".meta", excelTime);ModelImporter modelImporter = (ModelImporter)AssetImporter.GetAtPath(asset);SerializedObject serializedObject = modelImporter == null ? null : new SerializedObject(modelImporter);ModelImporterAnimationType m_AnimationType = (ModelImporterAnimationType)serializedObject.FindProperty("m_AnimationType").intValue;bool IsHumanClip = m_AnimationType == ModelImporterAnimationType.Human;HashSet<string> processed_clip = new HashSet<string>();if (serializedObject == null){Debug.Log("无法处理该文件:" + asset);}else{// 压缩方式serializedObject.FindProperty("m_AnimationCompression").intValue = (int)ModelImporterAnimationCompression.Optimal;// 切割或者merge动画SerializedProperty m_ClipAnimations = serializedObject.FindProperty("m_ClipAnimations");for (int i = 0; i < m_ClipAnimations.arraySize; i++){SerializedProperty sp = m_ClipAnimations.GetArrayElementAtIndex(i);string clip_name = sp.FindPropertyRelative("name").stringValue;if (clip_name.Contains("@")){clip_name = clip_name.Substring(clip_name.LastIndexOf("@") + 1);}ConfigLine csv_line = csv.FindData(clip_name);if (csv_line != null){processed_clip.Add(clip_name);// merge基本属性SetPropertyFunction(IsHumanClip, csv_line, sp);// 其他属性,如event,mask,都不处理,保持原来信息}}// 把剩下没处理的内容,作为新的动画,分割到fbx中Dictionary<string, ConfigLine> csv_data = csv.GetLines();foreach (var kt in csv_data){if (processed_clip.Contains(kt.Key)){continue;}processed_clip.Add(kt.Key);ConfigLine csv_line = kt.Value;m_ClipAnimations.InsertArrayElementAtIndex(m_ClipAnimations.arraySize);SerializedProperty sp = m_ClipAnimations.GetArrayElementAtIndex(m_ClipAnimations.arraySize - 1);sp.FindPropertyRelative("name").stringValue = it.Key + "@" + kt.Key;sp.FindPropertyRelative("takeName").stringValue = "Take 001";// 处理基本属性SetPropertyFunction(IsHumanClip, csv_line, sp);// 设置maskUnityEditorInternal.AvatarMask mask = new UnityEditorInternal.AvatarMask();mask.transformCount = modelImporter.transformPaths.Length;for (int i = 0; i < modelImporter.transformPaths.Length; i++){mask.SetTransformPath(i, modelImporter.transformPaths[i]);mask.SetTransformActive(i, true);}SerializedProperty bodyMask = sp.FindPropertyRelative("bodyMask");if (bodyMask != null && bodyMask.isArray){for (int i = 0; i < mask.humanoidBodyPartCount; i++){if (i >= bodyMask.arraySize) bodyMask.InsertArrayElementAtIndex(i);bodyMask.GetArrayElementAtIndex(i).intValue = mask.GetHumanoidBodyPartActive(i) ? 1 : 0;}}SerializedProperty transformMask = sp.FindPropertyRelative("transformMask");//ModelImporter.UpdateTransformMask(mask, transformMask);Type ty = typeof(ModelImporter);MethodInfo mi = ty.GetMethod("UpdateTransformMask", BindingFlags.Static | BindingFlags.NonPublic);if (mi != null){mi.Invoke(null, new object[] { mask, transformMask });}else{Debug.LogError("无法找到此方法!");}}serializedObject.ApplyModifiedProperties();AssetDatabase.WriteImportSettingsIfDirty(asset);AssetDatabase.ImportAsset(asset);}}else{Debug.LogError("无法找到这个FBX文件:" + it.Key);}}}}static void ShowProperties(SerializedProperty sp){SerializedProperty backup = sp.Copy();{sp = backup.Copy();Debug.Log("[显示属性] sp.name=" + sp.displayName + ", " + sp.name);sp.Next(true);do{Debug.Log("[显示属性] sp.name=" + sp.displayName + ", " + sp.name);} while (sp.Next(false));}}
简单说明一下,需要攻克的问题就是,找到ModelImporter的属性信息,我是咋找到的呢,看对应的meta文件,以及...(次数省略三个字)。不过话说回来,meta文件中的信息很丰富,再加上自己写的ShowProperties函数,基本上都能猜到,有点站着说话不腰疼的感觉,哈哈。
其实,这些属性在meta中都能找到
我只能说,我设置过属性,但是不知道怎么,他并没有导出到meta中,所以就用了这段代码
serializedObject.FindProperty("m_AnimationCompression").intValue = (int)ModelImporterAnimationCompression.Optimal;
最开始,没有研究Property的时候,采用了很直接的方法,如下所示。这段代码带来的危害就是,无法进行merge旧的meta文件,会导致event和mask丢失。
[MenuItem("liubo/自测/自动切割骨骼动画")]public static void AutoClip(){string path = Selection.activeObject == null ? "" : AssetDatabase.GetAssetPath(Selection.activeObject);Debug.Log("[切割动画] path=" + path);ModelImporter mi = AssetImporter.GetAtPath(path) as ModelImporter;if (mi == null){Debug.Log("[切割动画] 没找到FBX文件! path=" + path);}else{Debug.Log("[切割动画] 开始切割:path=" + path);// 注意保留就动画的一些数据,比如绑定的eventList<ModelImporterClipAnimation> oldClips = new List<ModelImporterClipAnimation>();List<ModelImporterClipAnimation> newClips = new List<ModelImporterClipAnimation>();if (mi.clipAnimations != null){oldClips.AddRange(mi.clipAnimations);}#if true//TEST//AnimationUtility.GetAnimationClipSettings(null);ModelImporterClipAnimation test = new ModelImporterClipAnimation();test.name = "test";test.takeName = "auto";test.firstFrame = 0;test.lastFrame = 30;test.wrapMode = WrapMode.Default;test.keepOriginalPositionY = true;test.loop = false;test.loopTime = false;test.loopPose = false;test.keepOriginalOrientation = false;test.keepOriginalPositionY = false;test.keepOriginalPositionXZ = false;test.cycleOffset = 0;test.heightOffset = 0;test.rotationOffset = 0;newClips.Add(test);newClips.AddRange(oldClips);#endifmi.clipAnimations = newClips.ToArray();EditorUtility.SetDirty(Selection.activeObject);}}
unity工程链接:https://github.com/badforlabor/test2 中的auto_split_animation
标签:
原文地址:http://www.cnblogs.com/badforlabor/p/5458814.html