Handling events more elegantly

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.

Getting ready

Follow the tasks of Setting up the game structure found in Chapter 1 prior to continuing with the current recipe.

How to do it...

The following are your tasks for this recipe:

  1. Add a new file called GameObject.py to your project.
  2. Insert the following source code into 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()
    
  3. Open 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()
    
  4. Hit F6 to run the program.

How it works...

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.

There's more...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset