Re: GamepadObserver (ie. MutationObserver + Gamepad)

Here's a rough sketch of an API that provides clean forwards-compatibility
for devices.  I think this also avoids all of the issues I talked about
earlier: it gives a clean, easy to use event-based API that preserves
ordering and timestamps; it can be used in both an event-based and polling
way; it can retrieve both events and the current state; and most
importantly it doesn't push things into a library, with all of the serious
(and hopefully obvious to everyone on this list) problems that would cause.

// Retrieve a representation of the device, with no actual access to inputs:
var gamepad = getSomeInputDevice();

// Get a list of supported layouts:
console.log(gamepad.layouts);
["X360", "NES", "SNES", "raw"]

// Get an interface, specifying which layout to use.
var gamepadInput = gamepad.getLayout("X360");

// Read the current state.
var state = gamepadInput.getCurrentState();
/* state == {
    // These values are defined by the X360 layout.
    leftThumbStickX: 0, leftThumbStickY: 0,
    rightThumbStickX: 0.4, rightThumbStickY: 0.5,
    up: true, right: false, down: false, left: false,
    buttonX: false, buttonY: false, buttonA: false, buttonB: false,

    // Inputs which have no representation in this layout are returned in
raw form.
    rawCompassAngle: 45,
} */

// Read the buffer of changes:
var stateChanges = gamepadInput.read();
/* stateChanges == [{
    input: "rightThumbstickX", // These labels are equal to the object keys
in "state" above.
    value: 0.5,
    lastValue: 0.4,
     timestamp: 1336102719319,
}, {
    input: "rawCompassAngle",
     value: 55,
    lastValue: 45,
    timestamp: 1336102721319,
}] */

function processInput(stateChanges)
{
    // Process inputs:
    for(var i = 0; i < stateChanges.length; ++i)
    {
        var change = stateChanges[i];
        var delta = change.lastValue - change.value;
        switch(change.input)
        {
        case "left": console.log("Left button was " + change.value?
"pushed":"released"); break;
        case "leftThumbStickX":
            console.log("Left stick X axis:" + change.value + " with a
relative change of " + delta);
            break;
        }
    }

    // Update the game state.  This API allows us to process all inputs
that happen simultaneously together,
    // such as axis changes, and then only update the game state once for
the whole batch.
    processInputs();
}

// Receiving an event on change:
gamepadInput.onchange = function(e) { processInput(gamepadInput.read()); };

Some points:

- A browser may support many different layouts for the same device.  For
example, an Xbox 360 controller can easily be used as an NES or SNES
controller, so it can expose those profiles.
- Raw inputs for any unmapped inputs are included for all profiles.  This
allows games to make use of additional controls, without being forced to
drop back to "raw" to access them at all.
- If a profile is exposed at all, the inputs of the profile must be
completely covered.
- Layout may not always map 1:1 to raw inputs.  For example, the
Playstation controller's D-pad is four buttons in a cross; layouts may map
these four buttons down to two axes.
- All data in getCurrentState is absolute, since it doesn't mutate the
object.  Therefore, purely relative inputs, such as trackballs and
(probably) steering wheels, are returned in an absolute, accumulated form.
To get relative motion, subtract the value from the previous value.

By the way, while I only included "NES" as an example, having a "baseline"
layout like that (with a more generic name) is useful: tons of simple games
use only a D-pad and a couple buttons.  This would give them get the widest
input device support possible, by not having to request a profile with far
more features than they need.

I haven't tried to incorporate Florian's suggestion of using something like
ArrayBuffer.  That could be supported later, eg. by providing a
readIntoBuffer(buffer) next to read().  That's too complex to try to tackle
all at once.

-- 
Glenn Maynard

Received on Saturday, 4 August 2012 17:03:44 UTC