Saturday, 11 August 2012

The craziness that is joysticks on PC

Joysticks on PC are absolutely insane. Not only are there all kinds of joysticks, but the creators of joysticks and joystick libraries have added some ridiculous inconsistencies and omissions, making supporting them a lot more hassle than it could have been if sensible people had made these. This is quite common knowledge among developers, of course, but while adding proper joystick support to Awesomenauts for the PC version, I learned that it was even worse than I thought. So for anyone who wants to have a very sour laugh, or considers supporting joysticks in his own game, here is a list of the oddities I have encountered.

Note that when I say joysticks, I actually mean all kinds of controllers, so also steering wheels, big force feedback flight simulator joysticks and of course the kind of controllers used on the Xbox and Playstation.

Joysticks have different layouts

This one is rather obvious, because you can actually see this when you look at a joystick: they all look different and the number of buttons and sticks they have can vary wildly. This makes perfect sense, since a flight sim has radically different requirements than a racing game. However, this does make adding proper joystick support on PC always a lot of work, because many joysticks simply don't work if you lack a very complete controls configuration screen.

It gets even more complex because some sticks don't even have a neutral position, like the throttle on a flightsim joystick. Such sticks can remain in full-output all the time, which is an added complexity when detecting what axis or button the user is trying to assign to an action.

Similar joysticks have randomly different mappings

This is where the craziness starts: joysticks that have exactly the same buttons and sticks often output those under randomly different numbers. It seems rather trivial for joystick manufacturers to come together and all agree on the same indices here, since these numbers don't have any impact on the actual features of the controller. Yet somehow they are so incredibly lame that they have never done this, and all have randomly different numbers for the exact same features.

Common libraries OIS and SDL don't support dynamically connecting

This is rather surprising to me. On console, when the user disconnects his controller he gets a nice warning message, asking him to reconnect. Since this makes a lot of sense, I wanted to do the same on PC. However, it turns out that OIS and SDL, two of the libraries that are used often for multi-platform joystick support, both don't support dynamically connecting and disconnecting controllers. Joysticks only work when they were already connected before the game started, and it is impossible to detect whether a joystick was disconnected.

The lack of this feature is extra surprising when you know how simple this is to implement: both DirectInput and XInput report whether the joystick is still connected when you try to find out which buttons are being pressed. However, somehow SDL and OIS both choose not to expose this information to the user. Meh. Note that SDL does plan to add this in the future in version 2.0 (whenever that comes), but SDL has existed for 14 years already and this seems rather late for such a basic feature.

An extra reason why this is so annoying, is that wireless controllers may report that they are not connected until you press a button. So if you don't press that button before the game starts, then the joystick won't work until you restart the game.

This is the main reason why I chose to remove our SDL and OIS joystick implementations and have instead built my own joystick library with the more low-level DirectInput and XInput libraries.

Drivers for the 360 controller on PC are insane

For low-level joystick support on Windows, the best libraries to use are DirectInput and XInput, both by Microsoft. Strangely, there is a battle between XInput and DirectInput, and the choices Microsoft made here are just straight out unbelievable...

The documentation of these libraries suggests that that DirectInput is the old standard, and XInput is the new one. However, this is not the case: XInput only supports the Xbox 360 controller. It does explain that all controllers that don't support XInput are 'legacy' controllers, but XInput is completely inflexible in terms of number of buttons and such. There are some joysticks that support XInput, but they always have to have the exact same layout as an Xbox 360 controller. This means that according to Microsoft, any new flightstick is automatically 'legacy'. I just can't do anything but cry when I see this piece of documentation...

DirectInput on the other hand documents that it is a flexible library that is set up to support every kind of joystick layout. It does not mention, however, that Microsoft's own Xbox 360 controller is one of the few (only?) joysticks that is not properly supported. Here's a little summary of the oddities regarding the Xbox 360 controller:
  • In DirectInput, the Xbox 360 controller does not support rumble.
  • In DirectInput, the two triggers on the shoulders are mapped to a single axis, so it is impossible to detect pressing both at the same time.
  • Voice chat through a headset connected to a 360 controller only works through XInput.

Since the Xbox 360 controller is, as far as I know, by far the most popular joystick for PC gamers, this means that for good joystick support, it is pretty much necessary to always implement both DirectInput and XInput. Lame!

XInput performance lameness

An extra 'nicety' of XInput that I came across is that you are supposed to check whether the controller is connected by calling XInputGetState for all four controllers. However, if you do this while they are not connected, then this will cost a lot of performance. I measured this on various PCs and the time it takes to call XInputGetState for four disconnected controllers was 2ms to 10ms (!). On 60fps a single frame can only take 16ms, so 10ms for checking whether 360 controllers are connected is a lot! The solution was to use windows events to learn whether a new Xbox 360 controller might have been connected. It is incredibly lame that XInput itself does not have an efficient way to detect connecting a controller, and it is even lamer how the XInput documentation advertises a method that actually takes a lot of performance if there is no Xbox 360 controller!

Some joysticks have a "mode" button

This is a pretty neat feature of some joysticks: to work with games that don't support all kinds of controllers, some joysticks feature a "Mode" button that may do things like making the DPAD act like a stick. On controllers that only have a DPAD and don't have a stick, this is a great feature to support more games. However, if you are programming for such a joystick and don't know this, and accidentally hit the Mode button, then this is a good excuse to get completely confused. An especially nice example of this is the Speedlink PS3 controller:

Some joysticks have buttons that act as several outputs

This is also a good thing to know and take into account when programming joystick support for a game: when pressing a single button on some joysticks, it outputs as if you are pressing two different buttons. For example, on one specific USB Super Nintendo controller the DPAD outputs both as buttons 1 to 4, and as a stick. Interestingly, the DPAD does not output as a DPAD.

Many of the things mentioned in this blogpost are not useful in any way. They are just the result of incredibly bad design. Not all, of course, but especially the random button mappings and the inconsistencies between DirectInput and XInput are just so stupid and lame...! So, if you ever plan on making a PC game with joystick support, be sure to take these awesome 'features' into account, and plan some serious time to get it right! (Or just waive it, like many developers seem to do, and don't really support joysticks properly. Sure saves a lot of time!)