Although Panda3D's event system is a great way for passing messages between objects, there are some things you should know before you are going to use it in your game. This recipe will show you an even more elegant way of integrating events into your code. You will learn how to use the introspection facilities of the Python language to create an annotation that marks a method as an event handler. Additionally, this article will discuss some problems you might encounter when using the messaging system of Panda3D.
Follow the tasks of Setting up the game structure found in Chapter 1 prior to continuing with the current recipe.
The following are your tasks for this recipe:
GameObject.py
to your project. GameObject.py:
from direct.showbase.DirectObject import DirectObject def handle_event(event): def inner_event(func): func.event_name = event return func return inner_event class GameObject(DirectObject): def __init__(self): for attrib in dir(self): method = getattr(self, attrib) if callable(method) and hasattr(method, 'event_name'): self.accept(method.event_name, method) def destroy(self): self.ignoreAll()
Application.py
and add this following code to the file:from direct.showbase.ShowBase import ShowBase from direct.interval.IntervalGlobal import * from GameObject import GameObject from GameObject import handle_event from panda3d.core import * class Sender(GameObject): def start(self): smiley = loader.loadModel("smiley") pause = Sequence(Wait(5), Func(messenger.send, "smiley-done", [smiley])) pause.start() class Receiver(GameObject): @handle_event("smiley-done") def showSmiley(self, smiley): smiley.reparentTo(render) messenger.send("smiley-shown") class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.accept("smiley-shown", self.clean) self.cam.setPos(0, -10, 0) self.rec = Receiver() snd = Sender() snd.start() def clean(self): self.ignore("smiley-shown") self.rec.destroy()
This recipe introduces the @handle_event()
decorator that allows you to mark methods to be event handlers for a given message. The decorator adds a new attribute called event_name
to the method. The constructor of the GameObject
class then iterates over all member methods, looking for this attribute and automatically calling accept()
for the given event name and handler method. This can help a lot to keep code cleaner, especially in classes that listen for a lot of events. Instead of filling the constructor with calls to accept()
to subscribe to messages, we can now simply mark a method to be an event handler, making its purpose clearer when reading the code.
One concept that this recipe is trying to teach you is that subscribing to a message has one slight side effect—it adds the object that registers for a kind of event to a list of listeners, thus increasing the object's reference count. This can result in surprising behavior and annoying bugs. Because of the entry in the list of subscribers, the reference count will never reach zero, keeping the object on reacting to events.
The effect of this can lead to the following situation: Let's suppose one of your game objects listens for an event called "explode", that displays a spectacular explosion on the screen and removes the object that was blown up from the scene graph. A few moments later, there occurs another "explode" event, but suddenly there's an explosion showing where it shouldn't. This is because the object from the first explosion still exists, eagerly listening for the order to explode.
To prevent this from happening, whenever an object that accepts messages isn't needed anymore, you should use ignoreAll()
to stop listening for events. This will remove it from the subscriber list, allowing its reference count to reach zero so it can ultimately be deallocated.
Panda3D's messaging system works within the boundary of a frame. This means that if you send a message in the first frame, all objects listening for that message will be reacting to it in the second frame.
This is usually sufficient for handling the needs of gameplay code, but should you somehow need to have an object react to a message immediately, or even multiple times within a frame, you will need to provide your own facilities or resort to Python libraries like PubSub
or PyDispatcher
.