ä½¿ç¨ Gamepad API
åºçº¿
广æ³å¯ç¨
*
èª 2017å¹´3æ èµ·ï¼æ¤ç¹æ§å·²å¨ä¸»æµæµè§å¨ä¸å¾å°æ¯æï¼å¯å¨å¤§å¤æ°è®¾å¤åæµè§å¨çæ¬ä¸æ£å¸¸ä½¿ç¨ã
* æ¤ç¹æ§çæäºé¨åçæ¯æç¨åº¦å¯è½ææä¸åã
å®éªæ§: è¿æ¯ä¸é¡¹å®éªæ§ææ¯
å¨å°å
¶ç¨äºç产ä¹åï¼è¯·ä»ç»æ£æ¥æµè§å¨å
¼å®¹æ§è¡¨æ ¼ã
HTML5 为丰å¯ç交äºå¼æ¸¸æå¼åå¼å
¥äºè®¸å¤å¿
è¦çç»ä»¶ãå <canvas> ãWebGLã <audio> å <video> è¿æ ·çææ¯ï¼éç JavaScript ç鿏æçï¼è®¸å¤ä»¥åéè¦ native code æ¥å®ç°çåè½ç°å¨é½å¯ä»¥å®ç°äºãGamepad(ææ) API æ¯å¼å人åå设计è
è¯å«åä½¿ç¨æ¸¸ææ§å¶æ¿åå
¶ä»æ¸¸ææ§å¶å¨çä¸ç§æ¹æ³ã
Gamepad API å¼å
¥æ°çäºä»¶å¨ Window 对象ä¸ï¼æ¥è¯»åææåæ§å¶å¨ï¼ä»¥ä¸ç§°âæ§å¶å¨âï¼çç¶æã餿¤ä¹å¤ï¼API è¿æ·»å äºä¸ä¸ª Gamepad 对象ï¼ä½ å¯ä»¥ç¨å®æ¥æ¥è¯¢å·²è¿æ¥æ§å¶å¨çç¶æï¼è¿æä¸ä¸ª navigator.getGamepads() æ¹æ³ï¼ä½ å¯ä»¥ä½¿ç¨å®æ¥è·å页é¢å·²ç¥çæ§å¶å¨å表ã
è¿æ¥æ§å¶å¨
å½ä¸ä¸ªæ°çææè¿æ¥å°è®¡ç®æºæ¶ï¼ç¦ç¹é¡µé¢ (å½å页é¢) é¦å
æ¥æ¶ä¸ä¸ª gamepadconnected äºä»¶ã妿å¨å è½½é¡µé¢æ¶å·²ç»è¿æ¥äºææï¼åä¼å¨ç¨æ·æä¸æä¸ªæé®æç§»å¨åæ æ¹å (axes) æ¶è§¦åç¦ç¹é¡µé¢ç gamepadconnected äºä»¶ã
夿³¨ï¼å¨ Firefox ä¸ï¼æ§å¶å¨åªä¼æ´é²ç»ä¸ç¨æ·äº§ç交äºçå¯è§é¡µé¢ãè¿æå©äºé²æ¢æ§å¶å¨è¢«ç¨äºè·åç¨æ·çæçº¹ã䏿¦æä¸ä¸ªææä¸é¡µé¢äº§ç交äºï¼é£ä¹å ¶ä»è¿æ¥çæ§å¶å¨å°èªå¨å¯¹é¡µé¢å¯è§ã
ä½ å¯ä»¥è¿æ ·ä½¿ç¨ gamepadconnected ï¼
window.addEventListener("gamepadconnected", function (e) {
console.log(
"æ§å¶å¨å·²è¿æ¥äº %d ä½ï¼%s. %d 个æé®ï¼%d ä¸ªåæ æ¹åã",
e.gamepad.index,
e.gamepad.id,
e.gamepad.buttons.length,
e.gamepad.axes.length,
);
});
æ¯ä¸ªæ§å¶å¨é½æä¸ä¸ªä¸ä¹å
³èçå¯ä¸ IDï¼å
¶å¨äºä»¶ç gamepad 屿§ä¸å¯ç¨ã
æå¼æ§å¶å¨è¿æ¥
彿§å¶å¨æå¼è¿æ¥æ¶ï¼å¦æé¡µé¢ä»¥åæ¥æ¶è¿è¯¥ææçæ°æ® (ä¾å¦ gamepadconnected)ï¼é£ä¹ç¬¬äºä¸ªäºä»¶ gamepaddisconnected å°ä¼åé
è³ç¦ç¹é¡µé¢ï¼
window.addEventListener("gamepaddisconnected", function (e) {
console.log("æ§å¶å¨å·²ä» %d 使å¼ï¼%s", e.gamepad.index, e.gamepad.id);
});
å³ä½¿ä½¿ç¨ç¸åç±»åçå¤ä¸ªæ§å¶å¨ï¼æ§å¶å¨ç index 屿§é½ä¼æ¯å¯ä¸çï¼æ¯ä¸ä¸ªè®¾å¤é½æä¸ä¸ªãindex 屿§è¿å¯å
å½ Navigator.getGamepads() è¿å Array çç´¢å¼ã
var gamepads = {};
function gamepadHandler(event, connecting) {
var gamepad = event.gamepad;
// 注ï¼
// 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 对象ãå 为å¯è½åæ¶è¿æ¥ä¸æ¢ä¸ä¸ªæ§å¶å¨ï¼æä»¥æä»¬å¯ä»¥ä½¿ç¨å®æ¥ç¡®å®æ¯åªä¸ªæ§å¶å¨ (æè
说 ID) 触åäºäºä»¶ãæä»¬å¯ä»¥ä½¿ç¨ Gamepad 对象åå¾å¤äºï¼æ¯å¦ä¿ç对象çå¼ç¨å¹¶ç¨å
¶æ¥è¯¢ï¼ä»¥æ¾åºåªäºæé®åææå¨ä»ä¹æ¶å被æä¸äºãç¸è¾äºå¨ä¸æ¬¡è§¦åï¼ç°å¨ç«å³å°±å¯ä»¥è·åæ§å¶å¨çç¶æå¯¹äºæ¸¸ææå
¶ä»äº¤äºå¼ç½é¡µæ¥è¯´æ¯ä¸è¬æ¯å¯åçã
å¼åè
æ§è¡æ¤ç±»æ¥è¯¢æ¶å¾å¾æ¶åå° Gamepad 对象åä¸ä¸ªå¨ç»å¾ªç¯ (ä¾å¦ requestAnimationFrame) ç»åå¨ä¸èµ·ï¼å¸ææ ¹æ®æ§å¶å¨çç¶ææ¥å¯¹å³å®å½åæ¡æ¶çè¡ä¸ºã
Navigator.getGamepads() æ¹æ³è¿åå½å对ç½é¡µå¯è§çææè®¾å¤çæ°ç»ï¼Gamepad 对象 (åå§å¼å§ç»ä¸º nullï¼æä»¥å½æ²¡ææ§å¶å¨è¿æ¥çæ¶åå°ä¼è¿å null ) ä¹ä¸æ ·å¯ä»¥ç¨æ¥è·åçæ§å¶å¨ä¿¡æ¯ãä¾å¦ä¸é¢å°ä¼éåå¼å¤´ç第ä¸ä¸ªä¾åï¼
window.addEventListener("gamepadconnected", function (e) {
var gp = navigator.getGamepads()[e.gamepad.index];
console.log(
"æ§å¶å¨å·²è¿æ¥äº %d ä½ï¼%s. %d 个æé®ï¼%d ä¸ªåæ æ¹åã",
gp.index,
gp.id,
gp.buttons.length,
gp.axes.length,
);
});
以䏿¯ Gamepad 对象ç屿§è¯´æï¼
-
id: å å«ä¸äºå ³äºæ§å¶å¨ä¿¡æ¯çå符串ãå®çæ ¼å¼æ²¡æç¹å®è§èï¼ä½æ¯å¨ Firefox ä¸å®å°ä¼å«æä¸é¨åä¿¡æ¯å¹¶ä»¥è¿æ¥çº¿è¿æ¥ (-)ï¼å 嫿§å¶å¨ç USB ä¾åºåã产å ID ç两个 4 ä½åå è¿å¶å符串以å驱å¨ç¨åºæä¾çæ§å¶å¨çåç§°ãæ¤ä¿¡æ¯æ¨å¨å è®¸ä½ ä¸ºè®¾å¤ä¸çæ§ä»¶æ¥æ¾éå½çæ å°ï¼å¹¶åç¨æ·æ¾ç¤ºæç¨çåé¦ã -
index: æ¯ä¸ªå·²è¿æ¥ææå¯ä¸çç´¢å¼æ°åï¼å¯ä»¥ç¨æ¥åºåå¤ä¸ªæ§å¶å¨ã请注æï¼æå¼ç®åç设å¤å¹¶è¿æ¥ä¸ä¸ªæ°ç设å¤å¯è½ä¼ä½¿ç¨ä¹åçç´¢å¼å¼ã -
mapping: ä¸ä¸ªç¨æ¥æç¤ºæµè§å¨æ¯å¦å·²å°è®¾å¤ä¸çæ§ä»¶éæ°æ å°å°å·²ç¥å¸å±çå符串ãç®ååªæä¸ä¸ªåæ¯æçå·²ç¥å¸å± - standard gamepad(æ åæ§å¶å¨)ã妿æµè§å¨è½å¤å°è®¾å¤ä¸çæ§ä»¶æ å°ä¸ºè¯¥å¸å±ï¼mapping屿§å°ä¼ä¸ºå符串standardã -
connected: ä¸ä¸ªç¨æ¥æç¤ºæ§å¶å¨æ¯å¦è¿æ¥å°ç³»ç»çå¸å°å¼ãå¦æå·²è¿æ¥å为Trueï¼åä¹å为Falseã -
buttons: ä¸ä¸ªè¡¨ç¤ºè®¾å¤ä¸æé®çGamepadButtonæ°ç»å¯¹è±¡ãæ¯ä¸ªGamepadButtoné½å«æä¸ä¸ªpressedåä¸ä¸ªvalue屿§ï¼pressed屿§æ¯ä¸ä¸ªè¡¨ç¤ºæé®å½åæ¯æä¸ (true) è¿æ¯æ²¡æä¸ (false) çå¸å°å¼ãvalue屿§æ¯ä¸ä¸ªè¡¨ç¤ºå·²è§¦å模ææé® (ä¾å¦ç®åå¾å¤æ§å¶å¨ç线æ§/æ®µå¼æ³æºé®) çå¼çæµ®ç¹å¼ãå¼çèå´è¢«è§è为 0.0 å° 1.0ã0.0 表示没被æä¸çæé®ï¼1.0 表示被å®å ¨æä¸çæé®ã
-
axes: ä¸ä¸ªè¡¨ç¤ºè®¾å¤ä¸åæ è¾å ¥æ§ä»¶ (ä¾å¦æ§å¶å¨ææ) çæ°ç»å¯¹è±¡ãæ°ç»ä¸çæ¯ä¸ªå¼é½æ¯ä»äº -1.0 å° 1.0 çæµ®ç¹å¼ï¼æ¥è¡¨ç¤ºåæ æ¹åçæä½ (-1.0) åæå¤§ (1.0) å¼ã -
timestamp: å®å°è¿åä¸ä¸ªDOMHighResTimeStampï¼è¯¥å¼è¡¨ç¤ºä¸æ¬¡æ´æ°æ¤æ§å¶å¨æ°æ®çæ¶é´ï¼ä»¥ä¾¿å¼åè ç¡®å®axesåbuttonæ°æ®æ¯å¦å·²ä»ç¡¬ä»¶æ´æ°ã该å¼å¿ é¡»ç¸å¯¹äºPerformanceTimingæ¥å£çnavigationStart对象ã弿¯åè°éå¢çï¼è¿æå³çå¯ä»¥éè¿å¯¹æ¯å¤§å°æ¥ç¡®å®æ°æ®æ´æ°çå å顺åºï¼å 为æ°çå¼å§ç»æ¯æ§çå¼å¤§ã请注æ Firefox å½å䏿¯æè¯¥å±æ§ã
夿³¨ï¼åºäºå®å
¨åå ï¼Gamepad å¯¹è±¡å¨ gamepadconnected äºä»¶ä¸å¯ç¨èå¨ Window 对象ä¸ä¸å¯ç¨ã䏿¦æä»¬å¾å°äºå¯¹å®çå¼ç¨ï¼æä»¬å°±å¯ä»¥è·åå
¶å±æ§ä»¥äºè§£æå
³æ§å¶å¨å½åç¶æçä¿¡æ¯ãå¨åå°ï¼æ¤å¯¹è±¡å°ä¼å¨æ§å¶å¨ç¶ææ´æ¹æ¶æ´æ°ã
ä½¿ç¨æé®ä¿¡æ¯
让æä»¬çä¸ä¸ªç®åç示ä¾ï¼æ¾ç¤ºä¸ä¸ªæ§å¶å¨çè¿æ¥ä¿¡æ¯ (忽ç¥åç»è¿æ¥çæ§å¶å¨) ï¼å¹¶è®©ä½ ä½¿ç¨æ§å¶å¨å³ä¾§çå个æä½æé®ç§»å¨å±å¹ä¸ä¸ä¸ªçãä½ å¯ä»¥ æ¥çå¨çº¿æ¼ç¤ºï¼å¹¶å¯å¨ Github 䏿¾å°æºä»£ç ã
æä»¬é¦å
声æä¸äºåéï¼gamepadInfo ç¨äºåå
¥è¿æ¥ä¿¡æ¯ç段è½ï¼ball æ¯æä»¬å¸ææ§å¶ç§»å¨ççï¼start ä½ä¸º requestAnimation Frame ID çåå§åéï¼ a å b åéä½ä¸ºçä½ç½®å¨éï¼å¹¶ä¸åéä¼è¢«ç¨äº requestAnimationFrame() å cancelAnimationFrame() ãï¼ï¼ï¼
var gamepadInfo = document.getElementById("gamepad-info");
var ball = document.getElementById("ball");
var start;
var a = 0;
var b = 0;
æ¥ä¸æ¥æä»¬ä½¿ç¨ gamepadconnected äºä»¶æ¥æ£æ¥æ§å¶å¨æ¯å¦è¿æ¥ã彿ä¸ä¸ªæ§å¶è¿æ¥æ¶ï¼æä»¬å°±ä½¿ç¨ Navigator.getGamepads()[0] æ¥æåï¼è¾åºæ§å¶å¨ä¿¡æ¯å°æä»¬âæ§å¶å¨ä¿¡æ¯âç div éï¼å¹¶å¼å§ gameLoop() 彿°æ¥å¯å¨ççè¿å¨è¿ç¨ã
window.addEventListener("gamepadconnected", function (e) {
var gp = navigator.getGamepads()[e.gamepad.index];
gamepadInfo.innerHTML =
"æ§å¶å¨å·²è¿æ¥äº " +
gp.index +
" ä½ï¼" +
gp.id +
"ã宿 " +
gp.buttons.length +
" 个æé®å " +
gp.axes.length +
" ä¸ªåæ æ¹åã";
gameLoop();
});
ç°å¨æä»¬åä½¿ç¨ gamepaddisconnected äºä»¶æ¥æ£æ¥å¦ææ§å¶å¨æå¼çæ
åµã妿æå¼äºï¼æä»¬ä¼åæ¢ requestAnimationFrame() å¾ªç¯ (è§ä¸æ¹) å¹¶éç½®æ§å¶å¨ä¿¡æ¯å°åæ¥çæ ·åã
window.addEventListener("gamepaddisconnected", function (e) {
gamepadInfo.innerHTML = "æ£å¨çå¾
æ§å¶å¨ã";
cancelRequestAnimationFrame(start);
});
Chrome å¨è¿éæäºåºå«ã宿²¡æå¨åéå
ä¸æçæ´æ°å卿§å¶å¨çæåç¶æï¼èåå¨åªæ¯å½æ¶çä¸ä¸ªå¿«ç
§ï¼æä»¥ä½ è¦å¨ Chrome ä¸åå°åæ ·çäºæ
çè¯ï¼å°±éè¦ä¸æå°è½®è¯¢ï¼ç¶åå¨å¯ç¨çæ¶ååªè½å¨ä»£ç ä¸ä½¿ç¨ Gamepad 对象æ¥è¾¾å°ç®çãæä»¬ä¸é¢ç¨ Window.setInterval()å®ç°äº; 䏿¦å¯¹è±¡å¯ç¨ï¼æ§å¶å¨ä¿¡æ¯å°±ä¼è¢«è¾åºï¼æ¸¸æå¾ªç¯å°±ä¼å¼å§ï¼å¯ä»¥ä½¿ç¨ Window.clearInterval() æ¸
é¤å®æ¶å¾ªç¯ã请注æå¨è¾æ§çæ¬ç Chrome ä¸å®ç° Navigator.getGamepads() éè¦å ä¸ webkit åç¼ãæä»¬å°è¯å¯¹ä¸¤ç§åç¼çæ¬é½è¿è¡çæµåå¤çï¼ä»¥ååå
¼å®¹ã
var interval;
if (!("ongamepadconnected" in window)) {
// æ²¡ææ§å¶å¨äºä»¶å¯ç¨ï¼åå¼å§è½®è¯¢ã
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 =
"æ§å¶å¨å·²è¿æ¥äº " +
gp.index +
" ä½ï¼" +
gp.id +
"ã宿 " +
gp.buttons.length +
" 个æé®å " +
gp.axes.length +
" ä¸ªåæ æ¹åã";
gameLoop();
clearInterval(interval);
}
}
}
ç°å¨ç主è¦ç游æå¾ªç¯ã卿¯æ¬¡æä»¬æéçå个æé®è¢«æä¸çæ¶åè¿è¡å¤çãå¦æè¢«æä¸äºæå°±ä¼éå½å°æ´æ°å¨éåé a å b çå¼ï¼ç¶ååå«ç¨ a å b ç弿´æ°çç left å top 屿§ãè¿æ ·å°±å¯ä»¥å¨å±å¹ä¸ç§»å¨æ°çä½ç½®äºãå¨å½åçæ¬ç Chrome ä¸ (çæ¬ 34) button ç弿¯åå¨ä¸ºæ°ç»ç两个å¼ï¼è䏿¯ GamepadButton å¯¹è±¡ãæ¤é®é¢å·²äºå¼åè
çæ¬ä¿®å¤äºã
å½è¿äºå¤ç好ä¹åï¼æä»¬ä½¿ç¨æä»¬ç requestAnimationFrame() æ¥è¯·æ±ä¸ä¸ä¸ªå¨ç»å¸§ï¼ç¶åè¿è¡ gameLoop() åç»§ç»æ§è¡ã
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);
}
宿´ç示ä¾ï¼æ¾ç¤ºæ§å¶å¨ç¶æ
è¿ä¸ªç¤ºä¾å±ç¤ºäºææ ·ä½¿ç¨ Gamepad 对象ï¼è¿æ gamepadconnected å gamepaddisconnected äºä»¶æ¾ç¤ºææå·²è¿æ¥å°ç³»ç»çæ§å¶å¨çç¶æãä½ å¯ä»¥æ¥çå¨çº¿æ¼ç¤ºå¹¶ä¸å¯å¨ 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.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);
// è§ 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);
}
è§è
| è§è |
|---|
| Gamepad > # gamepad-interface > |
| Gamepad Extensions > # partial-gamepad-interface > |