Pygame modules for interactive programs

Mouse Movement and Buttons

Like the keyboard, the mouse does not require any special setup for events to appear on the queue. A companion function, pygame.mouse.get_focused(), works the same as its keyboard counterpart. If the Pygame window is receiving mouse events, this function will return True; otherwise, it will return False.

The pygame.mouse module also allows you to set the position of the mouse pointer, make the pointer visible or invisible, and set the icon displayed as the mouse pointer. You can also use pygame.mouse.get_pos() to get the current position of the mouse pointer or pygame.mouse.get_rel() to get the amount of relative movement since the last call to pygame.mouse.get_rel(). When a mouse event appears on the event queue, both absolute and relative position are provided as additional parameters, so it's often not necessary to call these functions directly.

The underlying OS must support the mouse functions (e.g., hiding the cursor) for the Pygame functions to have any effect.

Joysticks

Pygame supports a wide range of joysticks and their assorted input styles. Gamepads, "classic" controllers, and traditional joysticks all fall into the joystick category in Pygame. A joystick will usually have at least two axes, returning analog values ranging from -1 to 1, and at least one button that provides a True or False (pressed or not pressed). The device may also contain trackballs that provide relative movement in x and y directions, as well as hats, which are four-way switches providing digital up/down/left/right input.

Because joysticks can contain any number of these axes, buttons, balls, and hats, the pygame.joystick module provides a method to query them for their capabilities.

The JoystickTest.py tool (Listing 6, at the end of the article; Figure 1) examines joysticks and their inputs. When run, it presents a list of joysticks currently connected to the system and allows the user to select the joystick to test. This choice is made in the console window rather than inside Pygame. Once a joystick is selected, the Pygame window is launched, and the test begins.

Figure 1: The joystick utility shows the inputs provided by a Logitech Extreme 3D joystick. This joystick has no trackballs, so the lower left quadrant is empty.

Listing 6

Joystick Test Utility

