0
Under review

NullReferenceException when loading scene the second time from saved profile

MiTschMR 9 months ago in Game Creator updated 4 days ago 15

Hi Marti

In almost every game you have a main menu where you can configure different settings. The user starts the game, lands first in the Main Menu and then enters the game. He then thinks, "I could surely change <insert setting>", returns to the menu and changes the setting. After that he plays the game and finally exits it. When launching the game the next time, the settings should be the same as he left them. 

I'm trying to achieve the above use case with GC and the save load system. 

Process / Setup

  1. Check if profile exists
  2. Yes --> Load profile
  3. No --> Set profile number and save
  4. Apply settings according to global variables

The important part is the one where I load the profile. 

I have the following setup: 

Trigger OnStart

ConditionsSaveGameExists

Notice the debug messages before and after loading the profile as well as after setting the profile number. 


And I have two scenes, Main_Menu and Forest_Environment_Test. 

Problem

When I first enter the Main Menu scene, I can set the settings and when I leave the game without entering the second scene, everything gets saved to the profile. However, when I enter the second scene (Forest_Environment_Test) and return to the first one (Main_Menu) I get a NullReferenceException. Here is the player.log file from loading Main_Menu the first time when everything works: 

Loading profile 1

//Some messages I don't know about
Setting up 4 worker threads for Enlighten.
  Thread -> id: 436c -> priority: 1 
  Thread -> id: af4 -> priority: 1 
  Thread -> id: 3098 -> priority: 1 
  Thread -> id: 3724 -> priority: 1 

Ending loading profile 1
Set current profile to 1

Saving profile 1

The line "Saving profile 1" is logged when I press one of the buttons on the left side: 

And here the player.log file from loading Main_Menu when returning from the second scene when it doesn't work anymore: 

