Overview
Moving beyond simple prototypes to full games with multiple scenes and complex rules, you'll face a design question: "Where and how should I manage overall game state?" For example:
- Is the current game state "title screen," "playing," "paused," or "game over"?
- Where should persistent data like player score, lives, and currency be stored across scenes?
- Who controls overall game flow—starting, restarting, transitioning to the next level?
Scattering this "game-wide information and logic" across individual player and enemy scripts quickly produces "spaghetti code" that's hard to maintain and understand.
The classic solution is the Game Manager pattern. A Game Manager is an object that centrally manages game-wide state and logic—essentially a "command center."
Game Manager Responsibilities
What a Game Manager handles varies by game size and type, but typically includes:
-
Game State Management: Holds game states defined with
enum(e.g.,Title,Playing,Paused,GameOver) and controls state transitions. Depending on current state, it can disable player input or show/hide UI. -
Game Rule Management: Handles score calculation, countdown timers, win/loss condition evaluation—the core game rules.
-
Global Data Management: Stores data that multiple objects or scenes need to access—player score, lives, experience points.
-
Providing References to Other Objects: Holds references to important manager classes like Player, UIManager, and AudioManager, providing a convenient access point for other objects.
Singleton Pattern Implementation
A Game Manager must be guaranteed to exist as exactly one instance in the game. The Singleton design pattern ensures this "single instance" and provides easy global access.
Here's a basic Game Manager implementation using the Singleton pattern:
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
// Static field holding the singleton instance
public static GameManager Instance { get; private set; }
// Enum defining game states
public enum GameState { Title, Playing, Paused, GameOver }
public GameState CurrentState { get; private set; }
// Global game data
public int Score { get; private set; }
private void Awake()
{
// Singleton pattern implementation
// If no instance exists, set this as the singleton
if (Instance == null)
{
Instance = this;
// Persist this object across scene loads
DontDestroyOnLoad(gameObject);
}
// If instance already exists, destroy this duplicate
else
{
Destroy(gameObject);
return;
}
// Set initial state
CurrentState = GameState.Title;
}
// Method to add score (callable from anywhere)
public void AddScore(int amount)
{
if (CurrentState != GameState.Playing) return;
Score += amount;
// UIManager.Instance.UpdateScoreUI(Score); // Request UI update
}
// Method to change game state
public void ChangeState(GameState newState)
{
if (CurrentState == newState) return;
CurrentState = newState;
// Execute state-specific processing
switch (newState)
{
case GameState.Title:
// Prepare title screen
break;
case GameState.Playing:
// Prepare for gameplay
Time.timeScale = 1f; // Resume time
break;
case GameState.Paused:
// Handle pause
Time.timeScale = 0f; // Stop time
break;
case GameState.GameOver:
// Handle game over
break;
}
}
// Example calls from other scripts:
// GameManager.Instance.AddScore(100);
// GameManager.Instance.ChangeState(GameManager.GameState.GameOver);
}
Usage
- Create an empty GameObject and name it "GameManager."
- Attach the
GameManager.csscript above. - Make this GameManager object a Prefab and place it in the starting scene (splash or title screen)—it will persist as the single instance throughout the game.
DontDestroyOnLoad(gameObject); is the crucial command that prevents the GameManager from being destroyed when scenes change.
Game Manager Considerations
While the Singleton pattern is convenient, overuse can cause problems:
- Tight Coupling: Since any script can access
GameManager.Instance, many objects may become strongly dependent on the GameManager, reducing code reusability and testability. - Bloated Responsibilities: Cramming all game logic into the GameManager quickly creates an unmanageable mega-class. Better design separates concerns into specialized managers—
ScoreManager,UIManager,AudioManager—with GameManager coordinating them.
Summary
The Game Manager is a powerful design pattern for organizing complex game structures.
- Acts as a "command center" centralizing game-wide state, rules, and global data.
- Uses
Singletonpattern to guarantee exactly one instance in the game. - Uses
DontDestroyOnLoad()to persist across scenes. - Defines game states with
enumand manages transitions withswitchstatements. - Consider splitting into specialized managers to prevent responsibility bloat.
Simply introducing this basic Game Manager structure dramatically organizes your codebase, making feature additions and debugging much easier.