senäh

17senäh und so…

HTML/CSS/JS
24. Feb 2012
Kommentare: 0

Vorstellung der HTML5 Gamepad API

Kategorien: HTML/CSS/JS | 24. Feb 2012 | Kommentare: 0

HTML5 ist schon eine tolle Sache. Insbesondere für Spieleentwickler sind eine Reihe von interessanten APIs entstanden. Eine davon ist die Gamepad API, welche die Steuerung eines HTML5-Spiels mit Gamepads/Controllern ermöglicht. Gamepads können aber noch für mehr Anwendungen verwendet werden. Gamepads besitzen meist zwei Thumbsticks, welche sich ideal dafür eignen um sich in einem 3D Raum zu bewegen. Dies kann für Simulationen und Visualisierungen nützlich sein. Außerdem sind Gamepads häufig wireless. Wie wäre es beispielsweise die ZDF Mediathek bequem vom Sofa aus zu steuern? Cool oder? (Eine wireless Maus besitze ich nicht…) Wer etwas kreativ ist, wird zahlreiche Verwendungszwecke für ein Gamepad im Browser finden. Ich werde euch im folgenden eine kleine Einführung in das Thema geben und euch an einem kleinem Beispiel zeigen, wie ich mit einer Xbox 360 Controller eine Präsentation steuere 🙂

Ein paar Infos zur Gamepad API

Die Gamepad API der W3C ist im Augenblick noch sehr experimentell. Nur zwei Browser implementieren die API (mit Vendor-Prefixes) in Entwicklerversionen:

  • Chrome Dev (Windows), Chrome Canary (Mac)
  • Firefox Nightly (Windows, Mac)

Ich selbst habe die API nur unter Windows mit Chrome Dev getestet. Damit ihr die API unter Chrome testen könnt, müsst ihr sie erst noch manuell aktivieren. Tippt dazu in die Adressleiste von Chrome chrome://flags ein und scrollt bis ganz nach unten. Klickt dort unter „Gamepad aktivieren“ auf „aktivieren“:

Gamepad API aktivieren

Gamepad API aktivieren

Die API erreicht ihr nun über navigator.gamepads:

navigator.gamepads;
navigator.webkitGamepads; // chrome
navigator.mozGamepads; // firefox

Das gamepads Objekt ist dabei in Wirklichkeit ein Array, welches alle angeschlossenen Gamepads repräsentiert. Das Array hält Objekte mit dem Gamepad Interface. Dieses ermöglicht uns die Abfrage der folgenden Werte pro Gamepad:

  • id: Ein String. Hier könnt ihr herausfinden, ob das angeschlossene Gamepad bspw. ein Xbox 360 Controller ist.
  • index: Der Index des Controllers im Array. Anscheinend sieht die Spezifikation hier kein Limit vor und ihr könnt so viele Gamepads anschließen wie es eure Hardware zulässt.
  • timestamp: Gibt an wann das Gamepad zuletzt aktualisiert wurde (sprich: wann es benutzt wurde).
  • buttons: Ein Array mit Zahlwerten zwischen 0.0 und 1.0 für alle Buttons auf dem Controller (0.0 = nicht gedrückt, 1.0 = gedrückt).
  • axes: Ein Array mit Zahlen zwischen -1.0 und 1.0 für alle Achsen eines Sticks (-1.0 = links/oben, 1.0 = rechts/unten).

Wie ihr seht lässt die Spezifikation als Eingabemöglichkeit nur analoge Buttons und Sticks zu. Es werden keine weiteren speziellen Sensoren berücksichtigt. Ihr seht außerdem, dass alle Knöpfe und Sticks nur als Zahlenwerte in einem Array vorkommen. So etwas wie „Knopf X“ oder „Knopf A“ werden nicht beachtet. Ihr müsst selbst herausfinden, ob euer Controller von Microsoft oder Sony (oder whatever) ist und in welcher Reihenfolge die Knöpfe im Array vorkommen. Puuuh! Ziemlich viel Aufwand. Zum Glück gibt es bereits die gamepad.js Bibliothek von Scott Graham, welche einem hier etwas entgegenkommt und die einzelnen Werte für verschiedene Controller/Browser/Betriebssysteme normalisiert.

Zusätzlich könnt ihr über zwei Events herausfinden, ob ein Gamepad am Computer angeschlossen oder von ihm getrennt wurde:

window.addEventListener('gamepadconnected', onConnected, false);
window.addEventListener('gamepaddisconnected', onDisconnected, false);

// chrome
window.addEventListener('webkitgamepadconnected', onConnected, false);
window.addEventListener('webkitgamepaddisconnected', onDisconnected, false);

