Update: Within minutes of me posting this, I received more information about the Gamepad API in CocoonJS. It is now confirmed, as of this writing, that gamepad index values always increase after disconnection events and that the ‘timestamp’ property is always ‘0’. No word yet on the axis out-of-range values.
I have been spending several hours a day for the last few days testing and documenting the Gamepad API in CocoonJS on the Ouya. It started with my realization that the ‘index’ and ‘timestamp’ properties were unreliable, and has now expanded into issues with axis values and my pursuit to incorporate a radial deadzone algorithm. This latest journey, though, started yesterday morning.
In my research, I came across this post detailing how to create a radial deadzone. It was something I didn’t realize I needed to implement myself until I saw the description of “as you rotate the stick across one of the cardinal directions . . . you’ll feel it ‘snap’ to the cardinal [direction]” in relation to a “naïve” way to use deadzone values. It was something I had noticed in my own code too, the ‘snap’ of gamepad axis values as they moved out of the deadzones and started reporting values.
After a considerable amount of time doubting my own ability to translate Unity C# code into JavaScript, I finally had something I thought was working correctly. Then, I tested it on the Ouya and found my values were… wrong. Now, not only were the magnitudes of my vectors wrong, they were hilariously wrong. Instead of, at a maximum of approximately 1.41 as the square root of 2 for the magnitudes, I was getting large numbers like 226 and 334. Numbers that should not even be possible.
Then, after even more time debugging my code line by line and testing every value calculated and used, I discovered something very… strange. The algorithm would be working perfectly in Firefox Aurora. I was getting a very smooth transition across each axis. However, when I used it in CocoonJS on the Ouya, the numbers were still very large. Numbers, again, that should not show up.
This lead me to comment out my newer code and do a simple fixed-rate polling dump of each gamepad connected to the system and its current axis values. This revealed the problem, and a major bug in CocoonJS on the Ouya. When at rest, the first connected gamepad’s axes[0] and axes[1] would report large numbers. Instead of some very small, close to zero values, it would produce numbers outside the range of -1 to 1. It’s a problem, as I took pictures with my phone to show, that affects the Ouya, PS3, and Xbox 360 gamepads. Whatever is considered the first paired gamepad at the time.
However, even after all those struggles, I do have some code to share now that is a working implementation of the high-precision radial deadzone algorithm in JavaScript. It’s not optimized, should probably test according to button layout instead of raw indices, and is obviously hard-coded with the axis out-of-range problem in mind (as it ignores if any axis values are greater than 1), but is a great starting point for others to port to their libraries and projects.
(Note: settings.LEFTAXISTHRESHOLD is 0.265 and settings.RIGHTAXISTHRESHOLD is 0.239. Numbers pulled from the recommended XInput deadzone values.)
Phaser.Plugin.CocoonJSOuyaGamepad.prototype.moved = function(pad, axisId) { | |
var movedAmount = 0; | |
var magitudeLeft = 0; | |
var magitudeRight = 0; | |
if (_gamepads[pad]) { | |
magitudeLeft = Math.sqrt((_gamepads[pad].axes[0] * _gamepads[pad].axes[0]) + | |
(_gamepads[pad].axes[1] * _gamepads[pad].axes[1])); | |
magitudeRight = Math.sqrt((_gamepads[pad].axes[2] * _gamepads[pad].axes[2]) + | |
(_gamepads[pad].axes[3] * _gamepads[pad].axes[3])); | |
if (axisId === "AXIS_0" && _gamepads[pad].axes[0] <= 1) { | |
if (magitudeLeft < settings.LEFTAXISTHRESHOLD) { | |
movedAmount = 0; | |
} | |
else { | |
movedAmount = (_gamepads[pad].axes[0] / magitudeLeft) * | |
((magitudeLeft - settings.LEFTAXISTHRESHOLD) / (1 - settings.LEFTAXISTHRESHOLD)); | |
} | |
} else if (axisId === "AXIS_1" && _gamepads[pad].axes[1] <= 1) { | |
if (magitudeLeft < settings.LEFTAXISTHRESHOLD) { | |
movedAmount = 0; | |
} | |
else { | |
movedAmount = (_gamepads[pad].axes[1] / magitudeLeft) * | |
((magitudeLeft - settings.LEFTAXISTHRESHOLD) / (1 - settings.LEFTAXISTHRESHOLD)); | |
} | |
} else if (axisId === "AXIS_2" && _gamepads[pad].axes[2] <= 1) { | |
if (magitudeRight < settings.RIGHTAXISTHRESHOLD) { | |
movedAmount = 0; | |
} | |
else { | |
movedAmount = (_gamepads[pad].axes[2] / magitudeRight) * | |
((magitudeRight - settings.RIGHTAXISTHRESHOLD) / (1 - settings.RIGHTAXISTHRESHOLD)); | |
} | |
} else if (axisId === "AXIS_3" && _gamepads[pad].axes[3] <= 1) { | |
if (magitudeRight < settings.RIGHTAXISTHRESHOLD) { | |
movedAmount = 0; | |
} | |
else { | |
movedAmount = (_gamepads[pad].axes[3] / magitudeRight) * | |
((magitudeRight - settings.RIGHTAXISTHRESHOLD) / (1 - settings.RIGHTAXISTHRESHOLD)); | |
} | |
} | |
return movedAmount; | |
} else { | |
return movedAmount; | |
} | |
}; |
Additional notes:
- As I mention at the bottom of the post here, if you contribute to any libraries that use the W3C Gamepad specification, you should consider testing for the GamepadButton interface object and its ‘value’ (double) and ‘pressed’ (boolean) properties soon. Firefox Aurora already uses it and it will properly ship in Firefox proper within the next couple months. At that point, or probably sooner I imagine, Chrome will change its API too to match.
- Firefox Aurora and current CocoonJS either do not have or incorrectly report the ‘timestamp’ property. I’ve already started preparing fixed-rate polling code for the possibility of a coming future without that property as part of gamepad objects. (This, despite it remaining in the Working Draft copy of the specification.) If you work on HTML5 libraries or projects using the Gamepad API, it might be time to start thinking about doing so too.