How to Design a Global GameManager in Unity
When developing a game, you’ll often have data that needs to be accessed globally across multiple classes. That’s when you start thinking: how should I design my GameManager?
This post explores several ways to structure a GameManager in Unity, from static classes to MonoBehaviour-based singletons.
Using a Static Class
In a regular C# project (not Unity), you might use a static class for global data access:
public static class GameManager
{
public static int score;
}
This allows you to access GameManager.score
from anywhere in your code.
Limitations of Static Classes
- You can't create an instance of a static class.
- You can’t inherit from
MonoBehaviour
, which means noStart()
,Update()
, orDontDestroyOnLoad()
. - You can’t attach it to a GameObject in the scene.
So while static classes are good for storing simple values, they fall short if you want to interact with Unity’s component system.
Using MonoBehaviour
If you need Unity lifecycle methods, define GameManager
as a normal MonoBehaviour class:
public class GameManager : MonoBehaviour
{
public int score;
}
Attach this script to a GameObject in the scene, and you can start using it. But what happens if you change scenes?
Limitations of Regular MonoBehaviour
Changing scenes will destroy the GameManager GameObject. If you only have one scene, that’s fine. But for multi-scene games, it’s a problem.
Using DontDestroyOnLoad()
public class GameManager : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
}
}
This keeps the GameManager alive even when changing scenes. However, it has one problem: duplicates.
The Problem with DontDestroyOnLoad()
If a new GameManager is created in the next scene, you'll end up with multiple copies. To solve this, we need a singleton.
Applying the Singleton Pattern
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public int score;
public int playerHealth = 100;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
This ensures that only one GameManager exists, even across scenes.
Benefits of Singleton
- Only one instance persists across scenes.
- Access globally via
GameManager.Instance
. - Automatically destroys duplicates.
Avoiding .Instance with Static Variables
One downside of the singleton approach is that you have to type .Instance
every time. To simplify:
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public static int score;
public static int playerHealth = 100;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
Now you can access the data directly:
GameManager.score = 10;
Debug.Log(GameManager.score);
It's fine with a single scene, but...
This code works safely in a project with only a single scene.
However, if your project has multiple scenes, the GameManager
may be created multiple times across scenes, so you'll need to improve the singleton structure a bit.
In a future post, I’ll show you how to build a more robust singleton pattern that works reliably across multiple scenes,
including how to load the GameManager
prefab dynamically from the Resources
folder.
Summary
- Use static class for simple, single-scene projects.
- Use MonoBehaviour when Unity features like
Start()
orUpdate()
are needed. - Use DontDestroyOnLoad() to persist between scenes.
- Use the Singleton pattern to ensure only one instance exists.
- Add static variables if you want to avoid typing
.Instance
.
In short, use static
for simple projects, and use the Singleton pattern for managing game state across scenes.