The above answers REALLY narrowed down WHY objectReferenceValue
was not being maintained/saved.
My issue, however, was that if I ran my script with the IDE open, it worked fine. But when I ran it in batchmode / headless via a command line, setting the motion property for a state just wouldn’t work.
So, even though the class I’m using extends ScriptableObject
, the value I was setting with objectReferenceValue
only worked after I created a prefab instance (which is added to the scene btw). My finding is that having it instantiated as the prefab / GameObject is the magic, which is was basically eluded to above.
Also worth noting: I could not simply do:
state.motion = clip
I had to get the state’s reference, cast as a ScriptableObject
and set the “m_Motion” property via objectReferenceValue
for it to work properly:
// Create our prefab
bool created = false;
// creating the prefab adds the new GameObject to the scene
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(g, localPath, out created);
// VERY IMPORTANT NOTE:
// This next section assigns clips to the states "motion" property. This can only successfully happen after the prefab is created, and thus added to the scene.
// THEN, you have to mimic a UI interaction by casting the state as a SerializableObject, get the property `m_Motion`, assign the clip and apply the changes.
// At this point, the clips are preserved in the motion value for the state and you can destroy the prefab from the scene.
var assetRepresentationsAtPath = AssetDatabase.LoadAllAssetRepresentationsAtPath(Path.Combine(path, fbx.name + ".fbx"));
// Associate the AnimationClips with their animation states
Dictionary<string, AnimatorState> aStates = new Dictionary<string, AnimatorState>();
AnimatorControllerLayer layer = controller.layers[0];
foreach (ChildAnimatorState s in layer.stateMachine.states)
{
aStates.Add(s.state.name, s.state);
}
Debug.Log($"CreatePrefab checks - assetRepresentationsAtPath: {assetRepresentationsAtPath.Length}, aStates.Count: {aStates.Count} ");
foreach (var assetRepresentation in assetRepresentationsAtPath)
{
var clip = assetRepresentation as AnimationClip;
if (clip != null)
{
AnimatorState state;
if (aStates.TryGetValue(clip.name, out state))
{
SerializedObject stateObj = new SerializedObject(state);
SerializedProperty motionProperty = stateObj.FindProperty("m_Motion");
motionProperty.objectReferenceValue = clip;
stateObj.ApplyModifiedProperties();
}
}
}
DestroyImmediate(g);
Also worth noting, that if you’re curious about the available properties you can set on a ScriptableObject
, you can do so with:
SerializedObject stateObj = new SerializedObject(state);
SerializedProperty prop = stateObj.GetIterator();
if (prop.NextVisible(true))
{
do
{
Debug.Log($" - prop: {prop.name}, type: {prop.propertyType}");
} while (prop.NextVisible(false));
}
Happy hunting
UPDATE:
If you happen to be importing multiple FBX / 3D files, and are creating an AnimatorController file where you need to assign motion clips like above, do it AFTER all of the assets have finished importing. If you don’t, there are timing issues with the what I’m guessing is the AssetDatabase. I’m now able to process mutiple FBX files, create the AnimatorController for each and assign all clips to their state.motion values and its working 100% of the time so far.