Game controllers on Windows (without PyGame)


#1

Since I published a snippet for accessing game controllers on Linux from Python, I figured I’d also make one for doing the same on Windows.

It’s not as robust as the Linux code since it doesn’t fetch a button mapping from the driver (I couldn’t find a way to fetch it via the API, let me know if you find a way) so I hardcoded the button map of my own gamepad.

It uses the WinMM API via ctypes. This is a little-known way of accessing the joysticks, and is the easiest to instrument from Python. An alternative is via the raw input API, which I also have code for, let me know if you want it.

One thing to keep in mind is that the Windows joystick drivers don’t allow the use of both triggers at the same time, since the triggers are seen as a single axis (which is -1 when the left trigger is pressed but 1 when the right trigger is pressed, but 0 when both are pressed because they cancel each other out).

Here’s the code. It’s raw, proof-of-concept code that should be a good start for implementing it into your game.
gist.github.com/rdb/8883307

(If you’re using an XBox 360 controller, you can use XInput instead, which is easier to interface with and does allow reading the two triggers individually. (example))


#2

Thanks much, not only for the main idea, but for making ctypes understandable for me (I had no idea there was such a thing, although it’s exactly what I was searching for).

On a perhaps-related note, do you know anything about the DirectJoybox.py file? I’ve found nothing but the API reference in my searches, so it seems no one uses it, or those that do don’t speak of it after.


#3

I’m glad it can be useful to someone.

I don’t know anything about DirectJoybox, but it looks like it is part of a high-level wrapper around the VRPN interfaces. It seems that “Joybox” is the name of a hardware product that allows hooking up assorted generic switches. It’s probably been used internally by Disney Imagineering.


#4

For the record, the axis and button mapping differs per controller. I haven’t found a reliable way to query the axis and button mapping via this interface. Right now, this code needs modification to work with controllers other than the Xbox 360 controller.


#5

You might want to take a look at the joystick code from the pyglet library and see what they are doing in there. It has a consistent mapping for axes across OSs.


#6

Ah, that is a good suggestion indeed. It seems that they use DirectInput, which I thought had a C++ interface and seemed not possible to wrap using ctypes, but I guess I was wrong; pyglet does manage to wrap it using ctypes.

For anyone interested in interfacing with DirectInput, here is the relevant code for reference:

code.google.com/p/pyglet/source/ … /dinput.py

code.google.com/p/pyglet/source/ … ctinput.py


#7

Necrotreading of the worst kind, sorry.

I’ve taken the code posted here and wrapped it into a class that throws events just like the keyboard/mouse buttons.
gist.github.com/wezu/9cd50bf57b … b6a3499a60

You can use it like this:

from __future__ import print_function
from panda3d.core import *
from direct.showbase import ShowBase
from direct.showbase.DirectObject import DirectObject

from controller import Controller

class Demo(DirectObject):
    def __init__(self):
        base = ShowBase.ShowBase()

        self.joy_pad=Controller('joystick')
        #get all buttons
        self.accept('joystick', self.print_key)
        #get all axis
        self.accept('joystick-analog', self.print_axis)
        #get just one specific
        self.accept('button_0', self.jump)
        #good-ol'-keymap

        #key mapping
        self.keyMap = {'key_up': False,
                       'key_down': False,
                       'key_left': False,
                       'key_right': False}
        self.accept('pad_up', self.keyMap.__setitem__, ["key_up", True])
        self.accept('pad_up-up', self.keyMap.__setitem__, ["key_up", False])
        self.accept('pad_down', self.keyMap.__setitem__, ["key_down", True])
        self.accept('pad_down-up', self.keyMap.__setitem__, ["key_down", False])
        self.accept('pad_left', self.keyMap.__setitem__, ["key_left", True])
        self.accept('pad_left-up', self.keyMap.__setitem__, ["key_left", False])
        self.accept('pad_right', self.keyMap.__setitem__, ["key_right", True])
        self.accept('pad_right-up', self.keyMap.__setitem__, ["key_right", False])

    def jump(self):
        print('jump!')

    def print_axis(self, axis, pos):
        print(axis, pos)

    def print_key(self, key):
        print(key)

d=Demo()
base.run()