Advanced Usage Guide
This module contains general support for game controllers. It includes the top level classes such as buttons and joystick axes which are used for any kind of controller, particular controllers and binding mechanisms are implemented in sub-modules. The key classes shared across all controllers are:
CentredAxis
TriggerAxis
andBinaryAxis
represent different kinds of axis of an analogue control. The centred axis is used for joysticks with a negative value at one end of the range and positive at the other, whereas the trigger axis is used for axes with zero at the resting position and increasingly positive values as the control is pressed. As the names suggest, these are used for centred and trigger controls respectively - a PS3 joystick consists of two centred axes, an XBox One front trigger consists of a single trigger axis. The BinaryAxis is used for controllers which export buttons as axes (both the PS4 and the XBox controller D-pads are actually a pair of axes in terms of implementation, although they don’t have any analogue control, they just emit either -1, 0 or 1)CircularCentredAxis
instances are automatically created when a controller defines pairs of axes named lx,ly, or similar (rx,ry and dx,dy are the only other ones used at the moment). These are more useful if you want to treat the two axes as a 2d control, as they have a circular rather than cross shaped dead zone and circular rather than box shaped hot zone.Button
represents a single button. As with the Axis class you don’t create these, instead you need to use the instances provided by the driver classes.Buttons
represents the state of all the buttons on the controller. You’ll use the provided instance of this class to register button handlers, ask whether any buttons were pressed, and get information about how long a button has been held down.Finally, all controller classes inherit from
Controller
. This provides aButtons
instance called ‘buttons’, and anAxes
instance called ‘axes’. These will come in handy later!
Constructing and Binding a Controller
Once your controller is physically connected to the computer (whether by USB, bluetooth, or magic) and you have a
corresponding entry in the dev filesystem, you need to create an object to receive and interpret events from the
hardware, and you need to set up a mechanism by which events will be sent to that object. The object in this case will
be a subclass of Controller
. As of version 2.6.0, the mechanism to specify controllers has
been changed. For controllers which are close to standard, they can be defined within YAML files found within the
library itself, or within a ~/.approxeng.input directory. Non-standard controllers, such as ones which use multiple
device nodes or have extra features such as LEDs, must be coded directly and added into the library itself.
Non-standard controller classes which include behaviour or controls which can’t be handled by the YAML definitions are currently:
approxeng.input.dualshock3.DualShock3
for PS3 controllers, see PlayStation 3 Controller Supportapproxeng.input.dualshock4.DualShock4
for PS4 controllers, see PlayStation 4 Controller Supportapproxeng.input.steamcontroller.SteamController
for the Valve Steam controller. See Steam Controller Support for details.approxeng.input.spacemousepro.SpaceMousePro
for Wired SpaceMouse Pro Support.
Other controllers will appear as instances of a class called ProfiledController, this is dynamically created from the YAML definitions and consequently has no linked documentation.
In general you will not explicitly create these objects yourself, instead you can use the binding layer to discover a connected controller (optionally supplying a particular kind of controller you want, otherwise it just finds the first one it can). This will create the controller object from which you can read things like axis values, and also set up the necessary logic to pull events out of the evdev linux system and update the values without you having to do anything.
The only time you’re likely to use these classes is to reference them when binding, this allows you to wait for a specific kind of controller to become available - handy if, say, you really have to have a PS4 controller but you’ve got a rock candy dongle plugged in. The details of the discovery process are at Controller Discovery - approxeng.input.controllers, once you’ve discovered the appropriate controllers you use the binding API to attach to their event streams; the details of the binding process are described at Binding - approxeng.input.selectbinder.
More about Analogue Axes
Analogue axes on the controller are those which can vary continuously over their range. Typically these are joysticks and triggers. This code maps all axes either to a range from -1.0 to 1.0 (for centred axes such as joysticks) or from 0.0 to 1.0 (for things like triggers where the resting point is at one end of the range of movement). Joysticks are modelled as two independent centred axes, one for the horizontal part and one for the vertical.
We could just read out the value supplied by the controller hardware and provide that value, but there are a few things we might want to do first, and which the code provides:
The centre point of the hardware is often not the numeric centre of the range. This is because hardware exists in the real world, where things can be slightly messy. It’s generally not far off, but often the resting position isn’t at 0.0.
The theoretical range of the controller is often larger than the actual range produced. For example, we might have a controller which claims to produce values from -255 to 255 (before we normalise down to -1.0 to 1.0) but which actually only ever produces values between, say, -251 and 243.
It’s often desirable to have a dead zone near the resting position, so only intentional movements of the controller are detected as motion. Analogue controls often have a bit of noise - the joystick may rest at 0 in theory, but in practice we might see a string of values such as -1, -1, 0, 1, 1, 0, 0 etc etc.
Similarly, we might want a ‘hot zone’ near the extreme positions of the axis, where any higher magnitude values should be interpreted as the maximum value. This means we’re able to get to the highest value without having to worry about controller noise.
Different controllers report different ranges (for example, the PS3 controller range is from 0 to 255 whereas the XBox controller is from -32768 to 32768 when plugged in and, for some ungodly reason, 0 to 65335 when wireless), but you don’t have to worry about this as the controller implementations specify this internally and you’ll only ever see values between -1.0 and 1.0, or between 0.0 and 1.0 for trigger axes.
The CentredAxis
and TriggerAxis
both auto-range, in that they start
off with a maximum and minimum value that’s well within the theoretical range, and expand this out when they see higher
values from the controller. This means we don’t have to worry that the theoretical range of the controller isn’t fully
used, we’ll always have our -1.0 to 1.0 correspond to the actual controller movement.
Auto-centring isn’t possible as we can’t know whether the user is touching the controller, but you can set the centre
point for an individual CentredAxis
by setting its ‘centre’ property, or for a complete set
defined by an Axes
object by calling the set_axis_centres() function on the Axes object. This
function takes an arbitrary number of parameters and ignores all of them - this is done so you can specify the function
as a button handler.
Dead zones and hot zones are defined as a proportion of the range of the axis:
For a trigger axis the dead zone is from the 0.0 raw position of the controller up to the specified value, and the hot zone is from 1.0 - the value to 1.0. Values below the dead zone value will be returned as 0.0, and values above the hot zone will be returned as 1.0, with values inbetween scaling from 0.0 at the edge of the dead zone to 1.0 at the edge of the hot zone.
For centred axes the same applies, but with the dead zone and hot zone values specifying the proportion of each half (positive and negative) of the range. So, if the dead zone is set to 0.1 and hot zone to 0.2, positive raw values above 0.8 will return a corrected value of 1.0, and those below 0.1 will return 0.0. For negative values the same applies, except that values below -0.8 will return -1.0 and those above -0.1 will return 0.0
To obtain the corrected values for an axis you need to retrieve the ‘value’ property on the axis object.