Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added joystick support. #115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions doc/builtins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,53 @@ This could be used in a Pygame Zero program like this::

def on_mouse_down():
beep.play()


Joysticks
---------

.. versionadded:: TBA

If you have joysticks connected and configured correctly, they should appear in
the ``joysticks`` list. Pygame Zero will automatically initialize them, and you
can access each joystick by number like this ``joysticks[1]`` (starting with
``0``).

.. class:: Joystick
.. method:: get_numbuttons()

Return the number of buttons on the joystick.

.. method:: getbutton(button)

Check whether a button is currently pressed.

:param button: The number of the button, starting at ``0``.

.. method:: get_numaxes()

Return the number of axes on the joystick. Normally at least two, one
for the left/right direction, and another for up/down.

.. method:: get_axis(axis_number)

Return a value for how far the joystick axis is moved, from -1 to 1.
The value will be negative for one direction, and positive for the
other. It might also be less than 1, depending on your joystick.

You can use them in a Pygame Zero program like this::

def on_joy_button_down(joy, button):
if button == joybutton.TWO: # jump
direction = (joysticks[joy].get_axis(0),
joysticks[joy].get_axis(1))
# the joystick number will tell us which player jumped
player[joy].jump(direction)

def on_joy_axis_motion(joy, axis, value):
if joysticks[joy].getbutton(SNEAK):
player[joy].set_sneaking()
direction = (joysticks[joy].get_axis(0),
joysticks[joy].get_axis(1))
player[joy].handle_movement(direction)

61 changes: 61 additions & 0 deletions doc/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,35 @@ To handle mouse drags, use code such as the following::
Note that this will not be called if the track is configured to loop.


.. function:: on_joy_button_down([joy], [button])

Called when a joystick button is pressed.

:param joy: An integer indicating the joystick that had its button pressed.
:param button: A :class:`joystick` enum value indicating the button that was
pressed.

.. function:: on_joy_button_up([joy], [button])

Called when a joystick button is released.

:param joy: An integer indicating the joystick that had its button released.
:param button: A :class:`joystick` enum value indicating the button that was
pressed.

.. function:: on_joy_axis_motion([joy], [axis], [value])

Called when a joystick axis is moved.

:param joy: An integer indicating the joystick that had its axis changed.
:param button: A :class:`axis` enum value indicating the axis that was
moved. Normally axis 0 is X and axis 1 is Y, and there may
be others if you use a gamepad with two sticks or shoulder
buttons.
:param value: The current position of the joystick axis. This will normally
be a floating point number ranging from -1 to +1, but may be
lower depending on the joystick.

.. _buttons-and-keys:

Buttons and Keys
Expand Down Expand Up @@ -352,3 +381,35 @@ Additionally you can access a set of constants that represent modifier keys:
.. attribute:: CAPS
.. attribute:: MODE

Joysticks have constants for both buttons and axes:

.. class:: joystick

A built-in enumeration of buttons that can be received by the ``on_joy_button_*``
handlers.

.. attribute:: ZERO
.. attribute:: ONE
.. attribute:: TWO
.. attribute:: THREE
.. attribute:: FOUR
.. attribute:: FIVE
.. attribute:: SIX
.. attribute:: SEVEN
.. attribute:: EIGHT
.. attribute:: NINE
.. attribute:: TEN
.. attribute:: ELEVEN
.. attribute:: TWELVE

.. class:: axis

The axes that are on the joystick. Note that joystick axes vary a lot, so these
might be mislabelled for your joystick.

.. attribute:: X
.. attribute:: Y
.. attribute:: ALT_X
.. attribute:: ALT_Y
.. attribute:: FOUR
.. attribute:: FIVE
3 changes: 2 additions & 1 deletion pgzero/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from .keyboard import keyboard
from .animation import animate
from .rect import Rect, ZRect
from .runner import joysticks

from .loaders import images, sounds

