Having been sold together with millions of Xbox 360 game consoles, the Xbox 360 controller is one of the most widespread and well-known types of input devices for gamers. But it is not only console gamers who are able to use this kind of controller as it can easily be plugged into a Windows PC too. Apart from the device being recognized by the operating system, many PC games are officially supporting the Xbox 360 controller as a possible input device.
In case you want to create a game with support for this gamepad, this recipe is for you. But also if you intend to support any other type of joystick or game controller you will find interesting resources ahead because the API you are going to use is not bound to any device in particular. Nonetheless, this recipe will show you how to read data from the Xbox 360 controller's analog sticks and buttons, and will provide you with a minimal class that maps the raw button and axis indices to more meaningfully named variables.
This recipe builds upon the code and knowledge presented in the recipe Implementing an abstraction layer for supporting multiple input methods. Before going on, you are required to follow and understand that recipe!
Additionally, as Panda3D does not have built-in support for analog input devices like joysticks and gamepads, you need to add the pygame
programming library to your installation of the Panda3D engine:
pygame-1.9.1.win32-py2.6.msi
. The version number might not match. In that case watch out for the -py2.6
postfix in the filename. python
subdirectory of your Panda3D installation. The screenshot shows the default installation path.Let's write some code for handling gamepad input:
XboxControllerHandler.py
and insert the following code:from panda3d.core import * import pygame import math class XboxControllerState: A = 0 B = 1 X = 2 Y = 3 LB = 4 RB = 5 BACK = 6 START = 7 LS = 8 RS = 9 def __init__(self, joy): self.joy = joy self.leftStick = Vec2() self.rightStick = Vec2() self.dpad = Vec2() self.triggers = 0.0 self.buttons = [False] * self.joy.get_numbuttons() def update(self): self.leftStick.setX(self.joy.get_axis(0)) self.leftStick.setY(self.joy.get_axis(1)) self.rightStick.setX(self.joy.get_axis(4)) self.rightStick.setY(self.joy.get_axis(3)) self.triggers = self.joy.get_axis(2) for i in range(self.joy.get_numbuttons()): self.buttons[i] = self.joy.get_button(i)
XboxControllerHandler
class below the code of XboxControllerState:
class XboxControllerHandler(InputHandler): def __init__(self): InputHandler.__init__(self) self.wasWalking = False self.wasReversing = False self.controller = None pygame.init() pygame.joystick.init() for i in range(pygame.joystick.get_count()): joy = pygame.joystick.Joystick(i) name = joy.get_name() if "Xbox 360" in name or "XBOX 360" in name: joy.init() self.controller = joy self.state = XboxControllerState(joy) taskMgr.add(self.updateInput, "update input") def updateInput(self, task): pygame.event.pump() if self.controller: self.state.update() x = self.state.rightStick.getX() y = self.state.leftStick.getY() if y < -0.5 and not self.wasWalking: self.wasWalking = True self.beginWalk() elif not y < -0.5 and self.wasWalking: self.wasWalking = False self.endWalk() elif y > 0.5 and not self.wasReversing: self.wasReversing = True self.beginReverse() elif not y > 0.5 and self.wasReversing: self.wasReversing = False self.endReverse() if math.fabs(x) > 0.2: messenger.send("turn", [-x]) self.dispatchMessages() return task.cont
Application.py
and add the highlighted line to the constructor of the Application
class:self.keyInput = KeyboardMouseHandler() self.xboxInput = XboxControllerHandler()
In the constructor of the XboxControllerHandler
class we can see the pygame
library and its joystick module being initialized before we iterate over all connected devices to see if we can find an Xbox 360 controller. If this routine is successful, a new instance of XboxControllerState
is created.
This class is a container for storing the state of an Xbox 360 controller and provides easier access to the controller data than using numeric indices. The class' leftStick
and rightStick
variables store the state of the two analog sticks, while dpad
and triggers
store the states of the cross-shaped directional pad and the analog triggers on the back of the controller. Data about the various buttons on the controller being up or down can be accessed using the buttons
list. To make accessing these buttons easier, the A, B, X
, and other class variables, found right under the class declaration, can be used to address buttons by name rather than by a numeric index.
This leaves the updateInput()
method of the XboxControllerHandler
class open for discussion. Here, we keep the internal message loop of pygame
running by calling pygame.event.pump()
. Handling the input for walking forwards and backwards requires special care as we are degrading the left analog stick to a binary control scheme. We do not care how far the stick was pushed forward. Instead, we just set a flag based on whether the stick has been moved forward or backward.
Because we are not receiving any events for when an analog stick has become active, we need to take care of detecting this case ourselves. Therefore we need to store and check if we were not walking or reversing before any of the -start
events are triggered. The same applies for the -stop
events, where we need to determine if we were in a walking or reversing state, respectively.
The data read from the analog sticks on the Xbox 360 controller does not just simply go back to zero if they are centered. Instead, we receive a lot of noise from the controller. This is the reason why the turn
event is starting to be triggered after the controller was moved more than 20% of the way towards one direction, or else the panda would be twitching uncontrollably and never stand still. We apply this low-cut filter in the code using the conditional expression if math.fabs(x) > 0.2.