// firefox
window.addEventListener('MozGamepadDisconnected', onDisconnected, false);
window.addEventListener('MozGamepadConnected', onConnected, false);

In einer von Graham bereitgestellten Beispielseite könnt ihr nun testen, ob ihr euren Browser richtig konfiguriert und die Gamepads installiert habt:

Gamepad Test

Gamepad Test

Mit dem Gamepad eine Präsentation steuern

Ich werde demnächst eine Präsentation über HTML5 halten und als Gadget soll die Präsentation natürlich nicht mit PowerPoint erstellt werden, sondern im Browser ablaufen. Aus diesem Grund greife ich auf das html5slides Framework von Google zurück. Es ist ziemlich simpel: Beschreibt ein paar Folien innerhalb eines <article>-Tags und bewegt euch dann von Folie von Folie. Das geht mit Scrollen, Maus- oder Tastenklicks. Ich erweitere das ganze mit einem Plugin, damit ich mit einem Gamepad von Folie zu Folie springen kann. Dazu benutze ich auch gamepad.js.

Mit requestAnimationFrame initiiere ich eine Loop in der ich alle ~16ms Abfrage, welche Tasten bei meinem Gamepad gedrückt wurden. Dies geht mit der von gamepad.js gestellten Funktion Gamepad.getStates(). Anschließend iteriere ich über die zurückgegebenen Gamepads und frage einzelne Tasten ab. (Welche Tasten ihr abfragen könnt verrät uns die Doku auf github.) So steht .faceButton0 bspw. für den A-Knopf eines Xbox 360 Controllers. Wird nun „A“ gedrückt, springt die Präsentation auf die nächste Folie. Halte ich „A“ gedrückt blättert die Präsentation schnell durch alle Folien.

Mit dem Plugin könnt ihr nun mit dem linken Stick bzw. dem Steuerkreuz, sowie „A“/“B“ (falls ihr einen Xbox Controller benutzt) die Präsentation steuern.

Hier der Quellcode für das Plugin:

/* requestAnimationFrame polyfill - http://paulirish.com/2011/requestanimationframe-for-smart-animating/ */

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame =
            window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
                timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

/* Gampad support in slides:
 xbox:
 faceButton0 : A
 faceButton1 : B
 faceButton2 : X
 faceButton3 : Y */

(function() {
    if (!Gamepad.supported) return;

    var config = {
    	stickSensitivity: 1.0,
        buttonSensitivity: 1.0,
        stutterTime: 150, // in ms
        fastSlideTreshold: 1000 // in ms
    };

    var lastTime = Date.now(),
        deltaTime,
        pressedTime = {
            prev: 0,
            next: 0
        };

    var update = function() {
        deltaTime = Date.now() - lastTime;

        // get input
        var pads = Gamepad.getStates();
        for (var i = 0; i < pads.length; ++i) {
            var pad = pads[i];
            if (pad) {
                // prev
                if(pad.leftStickX <= -config.stickSensitivity ||
                    pad.dpadLeft >= config.buttonSensitivity ||
                    pad.faceButton1 >= config.buttonSensitivity) {
                	pressedTime.prev += deltaTime;
                }
                else {
                    pressedTime.prev = 0;
                }
                // next
                if(pad.leftStickX >= config.stickSensitivity ||
                    pad.dpadRight >= config.buttonSensitivity ||
                    pad.faceButton0 >= config.buttonSensitivity) {
                    pressedTime.next += deltaTime;
                }
                else {
                    pressedTime.next = 0;
                }
            }
        }
        // slide
        if(pressedTime.prev > 0 && pressedTime.prev == deltaTime) {
            prevSlide();
        } else if (pressedTime.prev >= config.fastSlideTreshold) {
            prevSlide();
            pressedTime.prev -= config.stutterTime;
        }
        if(pressedTime.next > 0 && pressedTime.next == deltaTime) {
            nextSlide();
        } else if (pressedTime.next >= config.fastSlideTreshold) {
            nextSlide();
            pressedTime.next -= config.stutterTime;
        }

        lastTime = Date.now();
        window.requestAnimationFrame(update);
    };
    update();
})();

Fertsch! Das ganze könnt ihr natürlich auch online testen 🙂

Autor: Pipo

...kommt ursprünglich aus der Designerecke, ist aber im Laufe seines Studiums in die Webentwicklung gestolpert. Kann sich seit dem nicht so richtig entscheiden wo er hingehört und wagt deswegen vielleicht die Flucht nach vorne in ein komplett neues Gebiet. Vielleicht Management? Niemand weiß es. Auch er nicht.