Singleton GameManager with custom classes as properties

Hello everyone,

I am new to unity scripting and found a common practice to set up a singleton GameManager for persistent data across scenes. Here is the implementation:

public class GameManagerController : MonoBehaviour
{   
    public static GameManagerController Instance
    {
        get { return _instance; }
        private set {_instance = value;}
    }
    private static GameManagerController _instance;

    public float screenRatio = 1f;
    public Teams sceneTeams;

    void Awake(){
        sceneTeams = new Teams();

        if(!_instance){
            _instance = this;
            DontDestroyOnLoad(gameObject);
        } else {
            Destroy(gameObject);
        }   
    }

}

[System.Serializable]
public class Teams
{
    [NonReorderable] public List<Unit> team1;
    [NonReorderable] public List<Unit> team2;

    public void updateTeams(Teams newData){
        team1 = newData.team1;
        team2 = newData.team2;
    }
}

[System.Serializable]
public class Unit{
    public string name;
    public int hp;
    public int maxHp;

    public Unit(string _name, int _hp, int _maxHp)
    {
        name = _name;
        hp = _hp;
        maxHp = _maxHp;
    }

}

This GameManager is set up in the Login Scene, then Teams are updated in the next Lobby Scene. Lobby Scene has its own separate controller which makes a reference first:

public Teams sceneTeams = GameManagerController.Instance.sceneTeams;

and then updates the teams on network event:

sceneTeams.updateTeams(JsonUtility.FromJson<Teams>(e.data.GetField("teams").ToString()));

The updates are visible in the inspector, but as soon as the next Encounter Scene is loaded, the sceneTeams loses all data. Same referencing is used in the Encounter Scene to get stored data from previous scenes:

public Teams sceneTeams = GameManagerController.Instance.sceneTeams;

I have used screenRatio variable to test if other data and objects are persistent and they proved to work fine. Setting screenRatio in Lobby to 2f is reported to stay 2f when GameManagerController.Instance.screenRatio is called in the next scene. So I am wondering whether custom classes have different behavior with DontDestroyOnLoad? Or am I missing something?

DontDestroyOnLoad(sceneTeams) throws an error:
error CS1503: Argument 1: cannot convert from ‘Teams’ to ‘UnityEngine.Object’ but I have no clue how to resolve it.

Any help appreciated.

This error is not mysterious at all:

You even posted the code for Teams:

It doesn’t derive from UnityEngine.Object so calling UnityEngine.Destroy() on it is meaningless.

What do you EXPECT the Destroy() command to do for you on a Teams object?

Then it already fails because I want to start in some other scene that isn’t even integrated in the game yet because I’m prototyping a new feature.

Anytime you design a singleton that MUST be placed in a scene you have failed to make a good singleton.

Not only that you are perpetuating suffering and misery upon yourself and anyone else who has to live with a project set up like this. Yes, it’s common in tutorials, even in Unity example code, but it is Bad Practice™.

Instead, try this approach:

Simple Singleton (UnitySingleton):

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

If you really insist on a barebones C# singleton, here’s a highlander (there can only be one):

And finally there’s always just a simple “static locator” pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

WARNING: this does NOT control their uniqueness.

WARNING: this does NOT control their lifecycle.

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

Anyone can get at it via MyClass.Instance., but only while it exists.

I wrote code from scratch and set up minimalistic scenes to check why this particular approach did not work. The problem turned out to be dead simple as usual. References.

So basically in other scenes I used local properties to cache a reference to a global static class. The issue is in their names and scopes. They were declared public and had the same name. It just reset the global altogether. Setting a local property private is a good way to fix the conflict, choosing some other name works too if you need the variable to stay public for your spaghetti code adventures.

Also if you stumble into issues with keeping data persistent, keep in mind value and reference types. Caching global value types will create a local copy, not a reference.

public int testInt = MainData.Instance.nonStaticInt;
public string testString = MainData.Instance.nonStaticString;

I am still new to unity and will definitely check Kurt-Dekker’s suggestion, but understanding the core of your own mistakes still makes a great deal of learning.