Event Handling
Events are fairly simple idea. We want our game to perform its tasks regardless of user input. Imagine game which force user to constantly move mouse in order to just work!
However it mean that user can interact at any given point with our game. When we are displaying main menu, when we are painting our next frame, or computing game logic.
In order to store user input for letter use, we will (or more precisely PyGame will) put them in containers which we will call Events. Each event store data specific to one type of event (like mouse button pressed, key down, etc), and for only one user generated input. So if user click on multiple keys, we will get multiple events. If user press 'B' multiple times, we will get multiple events, etc.
To enable event handling in our games we will create one class, that have python functions for every type of events, and one which will call them depending on the type of event.
Code
Save it as cevent.py.
import pygame from pygame.locals import * import pygame from pygame.locals import * class CEvent: def __init__(self): pass def on_input_focus(self): pass def on_input_blur(self): pass def on_key_down(self, event): pass def on_key_up(self, event): pass def on_mouse_focus(self): pass def on_mouse_blur(self): pass def on_mouse_move(self, event): pass def on_mouse_wheel(self, event): pass def on_lbutton_up(self, event): pass def on_lbutton_down(self, event): pass def on_rbutton_up(self, event): pass def on_rbutton_down(self, event): pass def on_mbutton_up(self, event): pass def on_mbutton_down(self, event): pass def on_minimize(self): pass def on_restore(self): pass def on_resize(self,event): pass def on_expose(self): pass def on_exit(self): pass def on_user(self,event): pass def on_joy_axis(self,event): pass def on_joybutton_up(self,event): pass def on_joybutton_down(self,event): pass def on_joy_hat(self,event): pass def on_joy_ball(self,event): pass def on_event(self, event): pass if __name__ == "__main__" : event = CEvent()
Explanation
I think code is self-explanatory. I will say only that routines on_joy_* will be introduced in distinct tutorial if there will be demand for it. All others will be introduced through out this tutorials.
One to rule them all
So far we have lots of methods how to chain them all ? How to make one to rule them all ;) ?
Change on_event into following:
def on_event(self, event): if event.type == QUIT: self.on_exit() elif event.type >= USEREVENT: self.on_user(event) elif event.type == VIDEOEXPOSE: self.on_expose() elif event.type == VIDEORESIZE: self.on_resize(event) elif event.type == KEYUP: self.on_key_up(event) elif event.type == KEYDOWN: self.on_key_down(event) elif event.type == MOUSEMOTION: self.on_mouse_move(event) elif event.type == MOUSEBUTTONUP: if event.button == 0: self.on_lbutton_up(event) elif event.button == 1: self.on_mbutton_up(event) elif event.button == 2: self.on_rbutton_up(event) elif event.type == MOUSEBUTTONDOWN: if event.button == 0: self.on_lbutton_down(event) elif event.button == 1: self.on_mbutton_down(event) elif event.button == 2: self.on_rbutton_down(event) elif event.type == ACTIVEEVENT: if event.state == 1: if event.gain: self.on_mouse_focus() else: self.on_mouse_blur() elif event.state == 2: if event.gain: self.on_input_focus() else: self.on_input_blur() elif event.state == 4: if event.gain: self.on_restore() else: self.on_minimize()
Usage
Now that we have laid ground work, we can use it!
CApp should inherit after CEvent, so all functions in CEvent are available in CApp.
Then we just need to change event handling functions we need to.
Here is simple example which will use our new event handling code to handle exit event:
#add in import section import cevent # change CApp into class CApp(cevent.CEvent): #... #... delete on_event(): !!!!!!!!!! #add on_exit(self) to CApp: def on_exit(self): self._running = False
Now our code is a bit tricky! Python will not find on_event in App, so it will use it's parent's class (CEvent) method on_event. But on_event will call on_exit from App class!
How CEvent can know about our on_exit() function in App class? I can not!
How ever we have told python that App is inheriting after CEvent, so Python will do few things for us:
- will provide every method from CEvent as if they where App's own methods (just like with on_event)
- will make sure that methods defined in App will be preferred over CEvent methods (just like with on_exit)
Professional programmers call this pattern "Template method". Because if you look at what we have done, then you will realize that our on_event function is template, with places (like on_exit) that can be changed letter.
Hi! I found this article quite useful, so: thanks! However, there a two minor issues - nothing wild, but anyway:
1. 'on_mouse_wheel' in CEvent.py is never called by any event dispatched by the method 'on_event'
2. in 'on_event' method: In the two cases 'event.type == MOUSEBUTTONUP' and ' … == MOUSEBUTTONDOWN' event.key should be 1 for left mouse button (not 0), 2 for middle and 3 for right (and not 1,2 respectively); this one confused me a while, when using your code.
This is a great tutorial. Really helped, thanks!
Good, thanks! Also see www.teachyourselfpython.com and inventwithpython
Nice one.
The code is *not* self-explanatory! However, I will give an explanation and you are welcome to include it in a future revision.
cevent.py defines a lot of subroutines that are only called, they don't actually do anything, they are just placeholders. If one of those subroutines is called (when one of those events happens) and that subroutine is not present, then the program will crash It is better that an event is ignored than a program crash.
Any python program that imports cevent and then defines a method with the same name as a method in cenvent.py will use the method it defined. This is called "overriding", as the subroutine for the event that your program used will override the the subroutine already defined in cevent.py.
If you are testing your software and you want to make sure that every event is handled or there is an error, then replace "pass" in cevent.py with "raise NotImplementedError(SOME EXPLANATION STRING HERE)". That will raise the NotImplementedError exception and create a nice traceback that you can follow to find out what the error was.