自动切割FBX中的动画
最近写了一个有意思的工具,能够自动切割fbx中的动画信息,如下图所示:
最近写了一个有意思的工具,能够自动切割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, LoopPose
sp.FindPropertyRelative("loopTime").boolValue = csv_line.GetColumnUnsafe<int>("loopTime") > 0;
sp.FindPropertyRelative("loopBlend").boolValue = csv_line.GetColumnUnsafe<int>("loopBlend") > 0;
// Rotation_BakeIntoPose, Rotation_BaseUpon
sp.FindPropertyRelative("loopBlendOrientation").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendOrientation") > 0;
sp.FindPropertyRelative("keepOriginalOrientation").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalOrientation") == 0;
// PositionY_BakeIntoPose, PositionY_BaseUpon
sp.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_BaseUpon
sp.FindPropertyRelative("loopBlendPositionXZ").boolValue = csv_line.GetColumnUnsafe<int>("loopBlendPositionXZ") > 0;
sp.FindPropertyRelative("keepOriginalPositionXZ").boolValue = csv_line.GetColumnUnsafe<int>("keepOriginalPositionXZ") == 0;
return 0;
};
// 读取excel
string[] 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);
// 设置mask
UnityEditorInternal.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中都能找到
- name
- firstFrame
- lastFrame
- loopTime
- loopBlend
- loopBlendOrientation
- keepOriginalOrientation
- loopBlendPositionY
- keepOriginalPositionY
- loopBlendPositionXZ
- keepOriginalPositionXZ
- bodyMask
- transformMask
- event
有人可能会问ModelImporter已经包含了很多属性,比如animationCompression,为什么不直接设置,非得用修改Property的形式呢?
我只能说,我设置过属性,但是不知道怎么,他并没有导出到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);
// 注意保留就动画的一些数据,比如绑定的event
List<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);
#endif
mi.clipAnimations = newClips.ToArray();
EditorUtility.SetDirty(Selection.activeObject);
}
}
链接
unity工程链接:https://github.com/badforlabor/test2 中的auto_split_animation
时间: 2024-11-07 03:57:30