001 import pygame
002 import sys
003
004 class mainDisplay:
005    def __init__ ( self , screen , joystick ):
006       # Setup class variables
007       self.screen = screen
008       self.width = screen.get_width()
009       self.height = screen.get_height()
010       self.joystick = joystick
011
012       # Draw dividing lines onto the window
013       pygame.draw.line ( self.screen , ( 0 , 255 , 0 ) , ( self.width / 2 , 0 ) , ( self.width / 2 , self.height ) , 1 )
014       pygame.draw.line ( self.screen , ( 0 , 255 , 0 ) , ( 0 , self.height / 2 ) , ( self.width , self.height / 2 ) , 1 )
015
016       # Initialize the font module to label things
017       pygame.font.init()
018       self.labelFont = pygame.font.SysFont ( "" , 15 , False , False )
019
020       # Get the number of each type of input from the joystick
021       self.axisCount = joystick.get_numaxes()
022       self.buttonCount = joystick.get_numbuttons()
023       self.ballCount = joystick.get_numballs()
024       self.hatCount = joystick.get_numhats()
025
026       # Initialize the plotting functions for each type of input
027       self.joyAxes = dict()
028       for i in range ( self.axisCount ):
029          self.joyAxes [ i ] = 0
030       self.plotAxes()
031
032       self.joyButtons = dict()
033       for i in range ( self.buttonCount ):
034          self.joyButtons [ i ] = False
035       self.plotButtons()
036
037       for i in range ( self.hatCount ):
038          self.plotHat ( i , ( 0 , 0 ) )
039
040       self.ballLocations = dict()
041       for i in range ( self.ballCount ):
042          self.ballLocations [ i ] = list()
043          self.ballLocations [ i ].append ( 0 )
044          self.ballLocations [ i ].append ( 0 )
045          self.plotBalls ( i , ( 0 , 0 ) )
046
047    def meter ( self , position , size , label , value ):
048       # The meter function plots a value on a horizontal scale
049       # Variables:
050       # position -- a tuple where the meter should be plotted on screen
051       # size -- the width of the meter (height is always 30 pixels)
052       # label -- the text to label this meter
053       # value -- a float ranging from -1 to 1 to show on the meter (0 is center)
054
055       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( position [ 0 ] - 2 , position [ 1 ] , size + 4 , 30 ) , 0 )
056       caption = self.labelFont.render ( label , True , ( 0 , 255 , 0 ) )
057
058       self.screen.blit ( caption , position )
059       pygame.draw.rect ( self.screen , ( 0 , 255 , 0 ) , ( position [ 0 ] , position [ 1 ] + 14 , size , 2 ) , 0 )
060       pointerX = ( size / 2 ) + ( size * ( value / 2 ) ) - 1 + position [ 0 ]
061       pygame.draw.rect ( self.screen , ( 0 , 255 , 0 ) , ( pointerX , position [ 1 ] , 2 , 30 ) , 0 )
062
063    def plotAxes ( self ):
064       plotLocation = 30         # Initialize the location of the axes plot
065       for axis , value in self.joyAxes.items():         # Loop over all of the joystick axes
066          axisValue = self.joystick.get_axis ( axis )    # Get the value of the axis in question
067          self.meter ( ( 25 , plotLocation ) , 350 , "Axis " + str ( axis ) , axisValue )        # Call the meter function to plot the value
068          plotLocation += 35     # Update the plot location for the next axis
069       pygame.display.flip()     # Flip the display
070
071    def plotButtons ( self ):
072       # Initialize the button plots
073       buttonX = 425
074       buttonY = 25
075
076       # Clear the quadrant
077       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( buttonX , buttonY , 400 , 274 ) , 0 )
078
079       # Loop over all of the buttons and plot their current status
080       for i in range ( self.buttonCount ):
081          if self.joyButtons [ i ] == True:
082             pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( buttonX + 25 , buttonY + 25 ) , 25 , 0 )     # Button is pressed -- fill the circle
083          else:
084             pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( buttonX + 25 , buttonY + 25 ) , 25 , 1 )     # Button is not pressed -- empty circle
085
086          # Label the button
087          label = self.labelFont.render ( "Button " + str ( i ) , True , ( 0 , 255 , 0 ) )
088          self.screen.blit ( label , ( buttonX , buttonY + 50 ) )
089
090          # Update the button locations
091          buttonX += 55
092          if buttonX >= 750:
093             buttonX = 425
094             buttonY += 75
095
096       pygame.display.flip()     # Flip the display
097
098    def plotHat ( self , hat , value ):
099       # Initialize the hat display
100       hatX = int ( hat / 4 ) * 100 + 405
101       hatY = int ( hat % 4 ) * 125 + 305
102
103       # Erase the last drawing
104       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( hatX , hatY , 100 , 130 ) , 0 )
105
106       # Label the hat
107       label = self.labelFont.render ( "Hat " + str ( hat ) , True , ( 0 , 255 , 0 ) )
108       self.screen.blit ( label , ( hatX + 30 , hatY + 100 ) )
109
110       # Hat left / right
111       if value [ 0 ] == -1:
112          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 25 , hatY + 50 ) , 10 , 0 )      # Hat Left Active -- Draw a filled circle
113          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 75 , hatY + 50 ) , 10 , 1 )      # Hat Right Not Active -- Draw an empty circle
114       elif value [ 0 ] == 1:
115          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 25 , hatY + 50 ) , 10 , 1 )      # Hat Left Not Active -- Draw an empty circle
116          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 75 , hatY + 50 ) , 10 , 0 )      # Hat Right Active -- Draw a filled circle
117       else:
118          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 75 , hatY + 50 ) , 10 , 1 )      # Hat Right Not Active -- Draw an empty circle
119          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 25 , hatY + 50 ) , 10 , 1 )      # Hat Left Not Active -- Draw an empty circle
120
121       # Hat up / down
122       if value [ 1 ] == -1:
123          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 50 , hatY + 75 ) , 10 , 0 )      # Hat Down Active -- Draw a filled circle
124          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 50 , hatY + 25 ) , 10 , 1 )      # Hat Up Not Active -- Draw an empty circle
125       elif value [ 1 ] == 1:
126          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 50 , hatY + 75 ) , 10 , 1 )      # Hat Down Not Active -- Draw an empty circle
127          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 50 , hatY + 25 ) , 10 , 0 )      # Hat Up Active -- Draw a filled circle
128       else:
129          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 50 , hatY + 25 ) , 10 , 1 )      # Hat Up Not Active -- Draw an empty circle
130          pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( hatX + 50 , hatY + 75 ) , 10 , 1 )      # Hat Down Not Active -- Draw an empty circle
131
132       pygame.display.flip()
133
134    def plotBalls ( self , ball , position ):
135       ballX = int ( ball / 4 ) * 100 + 5
136       ballY = int ( ball / 4 ) * 125 + 5
137
138       # Clear the old plot
139       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( ballX , ballY , 100 , 130 ) , 0 )
140
141       # Update the ball location
142       self.ballLocations [ ball ] [ 0 ] += position [ 0 ]       # index 0 is the X axis
143       self.ballLocations [ ball ] [ 1 ] += position [ 1 ]       # index 1 is the Y axis
144
145       # Keep the ball locations "in bounds"
146       if self.ballLocations [ ball ] [ 0 ] < -45: self.ballLocations [ ball ] [ 0 ] = -45
147       if self.ballLocations [ ball ] [ 0 ] > 45: self.ballLocations [ ball ] [ 0 ] = 45
148       if self.ballLocations [ ball ] [ 1 ] < -45: self.ballLocations [ ball ] [ 1 ] = -45
149       if self.ballLocations [ ball ] [ 1 ] > 45: self.ballLocations [ ball ] [ 1 ] = 45
150
151       # Draw a bounding box
152       pygame.draw.rect ( self.screen , ( 0 , 255 , 0 ) , ( ballX , ballY , 100 , 100 ) , 1 )
153
154       # Plot the ball location in the box
155       pygame.draw.circle ( self.screen , ( 0 , 255 , 0 ) , ( ballX + self.ballLocations [ ball ] [ 0 ] , ballY + self.ballLocations [ ball ] [ 1 ] ) , 2 , 0 )
156
157       # Label the box
158       label = self.labelFont.render ( "Ball " + str ( ball ) , True , ( 0 , 255 , 0 ) )
159       self.screen.blit ( label , ( ballX + 30 , ballY + 100 ) )
160
161       # Flip the screen
162       pygame.display.flip()
163
164    def run ( self ):
165       # The Pygame event loop
166
167       running = True    # This is the main "loop running" variable -- set to false to exit the loop
168       while running:    # Loop until "running" becomes false
169          for event in pygame.event.get():       # Get all of the events from the queue
170             if event.type == pygame.JOYAXISMOTION:              # Main axis movement
171                self.joyAxes [ event.axis ] = event.value
172                self.plotAxes()
173             elif event.type == pygame.JOYBUTTONDOWN:            # Buttons pressed
174                self.joyButtons [ event.button ] = True
175                self.plotButtons()
176             elif event.type == pygame.JOYBUTTONUP:              # Buttons released
177                self.joyButtons [ event.button ] = False
178                self.plotButtons()
179             elif event.type == pygame.JOYHATMOTION:             # Change in hat position
180                self.plotHat ( event.hat , event.value )
181             elif event.type == pygame.JOYBALLMOTION:            # Change in trackball position
182                self.plotBalls ( event.ball , event.rel )
183
184 # Setup the joysticks
185 pygame.joystick.init()
186 stickCount = pygame.joystick.get_count()        # How many joysticks are connected?
187
188 for index in range ( stickCount ):              # Print the name of each joystick
189    joystick = pygame.joystick.Joystick ( index )
190    print ( "{0}) {1}".format ( index , joystick.get_name() ) )
191
192 # Get the user's selection, and exit if they just press enter
193 selected = input ( "Enter a joystick number or just Enter to exit:" )
194 if selected == "": sys.exit
195
196 # Convert the selection into an integer
197 index = int ( selected )
198
199 # Initialize the selected joystick
200 joystick = pygame.joystick.Joystick ( index )
201 joystick.init()
202
203 # Initialize the pygame display
204 pygame.display.init()
205 screen = pygame.display.set_mode ( ( 800 , 600 ) )
206 pygame.display.set_caption ( "Joystick tester" )
207
208 # Initialize the display class
209 window = mainDisplay ( screen , joystick )
210
211 # Start the main loop
212 window.run()

