InControl: Limitations and Best Practices

Although InControl defines a set of “standardized” controls, picked only because they appear on major console controllers, they are anything but standard. So, if you will take some humble advice offered by a battle-scarred curmudgeon who has seen some small part of the unspeakable array of crazy controllers out there, it is this:

Stick Buttons

Avoid using stick buttons in your input design, or make them optional. There are plenty of controllers that don’t have them even though they have sticks. It’s not in Apple’s MFi spec, for example, so no MFi controller will have them. They’re also awkward to use without moving the stick accidentally.

Triggers

This one is going to be hard to hear. Are you ready? Sitting down? Okay.

Avoid using triggers.

Triggers are wildly inconsistent. Some controllers or drivers have them work like buttons, not analogs. Some controllers have sensitivity and noise issues. Some controllers don’t have them, or they don’t work properly. Some controllers have them work differently on different platforms. Bottom line is, they’re just not reliable. Even the Xbox 360 controller will in some circumstances have both triggers on the same axis causing them to cancel each other out.

If you absolutely must use them: make them optional, treat them like buttons and, maybe even use only one of them.

Assigning Devices To Players

Devices do not have a unique ID. This is not Unity or InControl’s fault. Devices and drivers simply do not make such a value available. Some platform-and-controller-specific APIs such as XInput do, but they are the exception. As such, it is impossible to completely reliably identify a device between app launches, or changes of attached devices. I’m working on improving this in InControl in the future, but there is no perfect solution.

To make matters worse, Unity seems to place them in random order and the order may shuffle when a single device is attached/detached. Devices are also not guaranteed to be in the order that any player lights on them may indicate. On occasion there could be phantom or unresponsive devices present. I’ve even come across a webcam that reports itself as a controller. There is, of course, also the potential situation of having more connected devices than intended players.

The point is, InputManager.Devices is not an array of players. Please, please, don’t think of it that way. Consider it a pool of devices from which players may be assigned. Never store an index into the array. Store the device object reference instead, and use InputDevice.IsAttached or InputManager.OnDeviceDetached to be notified when it is disconnected.

So how should you handle assigning controllers to players? My current advice is to have a dialog where users select their player using the controller they wish to use. Show this dialog on game start and when any attached/detached devices are detected. It may seem onerous to create the UI for this process but it is by far a more user friendly approach to this problem. Of course, if you come up with a better solution, let me know!

If you have a single player game, consider using input from InputManager.ActiveDevice which will allow the player to use any connected controller and even switch if she prefers.

Persisting Device Settings

For the reasons stated above, this is just not going to work. Don’t do it. Treat every app launch like it’s the first launch as far as attached devices are concerned.

Rumble / Player Indicators / Colors

Player indicators, LED colors such as found on the PlayStation 4 controller and rumble is not currently supported by Unity and as such cannot be supported by InControl without native plugins for every platform.

XInput provides rumble support for the Xbox 360 controller (and compatible XInput controllers) on Windows by means of a native plugin.

If you have a native plugin you wish to integrate with InControl to expose additional functionality, the API does allow you to do so. You’ll want to implement custom subclasses of InputDeviceManager and InputDevice. You can take a look at how the XInput support is handled for an example.