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 messenger.send('event-name', [args])

Observer

Class inheriting DirectObject; calls self.accept('event-name', handler)

Subscription

self.accept() / self.ignore() / self.ignoreAll()

Notification

messenger.send()

How to Implement

  1. Identify the subject event — give it a descriptive string name: 'player-health-changed', 'enemy-died'.

  2. 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.

  3. Create observer classes that inherit DirectObject. In __init__ (or wherever the observer starts caring) call self.accept('event-name', self._on_event).

  4. Implement the handler: def _on_event(self, arg1, arg2): ...

  5. 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() in destroy() 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: messenger calls handlers in registration order. If two observers must run in a specific sequence, chain events or use a single coordinating handler.