This tool is useful not only to test the functionality of hardware but to see how Pygame interprets each type of input. Sometimes it can be difficult to tell whether a set of buttons on a joystick operate as buttons or as a hat input. The tool also shows you the index number of each axis, button, hat, or trackball.

Each quadrant of the screen represents different types of joystick inputs. Axes are shown in the upper left quadrant as meters with 0 in the center. Moving each axis of the joystick adjusts the meters appropriately. If an axis does not cause its associated meter to move, you likely have a hardware problem with the joystick.

Joystick buttons are shown in the upper right quadrant. Solid circles represent pressed buttons, and empty circles represent non-pressed buttons.

Hats are in the lower right quadrant. The display mirrors the up/down/left/right diamond configuration of the four buttons as they appear on the joystick. Filled circles represent the hat pushed in that direction. Note that it is possible for a hat to display a combination, such as up and right or down and left, but not up and down or left and right simultaneously.

Trackballs are plotted in the lower left quadrant as squares with a small dot cursor. Each ball allows the cursor to be positioned within its box. Because I do not have any joysticks or gamepads with trackballs, this portion of the code is not tested beyond proper syntax. Multiple joysticks, even if identical (Figure 2), still show up as separate devices.

Figure 2: Two identical joysticks and the joystick utility's selection menu when more than one joystick is attached.

Buy this article as PDF

Express-Checkout as PDF

Pages: 2

Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content