Hi,
I have written a wizard to set the root transform settings for muscle clips on multiple model assets, since the AnimationClipEditor embedded in the ModelImporter does not support multiselection. It works, but as soon as the clipAnimations array is set, all clips lose their events. I presume that is caused by the clips being passed by copy but I have not found any event setters at that ModelImport level being exposed in the API. It seems to be handled by AnimationClipInfoProperties, internally.
I have worked around the issue with a nasty hack involving reading the event properties from the meta file, making the changes, reinserting the lost events to the clips straight on the meta file and re-importing. I have been wondering, have I missed some function somewhere, have I just hit the need for functionality that has not been made public yet or is the lost event association a bug I should report?
Interestingly, events are also lost after using the Copy From Other Mask option in the importer, which I also solved the same way as above.
Thanks!
I’m running into this right now too. You wouldn’t be willing to share your hack, would you?
Hey, glad to see I’m not the only one then!
Unfortunately, while I can tell you what to do, I cannot share the code itself as it exists right now, as it is company property.
First of all, you need to be using textual format meta files, unless you fancy writing a parser for the binary YAML format, which I doubt. From there on, you will see that events and curves are clearly visible in every clip’s section.
The proper way to extract this information would be to use a full YAML parser but since I did not have the time for that, I did this:
o Read the original meta file.
o For every instance of “curves:”, save everything between “curves:” and “transformMask:”
o Make any changes you need to make to clipAnimations.
o AssetDatabase.WriteImportSettingsIfDirty()
o Read the new meta file.
o For every instance of “curves:”, replace everything between “curves:” and “transformMask:” with the saved data.
o Write it.
o AssetDatabase.Import()
Now, remember that this is a hack that goes well behind Unity’s back, should the serialization order changes in some future version of Unity, this could very well break. Let’s hope the API to write this stuff becomes public at some point…
Right, I’ve found a better way of doing this and I can actually share it, since it is based on Unity’s AnimationClipInfoProperties private class implementation, which you can browse in the Assembly Browser.
Also posted it on Answers.
ModelImporter modelImporter = (ModelImporter) AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(TargetObject));
SerializedObject so = new SerializedObject(modelImporter);
SerializedProperty clips = so.FindProperty("m_ClipAnimations");
List<AnimationEvent[]> animationEvents = new List<AnimationEvent[]>(modelImporter.clipAnimations.Length);
for (int i = 0; i < modelImporter.clipAnimations.Length; i++)
{
animationEvents.Add(GetEvents(clips.GetArrayElementAtIndex (i)));
}
// Make your changes and write them by setting clipAnimations, destroying the events.
modelImporter.clipAnimations = clipAnimations;
for (int i = 0; i < modelImporter.clipAnimations.Length; i++)
{
SetEvents(clips.GetArrayElementAtIndex(i), animationEvents[i]);
}
so.ApplyModifiedProperties();
public AnimationEvent[] GetEvents (SerializedProperty sp)
{
SerializedProperty serializedProperty = sp.FindPropertyRelative("events");
AnimationEvent[] array = null;
if (serializedProperty != null serializedProperty.isArray)
{
int count = serializedProperty.arraySize;
array = new AnimationEvent[count];
for (int i = 0; i < count; i++)
{
AnimationEvent animationEvent = new AnimationEvent();
SerializedProperty eventProperty = serializedProperty.GetArrayElementAtIndex (i);
animationEvent.floatParameter = eventProperty.FindPropertyRelative ("floatParameter").floatValue;
animationEvent.functionName = eventProperty.FindPropertyRelative ("functionName").stringValue;
animationEvent.intParameter = eventProperty.FindPropertyRelative ("intParameter").intValue;
animationEvent.objectReferenceParameter = eventProperty.FindPropertyRelative ("objectReferenceParameter").objectReferenceValue;
animationEvent.stringParameter = eventProperty.FindPropertyRelative ("data").stringValue;
animationEvent.time = eventProperty.FindPropertyRelative ("time").floatValue;
array [i] = animationEvent;
}
}
return array;
}
public void SetEvents (SerializedProperty sp, AnimationEvent[] newEvents)
{
SerializedProperty serializedProperty = sp.FindPropertyRelative("events");
if (serializedProperty != null serializedProperty.isArray newEvents != null newEvents.Length > 0)
{
serializedProperty.ClearArray ();
for (int i = 0; i < newEvents.Length; i++)
{
AnimationEvent animationEvent = newEvents [i];
serializedProperty.InsertArrayElementAtIndex (serializedProperty.arraySize);
SerializedProperty eventProperty = serializedProperty.GetArrayElementAtIndex (i);
eventProperty.FindPropertyRelative ("floatParameter").floatValue = animationEvent.floatParameter;
eventProperty.FindPropertyRelative ("functionName").stringValue = animationEvent.functionName;
eventProperty.FindPropertyRelative ("intParameter").intValue = animationEvent.intParameter;
eventProperty.FindPropertyRelative ("objectReferenceParameter").objectReferenceValue = animationEvent.objectReferenceParameter;
eventProperty.FindPropertyRelative ("data").stringValue = animationEvent.stringParameter;
eventProperty.FindPropertyRelative ("time").floatValue = animationEvent.time;
}
}
}
Thanks for that. I’m going to see if i can factor that into our model importer and ill let you now how i get on.