I can’t figure out why these values refuse to serialize. I know Unity doesn’t like abstract/polymorphism on custom editors but I think its pretty clear that the values are getting properly assigned and just failing to serialize. What is the correct way to get an editor for a derived class to serialize it’s base class variables correctly?
I’ve used abstract classes fine without custom editors, so its something going wonky with the custom editor.
Editor Script:
[CustomEditor(typeof(BaseSubject), true)]
public class EPlayerAvatar : Editor
{
protected PlayerAvatar X;
public virtual void OnEnable()
{
X = (PlayerAvatar) target;
}
public override void OnInspectorGUI()
{
serializedObject.Update();
for (int i = 0; i < X.Stats.Length; i++) X.Stats[i].Base =
EditorGUILayout.FloatField(X.Stats[i].Base, GUILayout.MaxWidth(45));
serializedObject.ApplyModifiedProperties();
}
}
Derived class:
public class PlayerAvatar : BaseSubject,
{
// ....
Abstract baseclass:
[Serializable]
public abstract class BaseSubject : MonoBehaviour, IUseStats
{
public Stat[] Stats;
// ...
The value refuses to serialize. It will change correctly until a recompile/restart, then revert.
SerializedObject.Update copies the state of the actual object (X in your case) to the SerializedObject.
SerializedObject.ApplyModifiedProperties copies the state of the SerializedObject to the actual object.
So your inspector code is saying:
Copy state of real object to serialized object
Draw individual float fields for elements of ‘Stats’ array, writing the values into the real object
Copy the state of the serialized object to the real object (effectively overwriting what you just did in previous step… only if the state actually changed I think, there’s weird unknown logic to when it does or doesn’t actually do it)
Honestly, I wish the documentation for both Update and ApplyModifiedProperties was more detailed. ApplyModifiedProperties literally says “Apply property modifications.” for its description on unity api… oh really? Is that what it does? I thought it might “Revert property modifications”, who’d a thunk.
You should be doing something along the lines of:
[CustomEditor(typeof(BaseSubject), true)]
public class EPlayerAvatar : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
var statsProp = this.serializedObject.FindProperty("Stats");
for(int i = 0; i < statsProp.arraySize; i++)
{
var baseProp = statsProp.GetArrayElementAtIndex(i).FindPropertyRelative("Base");
baseProp.floatValue = EditorGUILayout.FloatField(baseProp.floatValue, GUILayout.MaxWidth(45));
}
serializedObject.ApplyModifiedProperties();
}
}
(note - untested code… I wrote this in place in the browser, could have typos)
But yeah, basically you should be modifying the SerializedProperties, not the object directly.
Technically you can the other way around. Then calling Update should work. I do it in some places where I reeeeaallly need to. But honestly, it’s hoaky at times. Since unity isn’t straight forward about the documentation of these methods, it doesn’t always work. So I usually only use the ‘target’ property to ‘read’ and or ‘call functions’ but seldom to ‘write’.
I did not have a [Serializable] attribute on the custom class which, I suppose, confuses the serializer? I did finally figure it out and make everything work smoothly. It ends up looking like this:
So, pretty straightforward after I got a handle on the whole difference between SerializeableObject, SerializeableProperty and the need for [Serializeable] attributes on custom classes which was generally pretty confusing since I had always used EditorGUILayout stuff and never cared about undo.
The only thing I really dislike is having to find properties by name, as it doesn’t link up for refactoring.
What I usually do is put constants at the top of my editor scripts for the various properties. So that way it’s only one place to change said names, no matter how often it’s used in the editor script itself.
Can’t you simply avoid ever using SerializedObject alltogether?
Just from the top of my head, I believe I’ve done something like this before for custom editors:
Simply use the target variable and cast it to edited type (the X property in your case)
Modify properties on that variable
After modifications, use EditorUtility.SetDirty(X)
Save
Your serialized fields should now properly persist and you don’t have to deal with accessing properties by name
Using SerializedObject provides several advantages and is the way that Unity is pushing developers to go. It automatically handles undo without having to remember to call EditorUtility.SetDirty(), and you can use EditorGUI.PropertyField to automatically use custom drawers and attributes such as [Tooltip()], [Range()], [Header()], etc.
FYI you can effectively use nameof() function. example: var myProperty = serializedObject.FindProperty(nameof(MyTypeName.MyPropertyName));
it perfectly works with refactoring
I believe this thread (slightly) predates being able to use nameof() in Unity, at least outside of early Beta releases with experimental functionality, and the fields in question when using SerializedObject tend to be private, so nameof() doesn’t work anyways from outside of the class. That’s also a reason many people don’t use the casting approach instead of the SerializedObject approach (though the quoted reason here is utility)- public fields are rather icky.
But yeah, worth noting in case people happen upon it now and have public fields, I suppose, though I’d probably just cast it in that case.
Yep, as Lysander said, my post predates nameof’s availability in Unity (outside of beta).
I actually still can’t access it because the project I’m working is still .net 3.5, we haven’t been able to move to the newer .net support yet for our main project.
Also, there’s an issue with if the member is ‘private/protected’. You can’t access the member to call nameof on it if the member is not accessible from the editor script.
[Serializable] private int myVar;
#if UNITY_EDITOR
public static readonly string myVarName => nameof(myVar);
#endif
Which is compiler-checked and works with refactoring, but… eh.
An alternative is to have the field be internal, and mark the editor assembly as having access to internals through an AssemblyInfo file, but then the field isn’t hidden from other classes in the same assembly, which is icky as well.