2017年1月18日星期三

HTML5 Gamepad API

最近再次拿起手掣玩遊戲,希望可以在錄製影像同時紀錄玩遊戲按按鈕等操作
讓觀眾可以欣賞玩家進行遊戲,同時可以了解玩家如何操作
最初使用 Java 希望可以達到跨平台效果,但發現有點麻煩
最後發現 Firefox 支援一個稱為 Gamepad API 的實驗性 API 可以達到相似效果


實際上 Gamepad API 已經開發了一段時間,早在 Firefox 24 已經投入其中
Firefox 可以到 about:config 的 dom.gamepad.enabled 啟動
Chrome 可以到 chrome://flags 的 enable-gamepad-extensions 啟動
由於必須支援 HTML5 ,因此編程上大可不用理會相容性,直接使用只支援 HTML5 的 HTML元件 及 Javascript語法 即可

由於手掣的動作在 Javascript 都是一種 event 因此先以
window.addEventListener('gamepadconnected', function(event){
    var gamepads = navigator.getGamepads();
});

獲取已連接的手掣資訊
以 window.addEventListener 檢查關於 gamepadconnected 的事件,並載入一個具備該事件的至功能中
navigator.getGamepads() 會傳回 array
array 中的 Gamepad 物件就是每一個手掣的資訊

按下閣下的手掣任何按鈕獲得手掣資訊

每一個手掣資訊最基本會包括:
  • id
    • 手掣的類別名稱
  • index
    • 手掣在系統中的編號
  • connected
    • 手掣是否連接系統
  • buttons
    • 手掣的按鈕資訊
  • axes
    • 手掣的轉軸資訊

除了使用 var gamepads = navigator.getGamepads();
其實 gamepadconnected 中的 event 已經存取正在使用的手掣
window.addEventListener('gamepadconnected', function(event){
    var gamepads = event.gamepad;
});
亦獲得正在使用的手掣的資訊

當中的 gamepad.buttons 及 gamepad.axes 都是傳回 array
gamepad.buttons 會列出手掣上所有按鈕,由 0 開始
gamepad.axes 會列出手掣上所有轉軸,由 0 開始
預設的 buttons 中的 GamepadButton 物件有 pressed 及 value 的屬性,根據不同性質使用不同屬性
當 button
pressed 為 true 與 value 為 1 是相同,都是按下的意思
pressed 為 false 與 value 為 0 是相同,都是沒有按下的意思
可是不是所有瀏覽器對 button 都是 GamepadButton 的類別
Firefox 是以 GamepadButton 類別,可是 Chrome 則是以 DOMNumber 類別
在 Chrome 中,button 只會以數值傳回
若 button 為 0 是沒有按下,若 button 為大於 0 是按下

對於 axis (這裡的 axes 的單數不是 axe)
由於 axis 不能單純地表達按下及沒有按下的狀態 (不是指方向掣 (D-Pad) 的上下左右方向) 而是轉軸的位置
使用 方向掣 與 類比(Analog) 操作,因此本質相同,但概念上不同
axis 是介乎 -1 至 1 之間的資料, x 軸數值越小越偏向左,越大越偏向右,y 軸數值越小越偏向上,越大越偏向下
使用方向掣,例如按下左按鈕,實際是將 x 軸數設定為 -1 (或少於 0 的數值)

部分瀏覽器對 D-Pad 的操作會當成 button 的概念進行檢查

由於需要不斷檢查 event 的狀態,可是使用如 for, while 等迴圈 (looping) 會因為太快不能顯示互動效果 (實際應該會 loop 死)
因此會使用 window.setTimeout 或 window.setInterval 並設定間距時間來模擬迴圈
但由於使用 window.setTimeout 或 window.setInterval 會因為固定了間距時間的數值
若果間距時間太少會佔用資源,但數值太大又不能產生即時互動的效果
幸好 Firefox 及其他瀏覽器陸續都支援 W3C 提出 HTML5 的 window.requestAnimationFrame
一般情況下 window.requestAnimationFrame 會以 60fps (1秒60幀 ~ 0.016秒) 的速度更新,然而還會根據資源使用量調整 fps 值
而使用 Gamepad API 必須使用 HTML5 ,因此建議使用 window.requestAnimationFrame 效果會比較好

function render(event){
    window.requestAnimationFrame(function(){
        var button = document.querySelector('div#test-button');
        if (event.gamepad.buttons[0].pressed){
            button.style.backgroundColor = '#ff0000';
        } else {
            button.style.backgroundColor = button.parentNode.style.backgroundColor;
        }
        window.requestAnimationFrame(render(event));
    });
}

window.addEventListener('gamepadconnected', function(event){
    render(event);
});

please press buttons[0]
因為 window.requestAnimationFrame 與 window.setTimeout 都只執行一次
所以在 callback 中,需要使用多一次 window.requestAnimationFrame callback 同一個功能,造成 recursive 效果的迴圈

function render(event){
    window.requestAnimationFrame(function(){
        var info = document.querySelector('div#analog-info');
        var pointer = document.querySelector('div#analog-pointer');
        var x = (event.gamepad.axes[0] * 50 + 50).toFixed(4);
        var y = (event.gamepad.axes[1] * 50 + 50).toFixed(4);
        info.innerHTML = x + '<' + 'br/' + '>' + y;
        pointer.style.left = x + 'px';
        pointer.style.top = y + 'px';
        window.requestAnimationFrame(render(event));
    });
}

window.addEventListener('gamepadconnected', function(event){
    render(event);
});

move axes[0] and axes[1]
當中的 event.gamepad.axes[0] 及 event.gamepad.axes[1] 一般多軸手掣是第一組 x軸 及 y軸
event.gamepad.axes[0] * 50 + 50 是將數值擴大至 0 至 100 ,較容易閱讀及理解,亦能讓演示的效果更容易表達
toFixed 是 Javascript 的數值功能,能將數值指定小數位的長度, toFixed(4) 即是有小數位長度為 4 ,不足會補 0

這樣便可以達到與按鈕及類比操作時與頁面產生較順暢互動的效果

Java swing 在更新手掣的按鈕顯示時有延遲
雖然 Linux 及 Mac 可以直接存取 /dev/input/js* 便可獲得手掣的訊息,但 Windows 要存取手掣訊息需要使用 dll 的原生功能
導致 Java 都不能真正跨平台地存取手掣訊息
而改用 HTML5 Gamepad API 除了基於跨平台性,還有因為瀏覽器能簡化多執行緒 (Multi-Threading) 的處理,減省很多多執行緒問題

另外對於 Gamepad API ,Firefox 的支援效果及流暢度比 Chrome 較好
IE 、 Opera 及 Safari 沒有 Gamepad API 的功能因此沒有提
聽說 Edge 支援 Gamepad API 但由於不是跨平台瀏覽器亦不在此說明

一個模擬 PS2 啟動 Analog 的手掣實作
L2
R2
L1
R1
Select
Start
L3
R3

沒有留言 :

發佈留言