Observer¶
Intent: Let one system announce that something happened without knowing or caring who reacts.
Acron Mapping¶
Acron’s messenger + DirectObject implements the Observer pattern — you do not need to build it from scratch.
Note
Acron’s messenger is technically closer to what the book calls an event system: subjects send a named event object (a string + args) rather than calling observer methods directly on subjects. The book distinguishes this from the purist Observer pattern where observers register on a specific subject object. In practice the messenger’s approach is more flexible and is the right choice in Acron. The coupling trade-off is identical — subjects still don’t know who listens.
Pattern role |
Acron equivalent |
|---|---|
Subject (notifier) |
Any code that calls |
Observer |
Class inheriting |
Subscription |
|
Notification |
|
How to Implement¶
Identify the subject event — give it a descriptive string name:
'player-health-changed','enemy-died'.In the subject (physics, game logic, etc.) call
messenger.send('event-name', [arg1, arg2])at the moment the event occurs. No reference to observers needed.Create observer classes that inherit
DirectObject. In__init__(or wherever the observer starts caring) callself.accept('event-name', self._on_event).Implement the handler:
def _on_event(self, arg1, arg2): ...On teardown call
self.ignoreAll()to unsubscribe all at once.
Code Sketch (Python)¶
from direct.showbase.DirectObject import DirectObject
# Subject — knows nothing about observers
class HealthSystem:
def apply_damage(self, entity, amount):
entity.health -= amount
messenger.send('health-changed', [entity, entity.health])
if entity.health <= 0:
messenger.send('entity-died', [entity])
# Observer — UI
class HealthBar(DirectObject):
def __init__(self):
self.accept('health-changed', self._update)
def _update(self, entity, new_health):
# update bar graphic here
pass
def destroy(self):
self.ignoreAll()
# Observer — achievement system
class AchievementTracker(DirectObject):
def __init__(self):
self.accept('entity-died', self._check_achievements)
def _check_achievements(self, entity):
if entity.tag == 'boss':
messenger.send('achievement-unlocked', ['boss-slayer'])
def destroy(self):
self.ignoreAll()
Pitfalls¶
Observer leak: forgetting
self.ignoreAll()indestroy()keeps the handler registered after the object is garbage-collected, causing errors on next event.Event name collisions: use namespaced strings (
'audio:music-stopped','ui:menu-opened') to prevent unrelated systems accidentally sharing a name.No guaranteed order:
messengercalls handlers in registration order. If two observers must run in a specific sequence, chain events or use a single coordinating handler.