Difficulty Level: 🌟 Beginner
Hey there, Unity developer! 👋
Let me guess - you’ve probably come across the singleton pattern in Unity tutorials, Stack Overflow answers, or maybe even in some Unity projects you’ve inherited. It seems so convenient, right? One global access point to your game manager, audio controller, or player data. What could go wrong?
Well… quite a lot, actually! 😅
💡 Don’t worry! If you’ve been using singletons in Unity, you’re not alone. Most Unity developers (myself included) go through a “singleton phase” early in their journey. The important thing is recognizing when it’s time to evolve your approach.
Here’s the thing about singletons in Unity: While they might solve immediate problems, they often create bigger, sneakier issues that only surface later in development. And by “later,” I mean when you’re trying to add that new feature your client just requested, or when you’re scratching your head wondering why your game crashes in seemingly random situations.
But don’t panic! There are much better alternatives that are actually easier to work with once you understand them. Let me show you why singletons cause headaches in Unity, and more importantly, what you should use instead.
What Is the Singleton Pattern?
Before we dive into the problems, let’s make sure we’re on the same page about what a singleton actually is.
The singleton pattern ensures that only one instance of a class exists throughout the entire application, and provides a global access point to that instance.
Here’s what a typical Unity singleton looks like:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public int playerScore = 0;
public bool gameIsPaused = false;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
public void AddScore(int points)
{
playerScore += points;
}
}
And then you’d use it like this from anywhere in your code:
// From any script, anywhere
GameManager.Instance.AddScore(100);
Looks pretty convenient, right? That’s exactly the trap! 🪤
The Problems with Singletons in Unity
1. Testing Becomes a Nightmare 😰
Let’s say you want to test a script that uses your GameManager singleton:
public class Enemy : MonoBehaviour
{
public void Die()
{
// This line makes testing really hard!
GameManager.Instance.AddScore(50);
Destroy(gameObject);
}
}
The problem: How do you test the Enemy.Die()
method without setting up the entire GameManager singleton? You can’t! Your test either fails because GameManager.Instance
is null, or you have to create a whole complex test setup just to test one simple method.
2. Hidden Dependencies (The Invisible Web) 🕸️
When you use singletons, it’s not obvious what a script depends on just by looking at it:
public class PlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// Hidden dependency! Not visible in the inspector
AudioManager.Instance.PlaySound("jump");
GameManager.Instance.AddScore(10);
UIManager.Instance.UpdateScoreDisplay();
}
}
}
The problem: Looking at this script in the inspector, you have no idea it depends on AudioManager, GameManager, and UIManager. This makes debugging and understanding your code much harder.
3. Scene Loading Chaos 🎭
Unity’s scene management and singletons don’t play well together:
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject); // What happens to references to this instance?
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // This can cause problems too!
}
The problems:
- What if other scripts already have references to the old instance when it gets destroyed?
DontDestroyOnLoad
objects can accumulate and cause memory leaks- Scene reloading in the editor can leave you with stale singleton instances
4. Initialization Order Problems ⏰
Consider this scenario:
public class AudioManager : MonoBehaviour
{
void Start()
{
// This might fail if GameManager isn't initialized yet!
bool soundEnabled = GameManager.Instance.soundEnabled;
SetupAudio(soundEnabled);
}
}
The problem: Unity’s script execution order isn’t guaranteed. AudioManager might try to access GameManager before GameManager’s Awake()
method runs, causing a null reference exception.
What to Use Instead: Better Unity Patterns
Option 1: ScriptableObjects (The Unity Way) ✨
ScriptableObjects are often the best alternative to singletons for shared data:
[CreateAssetMenu(fileName = "GameData", menuName = "Game/GameData")]
public class GameData : ScriptableObject
{
[Header("Player Stats")]
public int playerScore;
public int playerLives = 3;
[Header("Game Settings")]
public bool soundEnabled = true;
public float gameSpeed = 1.0f;
public void AddScore(int points)
{
playerScore += points;
}
public void ResetGame()
{
playerScore = 0;
playerLives = 3;
}
}
Now your scripts can reference this data directly:
public class PlayerController : MonoBehaviour
{
[SerializeField] private GameData gameData; // Visible dependency!
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
gameData.AddScore(10);
}
}
}
Why this is better:
- ✅ Dependencies are visible in the inspector
- ✅ Easy to test (just create a test GameData asset)
- ✅ No initialization order problems
- ✅ Data persists between scene loads automatically
- ✅ You can have multiple configurations (test data, level-specific data, etc.)
Option 2: Simple References (The Straightforward Way) 🎯
Sometimes the simplest solution is the best:
public class GameManager : MonoBehaviour
{
[Header("Game State")]
public int playerScore = 0;
public bool gameIsPaused = false;
public void AddScore(int points)
{
playerScore += points;
}
}
And then pass references where needed:
public class Enemy : MonoBehaviour
{
[SerializeField] private GameManager gameManager; // Clear dependency!
public void Die()
{
if (gameManager != null)
{
gameManager.AddScore(50);
}
Destroy(gameObject);
}
}
Why this works well:
- ✅ Super clear what depends on what
- ✅ Easy to test by passing in a mock GameManager
- ✅ No magic static references
- ✅ Works perfectly with Unity’s serialization system
Option 3: Simple Dependency Injection (For Advanced Beginners) 🔌
If you want to get a bit more sophisticated, you can create a simple service locator:
public class Services : MonoBehaviour
{
public static Services Instance { get; private set; }
[SerializeField] private GameManager gameManager;
[SerializeField] private AudioManager audioManager;
[SerializeField] private UIManager uiManager;
void Awake()
{
Instance = this;
}
public GameManager GetGameManager() => gameManager;
public AudioManager GetAudioManager() => audioManager;
public UIManager GetUIManager() => uiManager;
}
Then inject dependencies at start:
public class PlayerController : MonoBehaviour
{
private GameManager gameManager;
private AudioManager audioManager;
void Start()
{
// Get dependencies at startup
gameManager = Services.Instance.GetGameManager();
audioManager = Services.Instance.GetAudioManager();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && gameManager != null)
{
gameManager.AddScore(10);
audioManager?.PlaySound("jump");
}
}
}
When Singletons Are Actually OK 🤔
Real talk: There are a few cases where singletons aren’t terrible in Unity, but they’re rarer than you might think:
Input Management
public class InputManager : MonoBehaviour
{
public static InputManager Instance { get; private set; }
void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
This is somewhat acceptable because:
- Input is truly global
- It rarely needs to be mocked for testing
- It doesn’t hold game state
But even then, using Unity’s new Input System is usually better!
Migration Strategy: From Singletons to Better Patterns 🚀
If you have existing singleton code, here’s how to refactor it safely:
Step 1: Identify Your Singletons
List all the singleton classes in your project and categorize them:
- Data holders (GameManager, PlayerData) → Convert to ScriptableObjects
- Service providers (AudioManager, UIManager) → Use direct references
- Utilities (MathHelper, Extensions) → Make them static classes instead
Step 2: Convert One at a Time
Don’t try to refactor everything at once! Pick the singleton that causes you the most problems and convert it first.
Step 3: Update References Gradually
You can even have a transition period:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; } // Keep for now
[SerializeField] private GameData gameData; // New approach
void Awake()
{
Instance = this; // Remove this eventually
}
// Old method - mark as deprecated
[System.Obsolete("Use gameData.AddScore() instead")]
public void AddScore(int points)
{
gameData.AddScore(points);
}
}
Common Questions and Concerns 🙋♀️
Q: “But passing references everywhere is so much work!” A: It seems like more work at first, but it saves you huge amounts of debugging time later. Plus, Unity’s serialization system makes it pretty easy.
Q: “What about performance? Isn’t static access faster?” A: The performance difference is negligible. Clean, maintainable code is worth the tiny performance cost (which you probably won’t even measure).
Q: “I saw this pattern in a Unity tutorial…” A: Unfortunately, many Unity tutorials still teach singletons because they seem simpler for beginners. But they’re teaching bad habits that will bite you later.
Your Next Steps 📝
- Audit your current project: How many singletons do you have?
- Try ScriptableObjects: Create a simple data container for your next feature
- Practice dependency injection: Start passing references instead of accessing static instances
- Test your code: You’ll be amazed how much easier testing becomes!
Wrapping Up 🎉
The bottom line: Singletons in Unity are like that friend who’s fun to hang out with but creates drama wherever they go. They solve immediate problems but create bigger ones down the road.
Remember: Good code isn’t just code that works - it’s code that’s easy to understand, test, and modify. Your future self (and your teammates) will thank you for choosing better patterns.
Start small, be patient with yourself, and don’t try to refactor everything at once. Every singleton you replace with a better pattern is a step toward more maintainable Unity projects.
What pattern will you try first? ScriptableObjects are usually the easiest place to start, especially for game data and settings. Give it a shot in your next project!
Struggling with Unity architecture decisions? Contact us for a consultation - we’ve helped dozens of developers untangle their singleton-heavy codebases and build more maintainable Unity projects.

About Angry Shark Studio
Angry Shark Studio is a professional Unity AR/VR development studio specializing in mobile multiplatform applications and AI solutions. Our team includes Unity Certified Expert Programmers with extensive experience in AR/VR development.
Related Articles
More Articles
Explore more insights on Unity AR/VR development, mobile apps, and emerging technologies.
View All Articles