Recently, I had a headache fixing a bug with a scriptable object which I used as a database. I added a new entry to the “database” through code, and it was reflected in the inspector, but when I reopened the project, the new entry just disappeared.
Let’s imagine we have the following ScriptableObject:
{
public string Field;
}```
And a MonoBehaviour that changes the scriptable object:
```public class TestBehaviour : MonoBehaviour
{
[SerializeField] private TestSO TestObject;
public void ChangeField()
{
TestObject.Field = "test";
}
}```
When I execute ChangeField(), I can see "test" in the TestSO inspector. But when I save the project and open the asset in a text editor, the field is still empty:
[quote]
Field:
[/quote]
Of course, if I re-open the project, the value will disappear from the inspector as well.
The use of *EditorUtility.SetDirty()* after a value assignment forces the object to save the change to disk:
```public void ChangeFieldAndSetDirty()
{
TestObject.Field = "testDirty";
EditorUtility.SetDirty(TestObject);
}
After saving the project, I can see the new value in the .asset file:
But it is a PITA to call it every time I want to change a scriptable object. Also, EditorUtility is located in the UnityEditor namespace and will not work outside of the editor.
One can find it a useful feature - being able to change a scriptable object without saving the changes to disk, but that’s what ScriptableObject.CreateInstance() is for. You can create an instance without saving it to disk and operate on it at runtime. When I reference a ScriptableObject saved as an asset, I want the changes to be saved to disk.
I checked this behavior in 2020.2.0b5 and 2019.4.12f1 - it is present in both versions. I am confused about whether it was always a case and I just never noticed it. Is it a bug that appeared recently or an old annoying ‘feature’?
Outside of the editor, ScriptableObject changes can never be saved back to disk at all, so there’s no reason for non-UnityEditor code to be able to do so, as it will just cause confusion and frustration.
Okay, I understand the “outside of editor” part. But is it reasonable to expect the changes to ScriptableObject inside the editor to be saved to disk without EditorUtility.SetDirty() ? Especially considering that the changes are reflected in the inspector, but they disappear after the project reload. In the case of MonoBehaviour, we can clearly see how values are reverted back after exiting Play Mode. But for ScriptableObjects, nothing indicates that the changes will not be saved. There is also no ‘*’ sign to indicate whether the asset is dirty or not.
No I don’t think so. This would create a situation where you have vastly different behavior in Editor Play Mode and a built game. That would be a nightmare.
Actually, changes to SO public (or serialized) fields are indeed written to disk, but for some unfathomable reason, Unity refuses to call fflush() and actually write the changes to disk until you a) exit Unity, or b) do Project-Save or Save Scene, at which point it coincidentally flushes all pending changes to disk.
This one issue can cause MASSIVE confusion when using source control (“How come the file didn’t change!?”) as well as cause MASSIVE losses of data when Unity crashes and you haven’t done a save-project. It’s one of the most maddening things about Unity, but it’s been that way forever and they seem immune to fixing it, despite how instantly fast it could be for them to purge the data to disk when it changes and you are NOT in play mode on a modern 2020-era PC.
As I tested, Undo.RecordObject() doesn’t work with ScriptableObjects when they are changed directly, not via a SerializedObject. I tried the following method with the same setup as in the first message, and the change was not saved to disk:
{
Undo.RecordObject(TestObject, "test");
TestObject.Field = "testUndo";
}```
From the SetDirty documentation:
[quote]
The only remaining use (which is used rarely) happens if a non-scene object is modified with other means and with no added undo entry.
[/quote]
The undo entry is added but doesn't work for some reason.
I’ve seen similar behaviour in 2020.2 - Undo.RecordObject() only seems to enable “Save” to actually flush to disk if an Undo operation is performed (i.e. ctrl+z) prior to saving. If no Undo has occurred then nothing is written to disk even if the SO has been modified. If a successful save is completed, and the SO is changed again, then a Save doesn’t work until another Undo has been performed.
I’m trying to use Undo.RecordObject on a SO but it’s not working right for me in 2022.3.18. Neither is SetDirty. It’s like they appear to work, values change, and seem to stick after I exit play mode, but then after the next time I’m in play mode they revert back to older values from before the first time I hit play. Very weird, I wish SO’s just had some equivalent to “copy component values” so I could just force them back to what I wanted.