この翻訳は不完全です。英語から この記事を翻訳 してください。
これは実験段階の機能です。
この機能は複数のブラウザで開発中の状態にあります。互換性テーブルをチェックしてください。また、実験段階の機能の構文と挙動は、仕様変更に伴い各ブラウザの将来のバージョンで変更になる可能性があることに注意してください。
HTML5 はリッチでインタラクティブなゲームを開発するために必要なコンポーネントを多く搭載しています。 <canvas>や
WebGL、<audio> に
<video>
などの技術は、今まで Native コードを書く必要のあった機能をサポートできるほどに成長しました。Gamepad API は開発者とデザイナーに Gamepad やコントローラーへのアクセスを提供するものです。
Gamepad API は Window
オブジェクトにGamepadとコントローラー(以下、Gamepad)の状態を読み取る新しいイベントをいくつか追加します。さらに、 Gamepad
というGamepadの接続状態が得られるオブジェクトと navigator.getGamepads
というGamepadの一覧を取得できるメソッドが追加されます。
Gamepad の接続
新しい Gamepad が接続された時、アクティブなページは gamepadconnected
イベントを受け取ります。ページ読み込み時にGamepadがすでに接続されている場合、Gamepad のボタンを押すなどの操作をした時に gamepadconnected
イベントがアクティブなページに対して発生します。
Firefox では、 ページが見える状態でかつユーザーによる Gamepad の操作を受け付けたときにのみ、Gamepad が利用可能になります。これによって、ユーザーを特定する Fingerprinting に利用されることを防止しています。一度一つのコントローラーが操作されれば、他のコントローラーも自動で接続され利用可能になります。
以下のようにして gamepadconnected
を使用します:
window.addEventListener("gamepadconnected", function(e) { console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); });
Gamepad はそれぞれ固有の ID を gamepad
プロパティの中に持っています。
Gamepad の切断
Gamepad が切断されると、Gamepad が以前に受信したデータ(例: gamepadconnected
)があると、2番目のイベント(例: gamepadconnected
)がフォーカスされたウィンドウにディスパッチします:
window.addEventListener("gamepaddisconnected", function(e) {
console.log("Gamepad disconnected from index %d: %s",
e.gamepad.index, e.gamepad.id);
});
Gamepadの index
というプロパティは同じタイプの複数のコントローラーが使用されている場合であっても、システムに接続されたデバイスごとにユニークになります。 Index
プロパティもまた Navigator.getGamepads()
として戻される Array
の index として機能します。
var gamepads = {};
function gamepadHandler(event, connecting) {
var gamepad = event.gamepad;
// Note:
// gamepad === navigator.getGamepads()[gamepad.index]
if (connecting) {
gamepads[gamepad.index] = gamepad;
} else {
delete gamepads[gamepad.index];
}
}
window.addEventListener("gamepadconnected", function(e) { gamepadHandler(e, true); }, false);
window.addEventListener("gamepaddisconnected", function(e) { gamepadHandler(e, false); }, false);
この前の例ではイベントが完了した後に gamepad
プロパティがどのように保持できるかを示しています - 後でデバイスの状態照会のために使用する技術となります。
Gamepad オブジェクトの問い合わせ
ご覧のように、上述の Gamepad イベントは Gamepad
オブジェクトを返すイベントオブジェクト、上の gamepad
のプロパティが含まれています。複数の Gamepad (すなわち、そのID ) を一度に接続される可能性があるため、イベントを発生させたのはどのゲームパッドを決定するためにこれらを使用することができます。それへの参照を保持し、それがボタンや軸のいずれかの時点で押されているかを知るために照会するなど、Gamepad
オブジェクトから様々なことを行うことができます。そうすることで、多くの場合、今回と次回のイベント発生とゲームパッドの状態を知っておく必要があり、ゲームやその他のインタラクティブな Web ページであることが望ましいです。
このようなチェックを実行すると、開発者はゲームパッドやゲームパッドの状態に基づいて、現在のフレームのための意思決定を行うために必要なアニメーションループ (例 : requestAnimationFrame
) と一緒に Gamepad
オブジェクトを使用して関与する傾向があります。
Navigator.getGamepads()
メソッドは現在 Web ページから見える Gamepad
オブジェクト ( Gamepad が繋がっていない時は毎回 null が返される ) のような、すべてのデバイスを配列として戻します。これは、同じ情報を得るために使用することができます。例えば、 以下に示すように上記の最初のコード例を書き換えます:
window.addEventListener("gamepadconnected", function(e) {
var gp = navigator.getGamepads()[e.gamepad.index];
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
gp.index, gp.id,
gp.buttons.length, gp.axes.length);
});
Gamepad
オブジェクトの機能は以下です :
id
: コントローラーに関する情報を含んだ文字列です。これは厳密には指定されていなく、Firefox では、コントローラのUSBベンダと製品IDを含んでいる2つの4桁16進数字列、およびドライバーから提供されるようなコントローラの名前といった3つの情報が、ダッシュ(-)により分離され含まれています。この情報はユーザーへの有益なフィードバックを表示するとともに、デバイスのコントロールのマッピングを見つけることができるようにします。index
: An integer that is unique for each gamepad currently connected to the system. This can be used to distinguish multiple controllers. Note that disconnecting a device and then connecting a new device may reuse the previous index.mapping
: A string indicating whether the browser has remapped the controls on the device to a known layout. Currently there is only one supported known layout — the standard gamepad. If the browser is able to map controls on the device to that layout themapping
property will be set to the stringstandard
.connected
: A boolean indicating whether the gamepad is still connected to the system. If this is so the value isTrue
; if not, it isFalse
.buttons
: An array ofGamepadButton
objects representing the buttons present on the device. EachGamepadButton
has apressed
and avalue
property:- The
pressed
property is a boolean indicating whether the button is currently pressed (true
) or unpressed (false
). - The
value
property is a floating point value used to enable representing analog buttons, such as the triggers on many modern gamepads. The values are normalized to the range 0.0..1.0, with 0.0 representing a button that is not pressed, and 1.0 representing a button that is fully pressed.
- The
axes
: An array representing the controls with axes present on the device (e.g. analog thumb sticks). Each entry in the array is a floating point value in the range -1.0 - 1.0, representing the axis position from the lowest value (-1.0) to the highest value (1.0).timestamp
: This returns aDOMHighResTimeStamp
representing the last time the data for this gamepad was updated, allowing developers to determine if theaxes
andbutton
data have been updated from the hardware. The value must be relative to thenavigationStart
attribute of thePerformanceTiming
interface. Values are monotonically increasing, meaning that they can be compared to determine the ordering of updates, as newer values will always be greater than or equal to older values. Note that this property is not currently supported in Firefox.
Note: The Gamepad object is available on the gamepadconnected
event rather than the Window
object itself, for security reasons. Once we have a reference to it, we can query its properties for information about the current state of the gamepad. Behind the scenes, this object will be updated every time the gamepad's state changes.
Using button information
Let's look at a simple example that displays connection information for one gamepad (it ignores subsequent gamepad connections) and allows you to move a ball around the screen using the four gamepad buttons on the right hand side of the gamepad. You can view the demo live, and find the source code on Github.
To start with, we declare some variables: The gamepadInfo
paragraph that the connection info is written into, the ball
that we want to move, the start
variable that acts as the ID for requestAnimation Frame
, the a
and b
variables that act as position modifiers for moving the ball, and the shorthand variables that will be used for the requestAnimationFrame()
and cancelAnimationFrame()
cross browser forks.
var gamepadInfo = document.getElementById("gamepad-info");
var ball = document.getElementById("ball");
var start;
var a = 0;
var b = 0;
Next we use the gamepadconnected
event to check for a gamepad being connected. When one is connected, we grab the gamepad using Navigator.getGamepads()
[0]
, print information about the gamepad into our gamepad info div
, and fire the gameLoop()
function that starts the whole ball movement process up.
window.addEventListener("gamepadconnected", function(e) {
var gp = navigator.getGamepads()[e.gamepad.index];
gamepadInfo.innerHTML = "Gamepad connected at index " + gp.index + ": " + gp.id + ". It has " + gp.buttons.length + " buttons and " + gp.axes.length + " axes.";
gameLoop();
});
Now we use the gamepaddisconnected
event to check if the gamepad is disconnected again. If so, we stop the requestAnimationFrame()
loop (see below) and revert the gamepad information back to what it was originally.
window.addEventListener("gamepaddisconnected", function(e) {
gamepadInfo.innerHTML = "Waiting for gamepad.";
cancelRequestAnimationFrame(start);
});
Chrome does things differently here. Instead of constantly storing the gamepad's latest state in a variable it only stores a snapshot, so to do the same thing in Chrome you have to keep polling it and then only use the Gamepad
object in code when it is available. We have done this below using Window.setInterval()
; once the object is available the gamepad info is outputted, the game loop is started, and the interval is cleared using Window.clearInterval()
. Note that in older versions of Chrome Navigator.getGamepads()
is implemented with a webkit
prefix. We attempt to detect and handle both the prefixed version and the standard version of the function for backwards compatibility.
var interval;
if (!('ongamepadconnected' in window)) {
// No gamepad events available, poll instead.
interval = setInterval(pollGamepads, 500);
}
function pollGamepads() {
var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
for (var i = 0; i < gamepads.length; i++) {
var gp = gamepads[i];
if (gp) {
gamepadInfo.innerHTML = "Gamepad connected at index " + gp.index + ": " + gp.id +
". It has " + gp.buttons.length + " buttons and " + gp.axes.length + " axes.";
gameLoop();
clearInterval(interval);
}
}
}
Now on to the main game loop. In each execution of the loop we check if one of four buttons is being pressed; if so, we update the values of the a
and b
movement variables appropriately, then update the left
and top
properties, changing their values to the current values of a
and b
respectively. This has the effect of moving the ball around the screen. In current versions of Chrome (version 34 as of this writing) the button values are stored as an array of double values, instead of GamepadButton
objects. This is fixed in development versions.
After all this is done, we use our requestAnimationFrame() to request the next animation frame, running gameLoop()
again.
function buttonPressed(b) {
if (typeof(b) == "object") {
return b.pressed;
}
return b == 1.0;
}
function gameLoop() {
var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
if (!gamepads) {
return;
}
var gp = gamepads[0];
if (buttonPressed(gp.buttons[0])) {
b--;
} else if (buttonPressed(gp.buttons[2])) {
b++;
}
if (buttonPressed(gp.buttons[1])) {
a++;
} else if (buttonPressed(gp.buttons[3])) {
a--;
}
ball.style.left = a * 2 + "px";
ball.style.top = b * 2 + "px";
start = requestAnimationFrame(gameLoop);
}
Using axes information
TBD (basically the same, except using axes[i] rather than button[i].value for both Firefox and Chrome.)
Complete example: Displaying gamepad state
This example shows how to use the Gamepad
object, as well as the gamepadconnected
and gamepaddisconnected
events in order to display the state of all gamepads connected to the system. You can find a working demo and look at the full source code on Github.
var haveEvents = 'ongamepadconnected' in window;
var controllers = {};
function connecthandler(e) {
addgamepad(e.gamepad);
}
function addgamepad(gamepad) {
controllers[gamepad.index] = gamepad;
var d = document.createElement("div");
d.setAttribute("id", "controller" + gamepad.index);
var t = document.createElement("h1");
t.appendChild(document.createTextNode("gamepad: " + gamepad.id));
d.appendChild(t);
var b = document.createElement("div");
b.className = "buttons";
for (var i = 0; i < gamepad.buttons.length; i++) {
var e = document.createElement("span");
e.className = "button";
//e.id = "b" + i;
e.innerHTML = i;
b.appendChild(e);
}
d.appendChild(b);
var a = document.createElement("div");
a.className = "axes";
for (var i = 0; i < gamepad.axes.length; i++) {
var p = document.createElement("progress");
p.className = "axis";
//p.id = "a" + i;
p.setAttribute("max", "2");
p.setAttribute("value", "1");
p.innerHTML = i;
a.appendChild(p);
}
d.appendChild(a);
// See https://github.com/luser/gamepadtest/blob/master/index.html
var start = document.getElementById("start");
if (start) {
start.style.display = "none";
}
document.body.appendChild(d);
requestAnimationFrame(updateStatus);
}
function disconnecthandler(e) {
removegamepad(e.gamepad);
}
function removegamepad(gamepad) {
var d = document.getElementById("controller" + gamepad.index);
document.body.removeChild(d);
delete controllers[gamepad.index];
}
function updateStatus() {
if (!haveEvents) {
scangamepads();
}
var i = 0;
var j;
for (j in controllers) {
var controller = controllers[j];
var d = document.getElementById("controller" + j);
var buttons = d.getElementsByClassName("button");
for (i = 0; i < controller.buttons.length; i++) {
var b = buttons[i];
var val = controller.buttons[i];
var pressed = val == 1.0;
if (typeof(val) == "object") {
pressed = val.pressed;
val = val.value;
}
var pct = Math.round(val * 100) + "%";
b.style.backgroundSize = pct + " " + pct;
if (pressed) {
b.className = "button pressed";
} else {
b.className = "button";
}
}
var axes = d.getElementsByClassName("axis");
for (i = 0; i < controller.axes.length; i++) {
var a = axes[i];
a.innerHTML = i + ": " + controller.axes[i].toFixed(4);
a.setAttribute("value", controller.axes[i] + 1);
}
}
requestAnimationFrame(updateStatus);
}
function scangamepads() {
var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
for (var i = 0; i < gamepads.length; i++) {
if (gamepads[i]) {
if (gamepads[i].index in controllers) {
controllers[gamepads[i].index] = gamepads[i];
} else {
addgamepad(gamepads[i]);
}
}
}
}
window.addEventListener("gamepadconnected", connecthandler);
window.addEventListener("gamepaddisconnected", disconnecthandler);
if (!haveEvents) {
setInterval(scangamepads, 500);
}
Specifications
Specification | Status | Comment |
---|---|---|
Gamepad Gamepad の定義 |
草案 | Initial defintion |
Browser compatibility
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari (WebKit) |
---|---|---|---|---|---|
General support | 21.0 webkit 35.0 |
29.0 (29.0) [1] | 未サポート | 15.0 webkit 22.0 |
未サポート |
Feature | Android | Firefox Mobile (Gecko) | IE Phone | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|
General support | 未サポート | 32.0 (32.0) | 未サポート | 未サポート | 未サポート |
[1] Starting with Firefox 24, the Gamepad API was available behind a preference. You can enable it in those versions by loading about:config
and setting the dom.gamepad.enabled
preference to true.