from .constants import mouse, keys, keymods
from .constants import mouse, keys, keymods, joybutton, axis

from .game import exit
24 changes: 24 additions & 0 deletions pgzero/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ class mouse(IntEnum):
WHEEL_DOWN = 5


class joybutton(IntEnum):
ZERO = 0
ONE = 1
TWO = 2
THREE = 3
FOUR = 4
FIVE = 5
SIX = 6
SEVEN = 7
EIGHT = 8
NINE = 9
TEN = 10
ELEVEN = 11
TWELVE = 12

class axis(IntEnum):
X = 0
Y = 1
ALT_X = 2
ALT_Y = 3
FOUR = 4
FIVE = 5


# Use a code generation approach to copy Pygame's key constants out into
# a Python 3.4 IntEnum, stripping prefixes where possible
srclines = ["class keys(IntEnum):"]
Expand Down
21 changes: 17 additions & 4 deletions pgzero/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ def show_default_icon():
pygame.MOUSEMOTION: 'on_mouse_move',
pygame.KEYDOWN: 'on_key_down',
pygame.KEYUP: 'on_key_up',
constants.MUSIC_END: 'on_music_end'
constants.MUSIC_END: 'on_music_end',
pygame.JOYAXISMOTION: 'on_joy_axis_motion',
pygame.JOYBALLMOTION: 'on_joy_ball_motion', # trackball
pygame.JOYBUTTONDOWN: 'on_joy_button_down',
pygame.JOYBUTTONUP: 'on_joy_button_up',
pygame.JOYHATMOTION: 'on_joy_hat_motion',
}

def map_buttons(val):
Expand All @@ -109,7 +114,9 @@ def map_buttons(val):
EVENT_PARAM_MAPPERS = {
'buttons': map_buttons,
'button': constants.mouse,
'key': constants.keys
'key': constants.keys,
'joybutton': constants.joybutton,
'axis': constants.axis,
}

def load_handlers(self):
Expand Down Expand Up @@ -145,8 +152,13 @@ def make_getter(mapper, getter):

param_handlers = []
for name in param_names:
# joystick hack
if name == 'button' and 'joy' in param_names:
mapper_name = 'joybutton'
else:
mapper_name = name
getter = operator.attrgetter(name)
mapper = self.EVENT_PARAM_MAPPERS.get(name)
mapper = self.EVENT_PARAM_MAPPERS.get(mapper_name)
param_handlers.append((name, make_getter(mapper, getter)))

def prep_args(event):
Expand All @@ -155,12 +167,13 @@ def prep_args(event):
def new_handler(event):
try:
prepped = prep_args(event)
except ValueError:
except ValueError as err:
# If we couldn't construct the keys/mouse objects representing
# the button that was pressed, then skip the event handler.
#
# This happens because Pygame can generate key codes that it
# does not have constants for.
print("ERROR on event {0}: {1}".format(event, err))
return
else:
return handler(**prepped)
Expand Down
6 changes: 5 additions & 1 deletion pgzero/runner.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import pygame
pygame.mixer.pre_init(frequency=22050, size=-16, channels=2)
pygame.init()

pygame.joystick.init()
joysticks = [pygame.joystick.Joystick(x)
for x in range(pygame.joystick.get_count())]
for j in joysticks:
j.init()

import os
import sys
Expand Down
26 changes: 26 additions & 0 deletions test/test_joystick.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
from unittest.mock import Mock
from pgzero.game import PGZeroGame
from pgzero.constants import joystick


class Event:
"""Mock event."""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)


class JoystickTest(unittest.TestCase):
def setUp(self):
self.game = PGZeroGame(Mock())

def test_button_press_handler(self):
"""The handler dispatch converts a button value to an enum."""
presses = []
h = self.game.prepare_handler(lambda button: presses.append(button))
h(Event(joy=0, button=3)) # Right mouse button
self.assertEqual(presses, [joystick.THREE])


if __name__ == '__main__':
unittest.main()