- Home /
Why does a ScriptableObject saves nonserialized fields?
I have the following two classes:
TestBehaviour.cs:
using UnityEngine;
public class TestBehaviour : MonoBehaviour
{
public TestObject TestObject;
private bool Value = false;
private void Start()
{
TestObject.TestFunction();
Debug.Log("MonoBehaviour Value: " + Value);
Value = true;
}
}
and TestObject.cs:
using UnityEngine;
[CreateAssetMenu]
public class TestObject : ScriptableObject
{
private bool value = false;
public void TestFunction()
{
Debug.Log("ScriptableObject Value: " + value);
value = true;
}
}
When I load the project and run it the first time, both logs are false as i would expect. But in the second excecution the output of the ScriptableObject is true.
Now I would like to know, how to prevent a ScriptableObject from saving those nonserialized fields I want to use to save the state of my object.
Answer by Bunny83 · Feb 20, 2020 at 02:48 PM
The values are not serialized to disk. It's just that the object loaded in memory from your assets will of course keep the current value. Try closing the Unity editor, open it again and you will see that the value is back to false. Assets are, from the life cycle point of view, loaded once and remain in memory until your application is shut down.
Technically it is true that when entering playmode Unity does perform an assembly reload. At this point usually all non serialized data would be lost since Unity destroys the whole appdomain including all objects. However when an assembly reload happens Unity does actually save and restore even private fields it can serialize. Otherwise hotreloading wouldn't be possible at all.
If you specifically want to prevent private fields to survive hot reloading / an assembly reload you have to use the NonSerialized attribute on your field. That will prevent Unity from ever saving this field. This is actually mentioned at the very end of the script serialization documentation. To quote:
When reloading scripts, Unity restores all variables - including private variables - that fulfill the requirements for serialization, even if a variable has no SerializeField attribute. In some cases, you specifically need to prevent private variables from being restored: For example, if you want a reference to be null after reloading from scripts. In this case, use the NonSerialized attribute.
Still, the "Play - Stop - Play" behaviour is different for $$anonymous$$onobehaviours and ScriptableObjects. ScriptableObjects remember values of private fields for sequential plays after stop, while $$anonymous$$onobehaviours do not. Then it boils down to "same code, two behaviours".
Not really. The difference is the lifetime of the objects. $$anonymous$$onoBehaviours live in the scene. The scene is reloaded when you enter playmode. ScriptableObject assets do not live in the scene but are assets in the project.
You will actually see the exact same behaviour if you change a private non serialized field in a monobehaviour of a prefab in the project (note: I don't mean a prefab instance in the scene but the actual prefab in the project). I quickly did a test just to be sure and yes, it works exactly the same.
So I declared a private non serialized bool variable in a scriptable object and inside a monobehaviour. I attached the monobehaviour to an object and made a prefab out of that object. I delete the instance from the scene. Now I created another script from which I can set / reset the boolean values in those two objects. So I referenced both, the scriptableobject instance that is stored in the project as asset, as well as the prefab that is also stored as asset in the project. The default value of the booleans is set to false. When I set the two booleans to true and stopped the game, the private variables kept their true values. When I re-enter play mode they are both still true. However none of those true values are actually serialized. When I restart Unity, both values are back to false.
As I said a $$anonymous$$onoBehaviour instance that lives in the scene of course behaves differently as the scene is loaded / unloaded when you enter / leave playmode. So this has nothing to do with ScriptableObject or $$anonymous$$onoBehaviour but simply where they are located and when and how they are loaded in memory. Keep in $$anonymous$$d that from the engine's point of view a ScriptableObject and a $$anonymous$$onoBehaviour are represented by the exact same class on the native side which handles the serialization of those instances.
The main reason why private variables in assets actually survive is because the editor does serialize private variables as well when it comes to assembly reloads which also happens at playmode change. It just does not serialize the value to disk. So as long as an object stays loaded, the value will survive.
If you want to ensure that your private variable are never serialized, you can use the System.NonSerialized attribute, even on private variables, to ensure they are never serialized. This is mentioned at the very end of the script serialization page as I already said in my answer.
ps: If you want a repo case, here are 3 files:
Create a prefab with the Test$$anonymous$$onoBehaviour attached and also create an asset of the TestScriptableObject. So you have two assets in your project and nothing in your scene as this is all about asset serialization. Open the editor window and assign your two assets to the corresponding fields. You should now have a set and reset button for the private testBool variable in each of the two classes. You also see the current state of the boolean value at the end. Now try setting both to true and change playmode. You will notice that both will stay true.
Now uncomment the "System.NonSerialize" attribute in both files and try again. When you enter playmode both variables will turn false again due to the assembly reload that is happening. Note that setting the values to true during playmode will keep their values when you exit playmode. That's because when you exit playmode there is no assembly reload, so the values and the actual instances stay in memory. Though on next play they will be false again.
Again this is all about the lifetime of assets vs instances serialized into scenes. A scene will never stay loaded unlike assets. So the different behaviour comes from this fact and not from the fact that one is a ScriptableObject and the other a $$anonymous$$onoBehaviour. As you can see here both behave the same when serialized as asset.
Your answer
Follow this Question
Related Questions
ScriptableObject asset loses reference after restarting Unity 1 Answer
[Solved]How to serialize Dictionary with Unity Serialization System 6 Answers
ScriptableObject : Serialized Polymorphism Classes Can Not be Deserialize with Polymorphism 1 Answer
Serializing ScriptableObject into scene without writing to asset 1 Answer