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

taskMgr (global TaskManager)

Event bus

messenger (global Messenger)

Rendering & window

base (global ShowBase instance)

Interval manager

ivalMgr

Asset loading

loader

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 ShowBase just to get a global: ShowBase.__init__() already registers itself as base; 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 lazy get() 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 = None pattern is trivially reset.