Variable is retrievable on Editor, but is isn't (becomes null) on build

For context: I’m working on a project wide scene management system. This isn’t quite literally to manage scenes in Unity, it’s actually for a loading screen.

I had some other issues which I manage to solve as I was working on the system (many order of execution issues, for example). At some point, I decided to make a build, just to test the system outside the editor, and for my surprise, the system didn’t work at all. It however, continued working on editor.

So I decided to make a development build and I put somewhere a Debug.Log() to see if I could get something. And well…

The system works this way: using this extension, I assign an SceneField type variable (called Loading Screen Scene) to the Scene Management inspector. The fields from the inspector are retrieved from a ScriptableObject called SceneManagementProjectSettings. The original fields are stored here.
Then, there’s the core code SceneManagement, which retrieves the values
from SceneManagementProjectSettings. Later on, the SceneManagement class raises the Init() method, where it calls the SceneManagementManager (very long name, I know) class and calls the method SetLoadingScene(SceneField scene). This method receives the already previously set field on the inspector. On the Loading process itself, Unity’s SceneManager class need to load Loading Screen Scene. Now, I obviously omitted lots of details, but that’s the basic structure.

The big issue comes when I build the project. According to the console message, it seems that the field Loading Screen Scene is set to null. For some reason, this doesn’t happen on the editor.

I did some research, and most of this cases boiled down to the order of execution. So I tried changing the Script Execution Order from the project settings with no luck. Honestly, I’m not even sure if that’s what they were referring to. It could be that, or the actual order I naturally set up while coding the system, but there doesn’t seem to be anything to change, or at least I didn’t find anything relevant to change in terms of order. I could be wrong.

Another thing I found while searching, was this pretty interesting comment on another post.
If I take what this comment says, then that would mean that my SceneField type field is getting nulled because of serialization.

If serialization is the case, then what are my options to solve it? Keep in mind that this structure that I’m using is based on the code structure of this particular asset (I actually could be misinterpreting the structure of its code). With my basic understanding of serialization, the only thing I could find was that the SceneManagementManager class was marked as Serializable. As far as I know, that means that it can be pass through Unity’s serialization process. So I’m really not sure what else can I do.

I attached the main codes of the system, the project settings scriptable, its editor, and the SceneManagement class. The SceneManagementManager doesn’t have anything that could help, it’s mainly code of the loading process.

Thanks in advanced.

7888651–1003936–SMProjectSettingsEditor.txt (1.71 KB)
7888651–1003939–SMProjecSettings.txt (1.2 KB)
7888651–1003942–SM.txt (2.03 KB)

It’s too much code for me to digest right now, but I will make one observation: You use a lot of null-coalescing operators (??) which Unity objects do not support. Your null checks are most likely failing to work the way you want, and you’re creating new instances (or not creating new instances) which is resulting in the flawed behaviour.

Also, ScriptableObjects are loaded very differently in the editor than a build. Make sure you have strong, direct references to them in your build. Clicking one in the project window, for example, is enough to load it in the editor and have it still be alive when you enter play mode but that obviously won’t work in a standalone build.

2 Likes

Hmmm, I see. I actually didn’t know that. Thanks. So, null operators are not supported, but I supposed I should be able to do a common null check (!= null), right? Although this seems to indicate the opposite.

About the ScriptableObjects, you mean that I should reference then the exact path location of the file? or am I misunderstanding something?

Well, that’s the wrong conclusion. They do work and do exactly what they are supposed to do. However they don’t really work “well” with any object derived from UnityEngine.Object because they can be “dead” objects which would not be detected by any of the null operators (??, ?., is null, … ) because they actually check the variable / value for “null”. However a dead UnityEngine.Object is not really null but just fakes that it’s null. They do this because the == operator is overloaded for UnityEngine.Object references (or any derived type).

No, you got that comment completely wrong ^^. Things do not get “nulled” by the serialization system. The opposite is the case. Unity’s serialization system essentially treats custom serializable classes (which are not derived from UnityEngine.Object) essentially like structs. So when a class with a serialized custom class is serialized, the inspector would automatically create an instance of that custom class for that field. Such a field can not intentionally be set to null in the inspector. However if you for example use AddComponent to add your script dynamically / manually at runtime, the field of our custom class is never serialized or initialized so it would have its default value which is null. The only references that are serializable and support an explicit null value are UnityEngine.Object derived types (and lately through the SerializedReference attribute, but this is a whole other beast with tons of special rules and restrictions).

Personally I would be careful with that “SceneField” solution. It’s not recommended to change the serialized fields based on preprocessor directives. This could lead to all sorts of issues since the serialization layout changes when you add / remove serialized fields. This may already be the source of your issue, though it’s hard to tell from the outside.

I’ve just found another potential issue here:

        public CoroutineChannel CoroutineChannel
        {
            get => _coroutineChannel;
            set => CoroutineChannel = value;
        }
        [SerializeField] private CoroutineChannel _coroutineChannel;

The setter of your property seems to be an infinite recursion, so using the setter would / should result in a stackoverflow exception when used.

3 Likes

Thanks for the explanation!

Ohh ok ok, I remember reading that, just never clicked on my head.

Noted.

I wonder how KNOT circumvented all of this. I missed a very important thing from their code, apparently.

Anyway, I am so traumatized with this that I dreamed that I programmed another solution lol, I’ll see if it works. I will also see how others have done it to get to something more concrete, I don’t have much experience with serialization and the inspector as you can see. Thanks for taking the time!