Inconsistent behaviors of Scriptable Object

Hi!

I would like to raise some concerns I have about ScriptableObjects. I have been starting to use them a lot recently after a few talks were made highlighting their power, and they are definitely very useful. In some aspects, however, they are very inconsistent:

-The serialization of ScriptableObjects is somewhat related to the editor session: OnEnable will be called when you create the object, and when it is linked to an active scene object, or on Play if it is referenced by a scene object or another ScriptableObject, but also when you click on an object that is not actively in memory in the editor!
This is inconsistent because it means that the same method is called for initialization purpose when creating an asset or loading an asset in the editor, and during the application’s normal behavior when creating/enabling a ScriptableObject. Shouldn’t a ScriptableObject’s OnEnable only be called when it would actually be Enabled during play, or when marked “ExecuteInEditMode”, like for MonoBehaviours? I think this would be much clearer (the same thing goes for Awake).

-It is not possible to use a ScriptableObject as a singleton without having to place it in the resources folder: if the SO is not referenced by any scene GameObject’s Component, it won’t be present in the build, so if I only call it from a method, this won’t work in the build. I understand where this comes from, but having the ability to mark an asset as “To be built” would allow having cleaner folders.

-Last, but most importantly, the state of a ScriptableObject is not properly preserved during a Unity session. This is really inconsistent with the usual “Entering and Exiting Play” behavior. Consider the following example:

using System;
using UnityEngine;

public class TestSOSerialization : ScriptableObject
{
    private bool privateValue = false;

    [NonSerialized]
    private bool value = false;

    void OnEnable () {

        Debug.Log(value);
        Debug.Log(privateValue);
        value = true;
        privateValue = true;

    }

}

With this code, after exiting play, privateValue will be true until I close the editor. On the contrary, value will remain false. Not only this is inconsistent with the usual serialization behavior, but also this can create bugs that can be hard to track down, especially when you are getting started with SO.
I don’t even understand why privateValue retains its value after Play: I understand the SO is in the memory and therefore the value is kept (although I would argue this should not be the case), but then why isn’t it the same for value?

It would be great to have some explanation as of why is it this way, but I would also like to point out that this isn’t exactly the cleanest behavior from my perspective: maybe this could be improved a bit?

ScriptableObject’s act more like assets than like scripts/components/scene objects. Where in a scene object has to be flagged to not be destroyed between scene, where as non-scene assets lives on regardless of the scene until nothing is using/referencing it.

They’re a bit more like say a Material. You’ll notice that if you animate a material (notably the shared instance of a material), it effects that Material on disk.

Which yes, they live the life of the editor rather than the game (as played in the editor… in a build the game is the entire lifespan). That’s just how they behave, just like all other assets (as opposed to in scene GameObjects).

Since they behave in this manner just like materials and the sort. You can clone them at start of play if you plan to modify them in game and not want it to effect the on disk version. Just like how accessing the ‘material’ property of a renderer creates a clone of the ‘sharedMaterial’ so you don’t effect the shared instance.

As for the ‘Resources’ folder and the suggestion of a ‘to be built’ option. I mean this technically goes for ALL assets in your project. It’s not really an inconsistent behaviour. Resources folder is how Unity allows you to force include objects. I agree though that a different design could be cleaner, but I’d argue making it available to everything and not just ScriptableObjects.

Since a ScriptableObject is more like a non-scene asset as I brought up before, this mean it is definitely dependent on the editor session when in the editor.

OnEnable will be called at the moment that the object is actually loaded/instantiated. This can happen in the editor at many different times and is wholey dependent on if the object is needed. Such as all the places you describe. If the SO is not loaded and something needs to touch it (including the editor itself), it loads it, and OnEnable is called.

Though I will point out this is not the ‘serialization’, this is when the object is loaded. In regards to serialization this may happen just after deserialization (but not necessarily, it could happen if cloned, or if Instantiated by ScriptableObject.CreateInstance, etc).

Note that ScriptableObject is really designed as a data container, as is sort-of mentioned in the documentation (unity has really vague documentation at times):

OnEnable is really only intended to general data setup.

What exactly are you needing in OnEnable that this work-flow is thwarting?

I know, but my point is mainly the difference between

private bool value;
and
[NonSerialized]
private bool value;

One will reset before and after play, but the other not. This is where I find it inconsistent since private fields are already supposed to not be serialized.

I know that SO are meant for data structure, and mainly that’s what I use them for. But data structure does not mean that it should not be able to have a form of life-cycle.

Yes, I agree, this stroke me the most with SO though as they are closer to logic than other assets.

This is my point though. If OnEnable is called at seemingly random time depending on an Editor behavior, it can create inconsistent behaviours with the build. What I mean by that is that the Enabling behaviour should be consistent with the one happening in the build: Any behaviour specific to the editor should be isolated elsewhere, don’t you think?

Being “needed” has two meaning: being inspected and modified in the editor, or being enabled during Play, and I don’t find this particularly clean.

