Hi,
So I’ve been working on a way to edit scriptable objects with a custom editor window and the editor worked perfectly until ReorderableLists were introduced and used as the default for all list serialization. Now any contained data with a list, array, or any data structure that utilizes ReorderableList can’t be modified or saved.
Everything apart of Reorderable list like changing the count max and reordering entries works perfectly fine while the contained data refuses to be modified in anyway.
Been struggling to find a solution for a while so figured I’d ask here.
Done a lot of research of the topic and haven’t found a solution. At one point tried working on a custom drawer for displaying lists but that ended up as a dead end. Read a lot about how ReorderableLists have to be serialized in a certain way but the design for this editor is to make it as dynamic as possible so the user doesn’t have to change anything about what they’ve done in order to access their data.
Extra Info:
This is a custom editor window so the currently active scriptable is set by the user during OnGUI() and the SerializedProperties are displayed after a ScriptableObject is selected and turned into a SerializedObject.
This has nothing to do with applying changes to the property, even setting the object as dirty and saving it in that manner has no affect or tracking changes and applying them at the end.
Changes are not tracked from any properties contained within another property as long as it’s data container is some derivative of ReorderableList
This is evident by the fact all properties are correctly saved upon dirty or applying modified properties as well as the variable of the list itself saving entry or index changes. Only the data stored within each entry is unable to be changed.
Yes each element within the struct/class contained within the list has the Serializable or SerializedField attribute, doesn’t change anything as noted by the fact that every other field is fine.
Removing removing reorderable using attributes works but isn’t the intended purpose of the custom editor.
Solutions like this don’t work as the object is selected by the user and known during OnGUI()
Ah, you’ve reminded me to add this to the notes, sorry about that. Sadly this has nothing to do with the issue as I’ve stated it’s only lists. It’s also a known issue with ReorderableLists and serializedproperties and was in a few issue trackers on the official bug tracker but solutions have been implemented to fix the issue but I’m currently unsure to what those issues are as the issue trackers don’t state the actual cause or fix.
Commented on 2 years ago, ongoing issue, marked as fixed but no solution stated.
For what i can see in your example, you are not directly using ReorderableList right? but instead you are just using EditorGUI.PropertyField on a serialized List field;
I tried to replicate but works fine for me. What version are you on?
this is the minimal scenario that works for me on Unity 6000.0.17f1:
Scriptable:
[CreateAssetMenu]
public class SomeScriptableTest : ScriptableObject
{
[SerializeField]
private List<ScriptableObject> _scriptables;
}
Window:
public class TestWindow : EditorWindow
{
private SomeScriptableTest _scriptable;
private SerializedObject _serialized;
private void OnEnable()
{
if (_scriptable) { _serialized = new(_scriptable); }
}
private void OnDisable()
{
_serialized?.Dispose();
_serialized = null;
}
public void OnGUI()
{
if (!_scriptable)
return;
_serialized.UpdateIfRequiredOrScript();
using var iterator = _serialized.GetIterator();
if (iterator.NextVisible(true))
{
do
EditorGUILayout.PropertyField(iterator, true);
while (iterator.NextVisible(false));
}
_serialized.ApplyModifiedProperties();
}
[OnOpenAsset]
public static bool OpenTest(int instanceId)
{
if (EditorUtility.InstanceIDToObject(instanceId) is SomeScriptableTest test)
{
var window = GetWindow<TestWindow>();
window._serialized?.Dispose();
window._scriptable = test;
window._serialized = new(test);
return true;
}
return false;
}
}
The window is allowing me to both reorder & change the references
PD: i see you commented here for 2021.2.9, i’ll try the latest 2021.3.43f1 to check
Tested across most LTS from 2019-2022 (2019 was the developed ver and then 2022 is the cur ver, upgraded over the years)
That’s beyond fucking confusing, let me give something a try and I’ll get back to you, it could have something to do with the way I’m iterating through the properties.
Still doesnt reproduce for me on 2021.3.43f1 nor 2022.3.1f1. I will encourage you to try the minimal example above, to see if this is a problem generated by “something” in your project.
If the example doesnt work, then i can only guess some package/custom code is interfering. But it would be very weird that it only breaks ReorderableList (maybe try updating to the last 2022, but it’s weird).
If the example works fine on your project, then it is likely something else going on in your code. My guesses would be
Some code on any OnValidate()
Some other code called on your Scriptable from your Window
Some other SerializedObject is interfering with the behaviour of your window (maybe a custom EditorWindow that is also opened while you edit, or some other code that generates another instance of a SerializedObject for the same scriptable you are editing)
Ah fuck. Yea it’s working perfectly fine as long as it’s a created instance of a ScriptableObject. I’m going to test if it’s the same for a physical scriptable and see if the result is the same. Else time to breakpoint everything
using UnityEngine;
using UnityEditor;
public class TestObjectEditor : EditorWindow
{
public static TestObjectEditor window;
// Selected Scriptable Object for Inspector.
[SerializeField] protected SerializedObject serializedObject;
[SerializeField] protected SerializedProperty serializedProperty;
// Selected Object Checks before serialization.
protected ScriptableObject test;
protected Object selectedObject;
[MenuItem("SOE/Test Object Editor")]
protected static void ShowWindow()
{
window = GetWindow<TestObjectEditor>("Scriptables Editor");
}
private void OnEnable()
{
test = Resources.Load<ScriptableObject>("Bug");
selectedObject = test;
serializedObject = new SerializedObject(selectedObject);
}
private void OnGUI()
{
serializedProperty = serializedObject.GetIterator();
serializedProperty.NextVisible(true);
DrawProperties(serializedProperty);
serializedObject.ApplyModifiedProperties();
}
protected void DrawProperties(SerializedProperty property)
{
if (property.displayName == "Script") { GUI.enabled = false; }
EditorGUILayout.PropertyField(property, true);
GUI.enabled = true;
EditorGUILayout.Space(40);
while (property.NextVisible(false))
{
EditorGUILayout.PropertyField(property, true);
}
}
}
If your ScriptableObject gets changed from somewhere else than your window (from another system like Inspector or else), your SerializedObject instance wont be aware of this changes until it calls Update(). this means that every time you “ApplyModifiedProperties()”, you would be overriding the changes from the other call (if .Update() is not used).
It doesnt directly interfere if you are only editing from 1 place, using a single SerializedObject instance. But it is good practice to Update anyways.
Imagine you have some other Tool that is also editing your Scriptable. If that tool is not calling Update, it would make sense that from your Window you couldnt change anything, because that other Tool is constantly applying its own SerializedObject modifications, oblivious to your window modifications.
If you always call Update before using your SerializedObject (at the beggining of your GUI, on every custom system you implement for this), you at least ensure that you always have the latest serialized version.
Oh interesting, I was handling this myself by ensuring that every time the list of objects wasn’t the same as the asset database, it would refresh. Good to know that’s unessacary with this, I can scrap it completely since it’s just a performance drain.
Will report back tomorrow once I’ve reconstructed my editor piece by piece till I find the culprit. Thank you so much for the help and especially with the realization I’ve created the bug somewhere.
Ok I’m so mad, thanks for the help btw. I’ve discovered what was causing it.
Line 15 was the cause as SerializedObject was being recreated every frame removing any ModifiedProperty changes that occurred to data that wasn’t directly on the object being edited (In other words, any data structure that has it’s own serialization properties)
protected void DrawScriptables(ScriptableObject[] objects)
{
foreach (ScriptableObject item in objects)
{
if (GUILayout.Button(ShortenString(item.name), GUILayout.ExpandWidth(true)))
{
selectedObject = item;
}
}
switch (true)
{
case bool _ when selectedObject != null:
serializedObject = new SerializedObject(selectedObject);
break;
}
}
I’ve swapped it to this and it works perfectly fine, I can’t remember the reason I coded it like this originally but I think there was some edge case that will pop up again
protected void DrawScriptables(ScriptableObject[] objects)
{
foreach (ScriptableObject item in objects)
{
if (GUILayout.Button(ShortenString(item.name), GUILayout.ExpandWidth(true)))
{
selectedObject = item;
serializedObject = new SerializedObject(selectedObject);
}
}
}