ScriptableObject - is it supposed to save its state or isn't it?

Ok I try to sum up what my setup is. First I create some assets of a custom class with my editor script and set some variables in the asset that appears in my project folder.

Quest quest = ScriptableObject.CreateInstance<Quest>();
AssetDatabase.CreateAsset(quest, "Assets/New Quest.asset");
AssetDatabase.SaveAssets();

After I finished configuring my asset I assign it to a trigger. I press play, go to the trigger, do some stuff with the quest and change some of its variables in game. When I end the game, I can see everything I’ve changed in the asset file of my class instance. If I hit play again everything’s exactly as I left it in my last session. As soon as I restart my editor, everything’s resetted to the default values I set in the editor at the creation of my custom asset.

So my question is: are ScriptableObjects supposed to save their values? If so, is it possible to disable this feature? I don’t need everything I change in game to be stored for the player and I don’t want to reset the whole asset file after every single test run I did in the editor.

Ok just to let you know, adding [System.NonSerialized] to everything I don’t want to change solved my issue. The strange thing is that even private variables were saved between the game sessions and cleared with a restart of the editor. I think that’s quite confusing as it neither saves nor deletes the data between the game sessions properly, but ok I can live with it. If you have for example some variables you want to set in the editor but don’t want to store their state throughout your game tests it could be a serious issue as with [System.NonSerialized] you cannot change it with the editor either.

The question is whether serialized ScriptableObjects are supposed to store their data or not, but right now it’s just inconsistant and not very comfortable to use. The idea behind the system in general is great however.

Yes, exactly my issue. I need to edit a SciptableObject asset in the editor and be able to save it. I also need to change it in runtime, but I only want the asset to change permanently in the editor.

Is it somehow possible to mark the asset not to be saved in runtime?

~ce

Asset changes are always permanent - regardless of whether they stem from editor scripts or play mode interaction. Only the scene is reset on play mode exit.

If you want to have an instance of your asset to modify runtime, but have the change not be permanent, you should instantiate it at runtime and only interact with the instance.

1 Like

I don’t think that’s correct AngryAnt. I have a ScriptableObject asset. If I edit it’s variables while in play mode, on exit the variables are changed, however, if I exit/reopen Unity the changes revert back to their original value.

Now, it’s possible to save the changes made in play mode by calling SetDirty(object) after you exit play mode, and then the changes are indeed permanent. But if you don’t do this and then exit the Unity editor, the changes are not permanent, and since in an actual game you can’t call SetDirty, there’s no way to make permanent changes to an asset during runtime.

Well, actually there does seem to be a way, as changes to TerrainData and Texture2D assets are both permanent during run time. But as far as I can tell, there’s no way to extend that behavior to other assets such as custom classes.

Or maybe I’m missing something. Please enlighten me, as I would love to save game states in scriptable objects!

2 Likes

I am also having a similar issue.

I want to save to game settings to a ScriptableObject, for example keybindings, camera settings, general settings etc.

I was hoping to be able to change the values in the editor, as well as by the players at runtime. Unfortunately two issues have arisen, firstly the changes don’t serialize between plays, setdirty can not be used at runtime. Secondly I was hoping to also duplicate the assets for each user so their profile gets saved to a second asset, which allows be to revert changes back to default if needed and multiple profiles for each user on a single computer, but you can’t create assets at runtime either.

Any suggestions?

It turns out I misspoke about the TerrainData and Texture2D classes. They inherit from Object, not ScriptableObject.

However, they do exhibit the same characteristics as ScriptableObjects. Changes made to them from code during Play Mode persist in the editor, but in a standalone build changes will not be permanent between game sessions.

So it turns out there are no objects that maintain run-time changes between play sessions.

To answer your question, Scriptable Objects cannot be used to store settings in that manner. You will just have to use some kind of serialization system.

Changed, but not saved.

If you do File > Save Project, those changes will be saved.