That’s true, however, I think they can be much more and making them have a behaviour that is not Editor session dependant would make them easier to expand on. (like on the following talk: https://www.youtube.com/watch?v=raQ3iHhE_Kk)

In the video I mentioned earlier, there is a great example of usage of SO as EventInvoker to create a global event system that can be very helpful for game designers. If you need to reset flags or bind some event listeners, you may want to do it on OnEnable and remove them on OnDisable for instance. You may as well want to create some texture or anything like that.

I know that this is strictly not what the documentation wants you to do with SO. I am just pointing out that there is a lot of potential here that is made more difficult to achieve because of these editor related behaviours.

Yeah, I’ve noticed this with scripts as well. I ALWAYS ‘NonSerialized’ all my private fields to ensure the unity serializer doesn’t touch them.

It’s not random though. It’s when it’s created.

In the editor this can be ‘sort of’ random in that you are loading and unloading objects constantly. This is because the editor hops in and out of play constantly. Recompiles constantly. It is constantly shifting state causing assets to unload and then be reloaded again.

In build though, this isn’t so. The game doesn’t have to shift states like this. It has a single state… “playing”. All other states are “program is not running”.

An editor that can act in a ‘play’ state and an ‘edit’ state and also has to compile and other stuff is inherently unclean.

And when you want to use something for what it’s not exactly intended for. You’re going to have to compensate for that.

If I try to hammer in screws, I’m going to have to consider the implications of using a hammer on a screw.

I do a similar event hooking thing like you describe, I’ve compensated for it in the editor by late binding and using an editor script to clean up anything the editor may bugger up. Knowing that at runtime the editor is no longer a variable (of course testing is still required of your build, and that’s what the ‘Development Build’ option is for).

Yes. I know. But this can happen in a whole range of circumstances in the editor, which is not clean. I did not mean random in a literal way.

And that’s precisely the point I’m trying to make: this could be improved and made cleaner.

Which does not mean we cannot strive to make it a bit cleaner :wink:

Of course. However, this behaviour I am describing was explained and used in official Unity panels, and I made this post in the first place in an effort to try and explore ways the engine could become more handy and reliable regarding the direction Unity seem to be advertising concerning SO.
To get back to your example, if all I am given for screws is a hammer, I can as well come and give opinions on how we could make that hammer more of a screwdriver :slight_smile:

  1. welcome to Unity, they don’t necessarily do it the way I or you might like it. The concept of ‘cleaner’ is subjective.

  2. We can’t strive for anything since we are not the ones who write Unity. If you’d like to give Unity feedback, their website has a facility for submitting feedback and bugs… this is not the place though.

  3. My point is that there is a logic to how it works. You may not like said logic… but that’s part of using a 3rd party API, you have to use it how they designed it. So your best effort is to understand how they designed it.

As for your example.

If you register in OnEnable, and unregister in OnDisable. You shouldn’t have issues. They’re called in order no matter how frequently or infrequently that may be. It just means that in the editor you may unregister/register at some point in the editor’s life, but it inconsequential. The entire game resets several times and such an occurrence would occur regardless.

This same sort of life span impacts things like 'static’s in your scripts. It’s just a consequence of the editor.

You know, I am not new to Unity, despite the fact that I joined the forum late.
I understand perfectly that what I am talking about is just my opinion, and that talking about it here will not lead to any change. Striving for a change can also start with an open discussion, no matter if we are Unity developers or not. Besides, Unity devs are also on this forum.
I know the tools to submit feedback and bugs, and I would appreciate not being patronized like this.

I simply wanted to start an open discussion about concerns I have since we had few bugs in my team related to private fields without clear mentions of the behaviour in the documentation. I think it is unclear and since few programmers of my team mentioned it I wanted to talk about it here.

Of course, there is a logic to how things work. I just wanted to start a discussion on it and hear different perspectives.

Registering on enable and unregistering on disable would work of course but you risk making object enter a certain state without noticing (if for instance, they react to being registered) when it happens in the editor before play, since private fields do not reset properly when entering play.

Yeah, and I agreed it’s a problem. I clearly stated I always use the ‘System.NonSerialized’ attribute on all private fields as my work around for it.

Yeah, I agreed this can be annoying if you want to use things for a manner different than they are designed.

That’s why I said you should compensate for it.

If you use OnEnable to react to being registered… well, maybe not do that since you understand how/why OnEnable occurs.

I get you want to “have a discussion”, I’m having a discussion back with you. I’m agreeing with some of your gripes, disagreeing with others, I’m describing work arounds for gripes, and I’m suggesting methods with which to deal with it.

As for your experience with Unity… ok, I don’t know you. You’re here having a discussion and in it you say you want to ‘change’ things. Well, I don’t know IF you know where to do that, but if you say that I’m going to say “well the area to change things is over there.” Cause sure you might have experience with Unity, I have no metric for that. I do have a metric for your experience with the forums (your 40 posts), and well the scripting forum is not where you request changes.

Sorry bruh if I don’t 100% agree with your gripes and only like 75% agree.

:shrug:

Sometimes Unity gives bad advice. And their recent pushing of ScriptableObject as a cure for cancer is one of them.

ScriptableObjects are assets, they behave as assets, and they should be treated as assets.

You’ll find most of the inconsistent behavior disappears when you do this.

Specifically that means

  • Treat assets as immutable. Don’t ever change the asset at playtime. If you have need for a mutable asset, Instantiate a copy for the duration of the play session.
  • Don’t have any initialization code that you expect the asset to run itself. Assets don’t run their own code.
  • Don’t use ScriptableObjects for data related to an individual play session. This belongs on a GameObject with DontDestroyOnLoad or similar.
  • ScriptableAssets should be primarily for reading data from.

As a general rule ScriptableAssets make great data containers. For everything else, its better to do it on a GameObject. Life will be much easier.

1 Like

Sometimes… lol

1 Like