Event Handlers

Events occur either when the user does something (such as clicking a mouse or pressing a key) or when sent by the script using messenger.send(). When an event occurs, Panda’s “messenger” will check to see if you have written an “event handler” routine. If so, your event handler will be called. The messenger system is object-oriented, to create an event handler, you have to first create a class that inherits from DirectObject. Your event handler will be a method of your class.

Defining a class that can Handle Events

The first step is to import the DirectObject module:

from direct.showbase import DirectObject

With DirectObject loaded, it is possible to create a subclass of DirectObject. This allows the class to inherit the messaging API and thus listen for events.

class myClassName(DirectObject.DirectObject):

The sample below creates a class that can listen for events. The “accept” function notifies panda that the printHello method is an event handler for the mouse1 event. The “accept” function and the various event names will be explained in detail later.

class Hello(DirectObject.DirectObject):
    def __init__(self):
        self.accept('mouse1', self.printHello)

    def printHello(self):
        print('Hello!')

h = Hello()

Event Handling Functions

Events first go to a mechanism built into panda called the “Messenger.” The messenger may accept or ignore events that it receives. If it accepts an event, then an event handler will be called. If ignored, then no handler will be called.

An object may accept an event an infinite number of times or accept it only once. If checking for an accept within the object listening to it, it should be prefixed with self. If the accept command occurs outside the class, then the variable the class is associated with should be used.

myDirectObject.accept('Event Name', myDirectObjectMethod)
myDirectObject.acceptOnce('Event Name', myDirectObjectMethod)

Specific events may be ignored, so that no message is sent. Also, all events coming from an object may be ignored.

myDirectObject.ignore('Event Name')
myDirectObject.ignoreAll()

Finally, there are some useful utility functions for debugging. The messenger typically does not print out when every event occurs. Toggling verbose mode will make the messenger print every event it receives. Toggling it again will revert it to the default. A number of methods exist for checking to see what object is checking for what event, but the print method will show who is accepting each event. Also, if accepts keep changing to the point where it is too confusing, the clear method will start the messenger over with a clear dictionary.

messenger.toggleVerbose()
print(messenger)
messenger.clear()

Sending Custom Events

Custom events can be sent by the script using the code

messenger.send('Event Name')

A list of parameters can optionally be sent to the event handler. Parameters defined in accept() are passed first, and then the parameters defined in send(). for example this would print out “eggs sausage foo bar”:

class Test(DirectObject):
    def __init__(self):
        self.accept('spam', self.on_spam, ['eggs', 'sausage'])

    def on_spam(self, a, b, c, d):
        print(a, b, c, d)

test = Test()
messenger.send('spam', ['foo', 'bar'])
base.run()

A Note on Object Management

When a DirectObject accepts an event, the messenger retains a reference to that DirectObject. To ensure that objects that are no longer needed are properly disposed of, they must ignore any messages they are accepting.

For example, the following code may not do what you expect:

import direct.directbase.DirectStart
from direct.showbase import DirectObject
from panda3d.core import *

class Test(DirectObject.DirectObject):
    def __init__(self):
        self.accept("FireZeMissiles", self._fireMissiles)

    def _fireMissiles(self):
        print("Missiles fired! Oh noes!")

foo = Test() # create our test object
del foo      # get rid of our test object

messenger.send("FireZeMissiles") # oops! Why did those missiles fire?
base.run()

Try the example above, and you’ll find that the missiles fire even though the object that would handle the event had been deleted.

One solution (patterned after other parts of the Panda3D architecture) is to define a “destroy” method for any custom classes you create, which calls “ignoreAll” to unregister from the event-handler system.

import direct.directbase.DirectStart
from direct.showbase import DirectObject
from panda3d.core import *

class Test(DirectObject.DirectObject):
    def __init__(self):
        self.accept("FireZeMissiles", self._fireMissiles)

    def _fireMissiles(self):
        print("Missiles fired! Oh noes!")

    # function to get rid of me
    def destroy(self):
        self.ignoreAll()

foo = Test()  # create our test object
foo.destroy() # get rid of our test object

del foo

messenger.send("FireZeMissiles") # No missiles fire
base.run()

Coroutine Event Handlers

It is permissible for any event handler to be a coroutine (i.e. marked as an async def), which permits use of the await keyword inside the handler. Usage is otherwise identical to a regular event handler.

class Test(DirectObject):
    def __init__(self):
        self.accept('space', self.on_space)

    async def on_space(self):
        await Task.pause(1.0)
        print("The space key was pressed one second ago!")