Also, Unity usually save on exit. However, if it crashes, it won’t save anything.

As for permanence, outside the editor, you cannot edit any asset. You’ll need to serialize your object differently (JSon, XML, Binary, etc.).

Necroing this post since it is still relevant, with current information:

  1. This bug still appears in Unity, where fields (I tested with ints), no matter if they are internal or protected or private, are maintained coming out of and back into play mode for scriptable objects.

  2. If I close/reopen the editor, the values are back to their correct default values.

  3. If I change the values in play mode and save the project, the non-public values are NOT saved into the scriptable object, even if I change a public value on the same object: Only the public value gets serialized to disk.

  4. Marking the non-public fields [System.NonSerialized] restores the correct behaviour: They are reset to their default values each time play mode is entered.

  5. MonoBehaviours do not display this behaviour: Even without [System.NonSerialized], non-public fields are correctly reset when entering play mode each time. Only ScriptableObjects display this bug.

Are you creating an instance/copy of the ScriptableObject or editing the original?

Same thing with prefabs, materials, etc. You can change serialized values on prefabs and get the changes to stay when you save the project. If you don’t want to change the original, then make a copy at runtime.

I am editing the original ScriptableObject, and what you said would make sense if there were not this thing with [System.NonSerialized]. However, because I can add the [System.NonSerialized] attribute to the field, and change nothing else, and suddenly now it is being reset when before it was not, it is not as simple a situation as you claim.

Is it going back to it’s default value each time? [System.NonSerialized] will cause Unity to not serialize it, so Unity shouldn’t save any kind of data for that field but use the default value instead.

When something is not serialized, the value of it stays zero (because the memory allocated for the class instance is zeroed before anything is done to it, and serialization overwrites only values that have been seralized). So, for a MonoBehaviour, if it is not public and it does not have [SerializeField], it will always be zero/null immediately after control of the instance is passed to your code, and if you enter/exit play mode, it is deserialized again, and again the memory is zeroed.

Now, the ScriptableObject is not being deserialized each time, because it is an asset rather than a scene object, so I actually would not have been surprised at this behaviour on its own, if the non-serialized fields kept their values in the editor, because Unity would just maintain one instance. HOWEVER, if you put [System.NonSerialized] on the field, this does not happen, and it gets reset every time.

I did a test: I made a bunch of ints (one public, one internal, one protected, and one private). I made a button that incremented them all, and I made a button that printed them all. I did a ScriptableObject version, and a MonoBehaviour version. Results:

MonoBehaviour - all changes to all values (public or otherwise) lost when exiting play mode (this is correct behaviour)

ScriptableObject with no attributes - all changes to all values (public or otherwise) maintained when exiting play mode but lost when exiting Unity (this is not what I want to happen but it is what I would expect to happen)

ScriptableObject with NonSerialized attributes on non-public fields - changes to public fields maintained when exiting play mode, changes to non-public fields lost when exiting play mode

I can think of no behaviour that supports this: If the asset were being serialized/deserialized every time, then even without the NonSerialized attribute, the values should not persist. If the asset were not being serialized/deserialized every time, then the NonSerialized attribute should have absolutely no effect because it’s an in-memory copy.

I’m starting to think this is some sort of dirty hack one of the developers put in deliberately for this situation, maybe somehow for using ScriptableObjects as editor-utility objects? Because there is no reason that I should be telling Unity “don’t do this thing you weren’t going to do anyway” and have it actually have an effect.

Can you post your code for your test? I can get a MonoBehaviour to keep a value on a prefab. Exactly as you describe for ScriptableObjects.

Where is your MonoBehaviour? Is it sitting in the scene? Are you instantiating a prefab? Changing values on the prefab directly? How are you creating an instance of the scriptable object? How are you referencing it? Is the reference to the original ScriptableObject in the project, or something that only exists at runtime?

