Singleton¶
Intent: Ensure a class has one instance, and provide a global point of access to it.
Warning
The book Game Programming Patterns dedicates this chapter to explaining why you should not use this pattern. Unlike every other chapter, which shows you how to use a pattern, this one shows you how to avoid one. Singletons are effectively global variables — they couple every part of the codebase to a concrete class and make testing, refactoring, and multi-threading much harder. Use the alternatives below instead.
Acron Mapping¶
Acron already provides the canonical singletons you’ll need. Avoid creating new singletons; reach for these instead:
Need |
Acron built-in |
|---|---|
Task scheduling |
|
Event bus |
|
Rendering & window |
|
Interval manager |
|
Asset loading |
|
When You Think You Need a Singleton¶
Before reaching for a singleton, ask: can I just pass the object to the code that needs it? Dependency injection (passing the object as a parameter or constructor argument) keeps the dependency visible, testable, and replaceable. The book strongly recommends this approach first.
If you genuinely need one instance accessible everywhere and passing it everywhere is impractical, use a module-level variable — Python’s module system guarantees a module is imported once, making it the natural singleton mechanism without the classic pitfalls.
How to Implement (module singleton)¶
Create systems/audio_manager.py:
# Module-level instance — created once on first import
_instance = None
def get():
global _instance
if _instance is None:
_instance = AudioManager()
return _instance
In any game system:
from systems import audio_manager
audio_manager.get().play('bgm')
To swap implementations in tests, simply reassign audio_manager._instance.
Code Sketch (Python)¶
# systems/score_manager.py
class ScoreManager:
def __init__(self):
self._score = 0
def add(self, points):
self._score += points
messenger.send('score-changed', [self._score])
@property
def score(self):
return self._score
_instance = None
def get() -> ScoreManager:
global _instance
if _instance is None:
_instance = ScoreManager()
return _instance
# Usage anywhere in the game
from systems import score_manager
score_manager.get().add(100)
Pitfalls¶
Don’t subclass
ShowBasejust to get a global:ShowBase.__init__()already registers itself asbase; adding more state there creates a god object. Use separate module singletons per domain.Order of initialization: module singletons created at import time may not yet have access to
base,taskMgr, etc. Use a lazyget()factory (shown above) so initialization is deferred to first use.Testing: a true class-level singleton (with
__new__tricks) is hard to reset between tests. The module-level_instance = Nonepattern is trivially reset.