objectReferenceValue in SerializedProperty

I am trying to make a PropertyDrawer for a custom type of mine where the user can select from a dropdown which sets the property as one of a few predefined static instances of this custom type.

Boiled down version of the custom type and drawer :

[Serializable]
public class LifebarType : SomeType
{
    static public readonly LifebarType TINY   = new LifebarType();
    static public readonly LifebarType SMALL  = new LifebarType();
    static public readonly LifebarType MEDIUM = new LifebarType();

    //...
}

[CustomPropertyDrawer (typeof (LifebarType))]
public class LifebarTypeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label)
    {
        LifebarType defaultValue = LifebarType.TINY;
        if(prop.objectReferenceValue == null)
        {
            prop.objectReferenceValue = LifebarType.TINY;
        }

        Rect contentPos = EditorGUI.PrefixLabel(pos, label);
        int  index      = GetIndexOfType(LifebarType)prop.objectReferenceValue);
        int  guiValue   = EditorGUI.Popup(contentPos, index, optionNames);
        prop.objectReferenceValue = GetLifebarTypeFromIndex(guiValue);
    }
}

My main problem is that I can’t set objectReferenceValue to anything. It always turns up null no matter what I try to set to it. The PopUp and everything else works fine. Has anybody found a way to do this?

use prop.serializedObject.ApplyModifiedProperties () at the end
to save changes

I would try using prop.serializedObject.ApplyModifiedProperties() to save changes, but that doesn’t account for all situations.

I found a different problem when trying to assign objectReferenceValue. I’ll explain, and name the objects I have to refer to later in parenthesis.

I was trying to assign a value (“owned object”) to a property (“owned property”) belonging to an object (“owner”) that was saved as an asset (“owner.asset”).

I found I could not assign the value unless “owned object” was added as a subasset of “owner.asset”, or added as a subasset to a scene asset. This is an early test, but it makes some sense - if the object exists nowhere then the reference would be broken immediately after assigning it.

This is also interesting because it would mean that Unity is normally automagically adding all Serialize-d objects to the scene asset.

It could be that since your object references don’t exist within an asset or a scene asset that they cannot be assigned using SerializedProperty.

A second possibility, but one I don’t have time to verify:

It could be that your objects don’t extend ScriptableObject. I’d suggest making your objects extend ScriptableObject and seeing if that helps.

I think either way you will have to use an indirect method to get your reference. If your LifebarType was saved in an asset, and your LifebarType constants were created at every edit time, you could end up having a ton of them made. Maybe store an integer that you can use to reference them?

From learning a bunch more on Unity’s serialization, it would seem that objectReferenceValue is specific to objects like a GameObject. Basically, Unity types (GameObjects, Assets, Sounds) that you can fetch using the EditorGUILayout.ObjectField (which seems to have been obsoleted w/ Unity 5).

It cannot take any C# object’s instance, Serializable or not. Inheriting ScriptableObject doesn’t seem to help in this regard either.

mintman’s answer is exactly correct, but it was hard for me to understand the reason for that until I dug much deeper, so I thought I would share some more information.

I was facing a similar problem where creating a ScriptableObject value and assigning it to the objectReferenceValue via an editor script was working great if the object that the script holding the ScriptableObject was a scene object, but would always fail and leave the objectReferenceValue as null if the object I was editing was a prefab in the project view, meaning it was an asset object

After digging deep into the subject and cracking my head for 5 hours straight until coming upon this answer, I realized the reasoning behind this:
When I edit a scene object and create a value to serialize on a script in it, I need to initialize an object in memory, which is held by the object and maintained by the scene, therefore, it exists in the scope of the scene’s serialization.
However, when doing the same for a prefab, which sits in the project-wide scope, outside of your scene scope, that would mean that the data you’re initializing must exist in the project-wide scope, or atleast be known by the prefab itself, though being known by the project scope would mean that the object you created must be an asset, or it would just be lost by garbage collection, and therefore unassignable as a serialized persistent value.
Setting the object as a sub-asset to the asset we’re assigning it to, makes it so that the memory is saved into the prefab asset just like earlier it was saved to the scene asset, and therefore, as long as the prefab exists, it will know of the reference of the object, and therefore be persistent within the scope of the prefab.

To demonstrate, this is the code that I used:

               MessageList messageList = i_MessageListProperty.objectReferenceValue as MessageList;
                
                if(messageList == null)
                {
                    messageList = MessageList.CreateInstance<MessageList>();

                    //When we try to create a new message list on an object that isn't a scene object (A prefab for example), the message list that's created is made a floating object, which eventually
                    //gets garbage collected, therefore, we can't assign a reference to it in a serialized object that needs to persist in a project-wide scope.
                    //Though we don't want to create an asset(That WILL be persisted project-wide) for every single message list we require.
                    //The solution is to define this new list as a SUB-asset of the prefab that it sits on top of. That way, the list exists in the prefab's scope so it can be assigned.
                    if (PrefabUtility.GetPrefabType(i_MessageListProperty.serializedObject.targetObject) == PrefabType.Prefab)
                    {
                        AssetDatabase.AddObjectToAsset(messageList, i_MessageListProperty.serializedObject.targetObject);
                    }
                }

It would’ve been nice if assigning to the objectReferenceValue would throw an exception instead of just remaining null.
Would’ve saved a lot of time.

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 :slight_smile:

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.