Should the private fields of custom ScriptableObject be serialized?

Should the private fields of custom ScriptableObject be serialized?

Because they do!

using UnityEngine;
using System.Collections.Generic;

public class PPSpriteBundle : ScriptableObject
{
	private bool loaded;
	private Dictionary<string, PPSpriteAnimation> animations;
	
	public PPSpriteAtlas[] Atlases;
	
	public bool FindAnimation(string name, out PPSpriteAnimation animation)
	{
		if (!this.loaded)
		{
			this.Load();
		}

		return this.animations.TryGetValue(name, out animation);
	}
	
	private void Load()
	{
		if (this.loaded)
		{
			return;
		}
		this.loaded = true;
		
		var platform = "default";
		
		this.animations = new Dictionary<string, PPSpriteAnimation>();
		foreach (var atlas in Atlases)
		{
			int i = -1;
			if (atlas.PlatformTags != null  atlas.PlatformTags.Length > 0  (i = System.Array.IndexOf(atlas.PlatformTags, platform)) < 0)
			{
				continue;	
			}
			
			foreach (var animation in atlas.Animations)
			{
				if (i >= 0 || !this.animations.ContainsKey(animation.Name))
				{
					this.animations[animation.Name] = new PPSpriteAnimation
					{
						Atlas = atlas,
						StartIndex = animation.StartIndex,
						FrameCount = animation.FrameCount
					};
				}
			}
		}
	}
	
	struct AnimationInfo
	{
		public PPSpriteAtlas Atlas;
		public PPSpriteAnimation Animation;
	}
}

The problem is that I get loaded == true after the resource is loaded.
Yes, I know, that marking it with System.NonSerialized will solve the issue.

But, hey, WTF? Is it documented somewhere?

The only reference to this behaviour I could find comes from here:
https://blogs.unity3d.com/2012/10/25/unity-serialization/

  • Private fields are serialized under some circumstances (editor).

But this doesn’t make sense really. It’s effectively so though

Can I somehow workaorund this? very annoying

1 Like

[System.NonSerialized]

Doesn’t help, private properties still get there values serializd

When are you expecting them to deserialize?

Like the OP I have a private bool in my scriptable object, it’s set to true during the course of its lifetime, this state will be serialized so next time the object is instanced it will be true from the get go

Are you sure the private variables are being serialized? I think it’s more likely that your object instances are persisting between runs which is how SerializedObject assets work, they are not scene objects.
Open the asset file in a text editor and see if those private fields are in it.
Try placing a print statement in OnDestroy to see when the object is unloaded. Is it when you expect or is it persisting.

1 Like

Its persisted between editor runs at least, maybe not between player runs, havent tried. But it’s bad enough its persisted between editor runs. Will look into this more when I get home from boring dayjob.

So two monobehaviours referencing the same scriptable object will get the same object reference? In our case thats fine since we only operate at it from one place at a time. But I can see people getting strange bugs because of this. Maybe document this better? :wink:

I mean like this when I talk about a mono.b referecing a scriptable object

Yes, you’re referencing an asset so that asset will persist in the editor after it has loaded like other assets do.
This will not be an issue in the player as you wont be restarting play mode.
A simple way to solve this is to Reset your private variables inside the OnEnable function, this gets called at the scene start even for persistent objects.

https://docs.unity3d.com/Manual/class-ScriptableObject.html

But you can run into a concurrent problem, you should document this better, I mean you should make it so its no way people can miss that the same reference is used. I know that the core user case is data only. But my use case above were I use the scriptable object both as data object and state machine you could run into problems. For example

    [CreateAssetMenu(menuName = "Tutorial/FireStep")]
    public class FireStep : TutorialStep, IHandle<FirearmDischarged>
    {
        public bool EmptyMagazine;

        private bool done;
        private Firearm firearm;
 
        public override IEnumerator Execute()
        {
            firearm = Get<Firearm>();
            var message = EmptyMagazine ? "Press and hold {0} to empty mag." : "Press {0} to fire a round.";
            ShowPopup(firearm.Trigger.transform,  string.format(message, GetCommandCaption(Commands.Fire)));
    
            while(!done)
                yield return null;
        }

        public void Handle(FirearmDischarged message)
        {
            if(firearm != message.Firearm) return;

            done = !EmptyMagazine || firearm.Magazine.Empty;
        }
    }

For me its not a problem since I fire this in a sequential manner, but if two of above execute at the same time that happens to be the same serialized asset you will run into concurrent problems from hell. Both firearm and done properties could potentially be completely wrong :smile:

edit: come to think about it, when we use Prefabs in similar manner we always call Instantiate on it to get a clone.

1 Like

ah, same workflow as for prefabs works :smile: Object.Instantiate
I have done alot of noob mistakes these latest days, I need to get alot of red wine and sleep this weekend

1 Like

Similar to the OP, my SO has logic in it, where I start a Coroutine in it. To prevent the coroutine from firing more than once, I created a private boolean variable called enabled which is defaulted to false. Imagine my surprise where it loads and the value is somehow true despite me not setting it. I’m guessing that somehow it was serialized as true (not sure how), and therefore it deserializes as true when my app loads, thus ignoring my default value. This is completely unexpected behaviour, IMO.

1 Like

yes, it is really awful. I need to add NonSerialized to private fields in SOs. Why?!!!

1 Like