When instantiating a MonoBehaviour prefab, the instantiated MonoBehaviour (clone) still has a reference to the ORIGINAL ScriptableObject in the project. So on exit, the (clone) is destroyed and loses it’s values, but changes the now-destroyed (clone) made to the original still-existing ScriptableObject will persist.

I was using a MonoBehaviour on a scene object, not a prefab, which is why it was being deserialized fresh every time. But that’s not even the point of this discussion: That’s just a contrast, showing the behaviour I was going for.

The point is that something is happening with ScriptableObject instances (not clones) that cannot be explained by either the presence or absence of serialization: There can be no explanation other than there is code within Unity somewhere that is actually checking for the NonSerialized attribute when that attribute should have no effect, and is giving undocumented but reliable behaviour.

How is your MonoBehaviour referencing the ScriptableObject? Does it create a new ScriptableObject at runtime? Otherwise you are changing the original and the values stick in the project window. I don’t understand

I’m just super curious if there is really something I don’t understand about how Unity uses these classes, but the only way I can know for sure is to see some code. Otherwise the behavior you mention is what I would expect.

So here the original ScriptableObject in the Project window is getting changed. When play stops, only items in the scene get destroyed. Since this isn’t a scene object, it’s values stay. Saving the Unity project at this point should make these changes permanent. This makes sense to me.

The changes to NonSerialized fields during gameplay don’t even get tracked by Unity. So stopping gameplay means that they go back to default values instead of values that Unity hasn’t tracked at all. This also makes sense to me.

Same thing happens to MonoBehaviours on prefabs in the Project window. Sorry, I’m just trying to be as clear as I can figuring out what the problem is…

You’re not understanding. Try this yourself: Make a ScriptableObject that has “public int x” and (private) “int y”. Create an asset out of it. Using a button from the new UI or a MonoBehaviour or whatever, make it so that you can increment and print (Debug.Log) x and y from code with functions in the ScriptableObject.

Press play. Print: “0 0”. Increment, print: “1 1”. Press to stop play, then press play again. Print: “1 1”.

Now, go back to your ScriptableObject and add [System.NonSerialized] to your private int y. Close and open Unity to make sure the ScriptableObject is reset to 0 and 0.

Press play. Print: “0 0”. Increment, print: “1 1”. Press to stop play, then press play again. Print: “1 0”.

Do you not see? Unity does not serialize fields that are not public and do not have [SerializeField]. Except it is preserving values of private fields here while the editor remains open…which means it is serializing private fields when it’s going back and forth to the C++ internal space. But not if you add that attribute.

If Unity were serializing/deserializing the ScriptableObject when going into C++ space, that means no play mode changes should propagate, and they do. Therefore, it is not doing so and is actually using the same instance in both places somehow. Therefore the NonSerialized attribute should have no effect BUT IT DOES. Serialization is not happening…but an attribute related to serialization is having an effect…on things that shouldn’t be serialized…but are acting as if they are serialized…

1 Like

Oh i see… Unity is serializing private members on ScriptableObjects even through they are private. Yeah that is kinda weird. I’d have to do some tests but if that’s true then Unity must be serializing everything on ScriptableObjects by default.

I’m gonna make a test project today or tomorrow and upload an example.

It’s just so weird. If Unity were serializing the SO instances going into C++ space for play mode, that means for the values to propagate back to the editor mode, it would have to serialize FROM PLAY MODE. That’s insane. So what if they were doing some sort of freakish memcpy business? Then why would NonSerialized do anything?

It has to be deliberate. It can’t simultaneously be a bug with serialization and also be something that isn’t serialized. The ONLY way for values to propagate back out of play mode is for them to either be serialized bidirectionally or for them to not be serialized at all and instead be preserved as instances on both sides of the C++ wall.

This is the entire reason why Unity lets you specify a color to indicate play mode, so you don’t screw up and think you’re changing things that will persist and then lose all your work. But then why does code running in the play mode change the SO, and have that persist, but then have the attribute affect it?

My head hurts.