This week has been a bit more productive than last for me despite getting sucked back into not 1 but 2 old favorite mobile games again. I worked primarily on what I am calling the 'flow' of the game. Basically how the Player will play through the game from the moment they load into your game until some end state. I will explain more later in the post. While working on the flow of the game I discovered a few different bugs that I was able to fix, but one in particular that caused a bit of trouble that ended up sparking some design discussions amongst the team. So the short list of what we have worked on is:

  • Continued working on the scene transitions (AKA the Flow of the game)
  • Updated the main title screen
  • Updated the story text when loading into the game
  • Began play-testing the game
  • Found & Fixed several bugs
  • Discuss the scene-transition bugs amongst the team on a larger scale

Scenes

In Unity, one of the main ways to break up your game into smaller pieces is by using what are called 'scenes'. A scene can be a lot of things ranging from a simple menu screen to a complex world with thousands of objects and interactions and just about anywhere in between. Our team has currently divided our game up into multiple playable scenes starting out with a Title Screen that every player loads into first thing. After the Title Screen the Player can load into another scene by hitting the Play button. Even more scenes are used for other parts of the game as the Player plays through.

The Bug - Multiple Persistent Singleton Objects

The bug I found was quite a fun one. What it came down to was having multiple persistent singleton objects in our scenes that didn't behave nicely when loading between scenes. For example, one issue was with our LightManager - a persistent object that manages the lighting in our scenes. Each scene has a LightManager, but only one LightManager is ever active at any time. There is some special code that is used to detect that there is only ever one LightManager running in each scene while the game is running.

As I play test through the scene transitions I notice some consistently odd behavior, everytime I load into a new scene all of my lights would go out. I double checked to make sure both of my LightManagers were setup properly and that there was code in place to turn on the lights appropriately for each scene, but something just wasn't working. I set multiple breakpoints and added Debug.Log() statements all over the place and found a few issues but not the main one I was trying to solve. No matter what I tried, the lights would always go out completely when loading into a new scene.

Taking a step back from the problem a bit lets break down the scenario:

  • Scene A - LightManager_A
  • Scene B - LightManager_B
  • One LightManager per scene
  • Each LightManager is a persistent object
  • Only one LightManager can ever be active at any time (a singleton)

Here is a small snippet of modified code that creates a persistent singleton game object:

public class LightManager : MonoBehaviour
{
    public static LightManager Instance;

    void Awake()
    {      
        if (Instance == null)
        {
            DontDestroyOnLoad(Instance.gameObject);
        }
        else
        {
            Destroy(Instance.gameObject);
        }
        
        //setup light references
    }
    ...
}

So you load into Scene A -> do some stuff -> load into Scene B.

Load into Scene A and do some stuff

Behind the scenes as you load into Scene A, LightManager_A wakes up and the first thing it does is check to make sure there aren't any other LightManagers around. Since Scene A is the first scene and LightManager_A is the only LightManager it gets a shiny gold star from Unity and joins the list of objects labeled DontDestroyOnLoad.
Everyone is happy and the LightManager decides to sets the mood by adjusting the lights.

Load into Scene B

As you load into Scene B (which also has a LightManager_B) the LightManager_A from Scene A is still hanging around because it got the special stamp of approval to not get deleted as Unity transitioned to a new scene (DontDestroyOnLoad()).
Its just chillin minding its own business.
The Awake() method is not called, and LightManager_A does not bother checking for other LightManagers because...well that would just be silly to have another LightManager around right? LightManager_A's Awake() method was called while in Scene A, and is not called again in Scene B. Awake() is called only once in the life-cycle of a Unity GameObject.

Also As you load into Scene B, LightManager_B wakes up and the first thing it does is check to make sure there aren't any other LightManagers around.
HOLD UP THO it says.
"Another LightManager has been mucking around in here and I can smell its stink all over this game!" LightManager_B exclaims.
Before anyone can stop it from happening, LightManager_B pushes the big red panic button marking itself for deletion (Destroy()).
But before it deletes itself, it fully executes all of the code in the Awake() method. Which is actually sort of a problem because that is where all of the object references for the lights are setup.

Awake()

Awake() is a built-in method for Unity Monobehavior objects (a standard class Unity objects typically inherit from). There are several of these methods called in the 'script life-cycle' of Unity, and it can be super helpful to understand how these work if you are doing Unity development (see this link for more details on Unity's script lifecycle).

The result

So what ends up happening is that in Scene A all of your LightManager setup is done happily and without issue. All of the references are set properly in the Awake() method and the lights are turned on as they should be.

In Scene B, new references are set to the static Instance of LightManager so LightManager_A is the persistent object, with references to LightManager_B's lights.
Immediately after the references are switched LightManager_B gets deleted and so do the lights (at the moment the lights are child objects under the LightManager parent object).

Why multiple LightManager objects?

Part of the problem for this bug is having multiple persistent singleton objects. Typically you instantiate one of those objects one time in the beginning of the game and you carry that object forward through the life-cycle of the game as you transition between scenes. That works great for the happy path execution of the game.

During development however, testing your game by always going down the happy path can completely blow up the amount of time it takes to test a new feature or change. If you are testing one very specific area in the 3rd scene of your game, you don't want to have to play through Scene 1, Scene 2, and into scene 3 EVERY time you want to test new changes. You absolutely should do loads of full play-testing of your game, but that can happen later after the individual pieces work as expected.

So if you only have a LightManager in your Scene 1 and you load directly into Scene 2 without going through Scene 1 then you won't have a LightManager. The natural progression of developing and testing led us to have a set of all of the Manager level persistent objects in each of our scenes so we can easily jump into each scene without having to play through the entire game. That led to much faster testing time, but also led to having to deal with this bug.

The solution

There are a couple of ways we could have solved this and the short answer is that our fix is still ongoing. Due to the way we designed our game, we have several singleton
objects that do a number of things for us. So taking the testing situation into account that means we have several objects we need to enable/disable each time we are going through a development and play-testing cycle with our scenes. Not ideal, but also not that cumbersome at the moment. But 'not that cumbersome at the moment' for now means that with everything new we add to the game, that chore gets bigger and will bite us more and more.
Tech Debt baby.

For now what we have done is to make sure that every time we save the game out into a "ready for testing" state, all we have to do is just make sure all copies of the persistent objects are saved in the Disabled state. Except for one set that is saved in the Active state, the set in our very first scene. This allows the objects to still exist in the scenes and be used for testing if we need to manually enable them, but the objects will not wake up when the scene loads and cause us problems.

TL;DR

Multiple persistent singleton objects caused weird issues when transitioning between scenes because the object had time to execute its Awake() method before being deleted. A good example of this was with our LightManager, where the lights would always go out every time we loaded into a new scene.

The solution we chose for now until we can refactor some things is to make sure that only one set of persistent objects is ever saved in the Active state - the very first set in our first scene. The other persistent objects still exist in the other scenes to allow for easy testing, but they are saved in a disabled state so that they do not activate on scene load unless we explicitly activate them ourselves.

Thanks for reading! Until next time.

Matt "Taius"

References

Unity Script Life-cycle