Loading profile 1
NullReferenceException: Object reference not set to an instance of an object.
  at GameCreator.Characters.Character.ResetData () [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.SaveLoadManager+d__38.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.MonoBehaviour.StartCoroutine (System.Collections.IEnumerator routine) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.SaveLoadManager.Load (System.Int32 profile, System.Action callback) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.ActionLoadGame+d__3.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.IAction+d__3.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.IActionsList+ActionCoroutine+d__10.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.MonoBehaviour.StartCoroutine (System.Collections.IEnumerator routine) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.IActionsList+ActionCoroutine..ctor (System.Collections.IEnumerator target) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.IActionsList+d__7.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.MonoBehaviour.StartCoroutine (System.Collections.IEnumerator routine) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.IActionsList.Execute (UnityEngine.GameObject target, System.Action callback, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.Actions.Execute (UnityEngine.GameObject target, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.Clause.ExecuteActions (UnityEngine.GameObject target, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.Conditions.Interact (UnityEngine.GameObject target, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.Trigger.Execute (UnityEngine.GameObject target, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.Igniter.ExecuteTrigger (UnityEngine.GameObject target, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.Igniter.ExecuteTrigger (UnityEngine.GameObject target) [0x00000] in <00000000000000000000000000000000>:0 
  at GameCreator.Core.IgniterStart.Start () [0x00000] in <00000000000000000000000000000000>:0 
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
GameCreator.Core.SaveLoadManager:Load(Int32, Action)
GameCreator.Core.d__3:MoveNext()
GameCreator.Core.d__3:MoveNext()
GameCreator.Core.d__10:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
GameCreator.Core.ActionCoroutine:.ctor(IEnumerator)
GameCreator.Core.d__7:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
GameCreator.Core.IActionsList:Execute(GameObject, Action, Object[])
GameCreator.Core.Actions:Execute(GameObject, Object[])
GameCreator.Core.Clause:ExecuteActions(GameObject, Object[])
GameCreator.Core.Conditions:Interact(GameObject, Object[])
GameCreator.Core.Trigger:Execute(GameObject, Object[])
GameCreator.Core.Igniter:ExecuteTrigger(GameObject, Object[])
GameCreator.Core.Igniter:ExecuteTrigger(GameObject)
GameCreator.Core.IgniterStart:Start()

Note how everything after the action "Load Game (profile 1)" does not get executed. Also, when I press one of the buttons to save the game, nothing gets executed. 

If you need more information, let me know. I'm at the end of my latin...

Cheers

MiTschMR

(Michel)
Unity version:
2019.2.15f1
Game Creator version:
1.0.7
Under review

Hi Michel!

Sorry for the late reply and thanks for the detailed report! Regarding the issue, it is by design that anything below the "Load Game" action will not be executed. The reason being that, when you load a saved game, the scene is unloaded and the next one (or same) is loaded.

So I'd advice to change the order in which the Conditions are executed and move the "Load Game" to the end of the list. Also, the "Set Current Profile" Action is redundant, as loading a certain profile also changes it.

On a broader view, I'm not sure what you're trying to do, but here's my educated guess. Some games have a "general" settings which are shared between all "save profiles". For example, the volume could be a candidate, which is the same one for all saved games.

This is currently not possible with Game Creator, as each data piece needs to be stored in a profile. However, if this is what you mean, it makes total sense to have it. I'm opening a ticket to see if I can modify the current Save/Load system and integrate it.


Thanks! Hope this answers your question. If I got something wrong, feel free to comment below :-)
 

Hi Marti

Thanks for your answer. According to you, it is by design, that everything below the „Load Game“ action doesn’t get executed. But then I don’t understand why, when starting the game, the actions below „Load Game“ get executed (log 1). During the second start they then don’t get executed. Why is that?


I have deactivated the checkbox in the GC preferences menu to save scene objects, so that it only saves the variables. This is what I want to achieve:

  1. Check if profile exist
  2. Yes --> Load profile (with saved variables)
  3. No --> Set profile number and save
  4. Apply settings (change values of UI components to show the settings defined) according to loaded global variables

These actions and conditions are defined within the OnStart trigger and get executed every time the game starts the first time. When I return to the main menu from the game scene it loads the saved profile (as we can see) but then throws an error and doesn’t continue. When I then press a button on the left side (picture of my menu above) the „Save Game to profile“ action doesn’t get executed. This bugs me as well and I don’t know why it doesn’t get executed. 

Do you know something or can give me a tip on how to achieve what I seek to do?


Cheers

Michel

    Hi Marti

    After a lot of testing everything gets correctly loaded and applied so it does not look like a bug of the save/load system itself to me. I refer to the global variables, tested with the "OnLoad" trigger. My approach now is the following: 


    1. Load profile 1 (even if it does not exist)
    2. Apply settings (change values of UI components to show the settings defined) according to loaded global variables

    The save action that gets executed every time I press a button in the left side bar does not get executed when the scene is loaded the second time. I have no idea why that is or what is causing this. 

    This picture shows the actions that get executed when I press the button "Home" (the 90° turned rectangle in the left side bar in the picture in the main question above):

    This picture shows the detailed action that gets executed when I press the button:

    Somehow this action gets disconnected from the button gameobject when I load the scene the second time. I do not have any proof of this, but it simply does not get executed the second time. 

    Do you have any idea what is causing this?

    Cheers

    Have you tried utilizing Awake?  


    "The Awake function is called on all objects in the Scene before any object's Start function is called. This fact is useful in cases where object A's initialisation code needs to rely on object B's already being initialised; B's initialisation should be done in Awake, while A's should be done in Start."

    Hi vorokoi

    I have tried using the Awake() function but this results only in crashing the build or the editor (when script execution is allowed for Editor and Runtime). Where and for what do you think should I use Awake()?

    Awake() and Start() methods are a patch to an unresolved problem in Unity: We should be able to decide the order in which methods are called (there's the script execution order tool, but that's... not very modular).

    So as a rule of thumb, Awake should only initialize an object, not communicate with other components. On the other hand, the Start method is where the communication between other components should happen.

    The problem is that the SaveLoad system requires to run on the Start in order to communicate with the scene game object components. But that Start is not guaranteed to be called the first one.

    If only Unity allows to have something like "int StartPriority()" where the method would return the priority in which methods should be called.

    Hi Marti


    As described above the Save/Load system looks OK to me, but the actions on the buttons disappear magically. This is what bugs me. I have not yet found something helpful, whether in the Unity forums nor in the bug list. 

    I will remove the actions from the buttons and experiment a bit today, what the problem could be. 

    If you have an idea, please let me know.

    Is it possible that it has something to do with DontDestroyOnLoad? The following Gameobjects get created in DontDestroyOnLoad when the scene first loads:

    I try to understand more of what happens here but don't really know what I can do. It looks like some kind of bug with DontDestroyOnLoad or the scripts, because the gameobject (with the actions) exists. 

    I don't think the DontDestroyOnLoad objects could be interfering with the buttons. When you say the Actions get disconnected from the buttons, you mean that the references tot he Actions are null? I mean, the OnClick event list, do they appear as null?

    I'm specifically referring to this image:

    There are 3 On Click () invocations. Do the first two work and not the third one (the ActionsSaveGame)?

    If the answer is yes, the problem is in the Actions itself (and thus we'll have to look into it). On the other hand, if none of the On Click() events get fired, the problem is that the Button might not be registering button clicks.

    It's most likely to be the latter one. If that's the case, it could be possible that the camera doesn't have a "Graphic Raycast" component, which is required by Unity in order to catch clicks on UI elements.

    I have now confirmed, that with "Run in background" ticked (activated, checked) the action gets executed. But now I have a new error message: 

    MissingReferenceException: The object of type 'PlayerCharacter' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object.
    NullReferenceException: Object reference not set to an instance of an object.
      at GameCreator.Characters.Character.GetSaveData () [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.SaveLoadManager.Save (System.Int32 profile) [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.ActionSaveGame+<execute>d__2.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.IAction+<execute>d__3.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.IActionsList+ActionCoroutine+<run>d__10.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.MonoBehaviour.StartCoroutine (System.Collections.IEnumerator routine) [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.IActionsList+ActionCoroutine..ctor (System.Collections.IEnumerator target) [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.IActionsList+<executecoroutine>d__7.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.MonoBehaviour.StartCoroutine (System.Collections.IEnumerator routine) [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.IActionsList.Execute (UnityEngine.GameObject target, System.Action callback, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.Actions.Execute (UnityEngine.GameObject target, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 
      at GameCreator.Core.Actions.Execute () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.Display+DisplaysUpdatedDelegate.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.Events.InvokableCall.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.UI.Button.Press () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 
      at System.Xml.Serialization.XmlElementEventHandler.Invoke (System.Object sender, System.Xml.Serialization.XmlElementEventArgs e) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.StandaloneInputModule.ReleaseMouse (UnityEngine.EventSystems.PointerEventData pointerEvent, UnityEngine.GameObject currentOverGo) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.StandaloneInputModule.ProcessMousePress (UnityEngine.EventSystems.PointerInputModule+MouseButtonEventData data) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.StandaloneInputModule.ProcessMouseEvent (System.Int32 id) [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.StandaloneInputModule.ProcessMouseEvent () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.StandaloneInputModule.Process () [0x00000] in <00000000000000000000000000000000>:0 
      at UnityEngine.EventSystems.EventSystem.Update () [0x00000] in <00000000000000000000000000000000>:0 
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    GameCreator.Core.ActionCoroutine:.ctor(IEnumerator)
    GameCreator.Core.<executecoroutine>d__7:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    GameCreator.Core.IActionsList:Execute(GameObject, Action, Object[])
    GameCreator.Core.Actions:Execute(GameObject, Object[])
    GameCreator.Core.Actions:Execute()
    UnityEngine.DisplaysUpdatedDelegate:Invoke()
    UnityEngine.Events.InvokableCall:Invoke()
    UnityEngine.Events.UnityEvent:Invoke()
    UnityEngine.UI.Button:Press()
    UnityEngine.UI.Button:OnPointerClick(PointerEventData)
    UnityEngine.EventSystems.ExecuteEvents:Execute(IPointerClickHandler, BaseEventData)
    System.Xml.Serialization.XmlElementEventHandler:Invoke(Object, XmlElementEventArgs)
    UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
    UnityEngine.EventSystems.StandaloneInputModule:ReleaseMouse(PointerEventData, GameObject)
    UnityEngine.EventSystems.StandaloneInputModule:ProcessMousePress(MouseButtonEventData)
    UnityEngine.EventSystems.StandaloneInputModule:ProcessMouseEvent(Int32)
    UnityEngine.EventSystems.StandaloneInputModule:ProcessMouseEvent()
    UnityEngine.EventSystems.StandaloneInputModule:Process()
    UnityEngine.EventSystems.EventSystem:Update()
    </executecoroutine></executecoroutine></run></execute></execute>

    This references to Assets\Plugins\GameCreator\Characters\Mono\Characters\Character.cs Line 316. After I have put this in a try/catch block it, every time I pressed the button the exception was caught. There definitely is something fishy going on (sorry to put it like this). The ResetData() function also throws an error, but you already have picked that up. 

    To summarize, I have a workaround for the problem, which is to put a try/catch block around the the code in the character.cs functions GetSaveData() and ResetData(). After that, everything is working perfectly. 

    +1

    Yes, I believe this is on our end. I have an idea where the problem might be. Thanks for the detailed report and patience!

    Hi Marti

    The problem still occurs with GC 1.1.6. Any news so far?

    Also with GC 1.1.8

    Still with GC 1.1.9. 

    Sorry, regarding the Character error, I'm also opening a ticket. This shouldn't be happening, but might be related to the order in which events are called when loading a game. I'll investigate this further and circle back as soon as I have news on this.