This is a follow up to my last post on the current Xbox 360 controller bug in Unity. In this post, I speculate on the possible cause of the bug.

In my last post, I wrote the following:

“An brief note on the Xbox 360 controller: Microsoft originally designed the triggers to work on a combined axis, with the left trigger adding 0..-1 to the axis and the right trigger 0..1. The value of the axis is the sum of the two trigger values. In Unity on Windows, this axis happens to be axis 2. Recently, support has (somehow) been added to Unity for splitting the triggers properly on axes 8 and 9.”

I was surprised when I found out that separated triggers had become possible with this controller in Unity. As far as I know, there isn't a way to get access to those through DirectInput. But, I thought, maybe Microsoft had done something about it in a Windows update, or maybe Unity was doing some low level HID shenanigans to query the necessary trigger data.

In the light of what I observed while experimenting with the recent bug, I have a new theory.

I think Unity is using XInput.

But XInput is a very targeted API for querying up to four Xbox 360 controllers. Unity supports up to ten controllers, all of which could conceivably be of Xbox variety.

Furthermore, Unity would logically need to use DirectInput to query for other types of controllers, but Xbox controllers would also show up through DirectInput. The same goes for XInput compatible clones. Which creates the challenge of ignoring the first four XInput compatible controllers from the DirectInput list.

And this is where I feel Unity may have gone wrong. I do not believe matching up controllers between XInput and DirectInput is reliably possible. And yet, they may have tried, most likely by trying to identify XInput compatible controllers in the DirectInput list by name and then matching them up in order.1

The problem is Microsoft never intended for XInput and DirectInput to be used in conjunction. They explicitly state DirectInput is essentially deprecated, and is, in fact, not supported for Windows Store apps. Which is interesting, considering Unity 5 will add support for joysticks in Windows Store apps. Hmm.

So now, assuming that it is the case that Unity is trying to use XInput together with DirectInput, or perhaps their own native HID input library, suddenly the observations made about the bug begin to make sense.

This would explain why the problems usually start with a controller being in the wrong XInput position, for example, slot 3 instead of 1, with slot 1 being empty. There is no guarantee that the DirectInput/HID device order would match up with the XInput order. This could throw off the logic for matching up controllers, which would in turn cause the wrong devices to be ignored or “deactivated” by Unity leading to a dead controller.

This would explain why it's always the separated triggers that malfunction on one or both devices.

This would explain why, in rare circumstances, there is a third phantom controller which matches one of the others on all input except the separate trigger axes. In this case, Unity has not disabled the DirectInput/HID version while still enabling the XInput version.

This would explain why it takes more than one Xbox controller to cause the problem reliably, as quickly plugging and unplugging them could create a small window of time where Windows hasn't quite realized one is disconnected before assigning a new slot.

This would explain why, at times, even with a single controller, the separated triggers don't work for some users. This is either because DirectX (and therefore XInput) isn't installed correctly or some internal logic in Unity didn't match up the controllers and is ignoring the XInput side.

1. It's worth noting, that InControl takes a similar approach when enabling XInput support, except that it never tries to match up controllers. It simply ignores and hides any known XInput compatible devices with the result that a limit of four Xbox 360 controllers can be used in that mode. If Unity wants to tread this path, it should impose the same limit as the price to pay for separated triggers, or accept the limitation of the single combined trigger axis.