2017-01-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 因此先以
1
2
3
window.addEventListener('gamepadconnected', function(event){
    console.log(event.gamepad);
});
便可以偵測到已經連接的手掣
但不同瀏覽器對 gamepadconnect event 的實作方法都不一樣
  • 有些會在連接著打開網頁便可以立即偵測到
  • 有些需要不連接著,要開啟網頁後,連接時才能偵測到
  • 有些會在連接後,按下手掣上某個觸法按鈕,才能偵測到

能夠偵測連接手掣,亦有能夠偵測取消連接的 event
1
2
3
window.addEventListener('gamepaddisconnected', function(event){
    console.log(event.gamepad);
});
gamepaddisconnect event 則比較統一獲取已連接的手掣資訊

每一個手掣資訊最基本會包括:

id
手掣的類別名稱 作業系統、瀏覽器、手掣驅動程式會影響類別名稱
index
手掣在系統中的編號
connected
手掣是否連接系統
buttons
手掣的按鈕資訊
axes
手掣的轉軸資訊

當中的 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 的概念進行檢查
由於現在有不少手掣的 Trigger (PS3 的 L2 及 R2 按鈕或 XBox360 的 LT 及 RT 按鈕) 都有壓力感應模擬
會當作 axis 概念進行檢查,不使用時是 -1 而不是 0

由於需要不斷檢查 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 效果會比較好

但在下發現不同瀏覽器對 Gamepad 都有不同
Firefox 的 Gamepad 物件當 手掣 有變化時直接更新 Gamepad 中的屬性資料
但 Chrome 的 Gamepad 物件則不會更新內容,需要使用 navigator.getGamepads(); 不斷接收 Gamepad 物件的新資料
然而,如果同一電腦超過一個網頁,便只有第一個存取手掣的資料網頁能夠持續存取手掣的資料
另外, navigator.getGamepads(); 在不同瀏覽器又有不同結果, Firefox 會傳回 array ,Chrome 會傳回 GamepadList
不過兩者都可以使用 JavaScript 的 for each 語法列出內容,因此影響不大

較舊版的 Firefox 及 Chrome 因為實作方式更不統一
window.requestAnimationFrame 及 navigator.getGamepads 可能需要使用前綴才能使用這些功能
Firefox 需要使用 window.mozRequestAnimationFrame 及 navigator.mozGetGamepads
Chrome 則需要使用 window.webkitRequestAnimationFrame 及 navigator.webkitGetGamepads
若想避免意外,可以使用
1
2
3
4
5
6
7
8
9
10
11
12
13
if (!!window.requestAnimationFrame){
    window.requestAnimationFrame(function(){
        // do something
    });
else if (!!window.mozRequestAnimationFrame){
    window.mozRequestAnimationFrame(function(){
        // do something
    });
else if (!!window.webkitRequestAnimationFrame){
    window.webkitRequestAnimationFrame(function(){
        // do something
    });
}

1
2
3
4
5
6
7
8
9
10
11
var gamepads = null;
if (!!navigator.getGamepads){
    gamepads = navigator.getGamepads();
else if (!!navigator.mozGetGamepads){
    gamepads = navigator.mozGetGamepads();
else if (!!navigator.webkitGetGamepads){
    gamepads = navigator.webkitGetGamepads();
}
if (gamepads != null){
    // do something
}

Javascript
(在下將不同功用的 JavaScript 分開,方便改動)

先前提及過不同作業系統、瀏覽器、手掣驅動程式會影響效果
因此需要根據不同作業系統、瀏覽器、手掣驅動程式而調整內容

default-driver.js
1
var drivers = {};

android-chrome-xbox360-0.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (!("Android" in drivers)){
    drivers["Android"] = {};
}
if (!("Chrome" in drivers["Android"])){
    drivers["Android"]["Chrome"] = {};
}
drivers["Android"]["Chrome"]["Microsoft X-Box 360 pad"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "l2",
        "7": "r2",
        "8": "se",
        "9": "st",
        "10": "l3",
        "11": "r3",
        "12": "du",
        "13": "dd",
        "14": "dl",
        "15": "dr",
        "16": "ho"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "analog", "key": "r3", "dimension": "x"},
        "3": {"type": "analog", "key": "r3", "dimension": "y"}
    }
};

android-firefox-xbox360-0.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (!("Android" in drivers)){
    drivers["Android"] = {};
}
if (!("Firefox" in drivers["Android"])){
    drivers["Android"]["Firefox"] = {};
}
drivers["Android"]["Firefox"]["android"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "l2",
        "7": "r2",
        "8": "se",
        "9": "st",
        "10": "l3",
        "11": "r3",
        "12": "du",
        "13": "dd",
        "14": "dl",
        "15": "dr",
        "16": "ho"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "analog", "key": "r3", "dimension": "x"},
        "3": {"type": "analog", "key": "r3", "dimension": "y"}
    }
};

linux-chrome-xbox360-0.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (!("Linux" in drivers)){
    drivers["Linux"] = {};
}
if (!("Chrome" in drivers["Linux"])){
    drivers["Linux"]["Chrome"] = {};
}
drivers["Linux"]["Chrome"]["Microsoft Inc. Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "l2",
        "7": "r2",
        "8": "se",
        "9": "st",
        "10": "l3",
        "11": "r3",
        "12": "du",
        "13": "dd",
        "14": "dl",
        "15": "dr",
        "16": "ho"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "analog", "key": "r3", "dimension": "x"},
        "3": {"type": "analog", "key": "r3", "dimension": "y"}
    }
};

linux-chrome-xbox360-1.js
(在 Linux 使用 Chrome 連接 XBox360 手掣有兩種驅動程式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (!("Linux" in drivers)){
    drivers["Linux"] = {};
}
if (!("Chrome" in drivers["Linux"])){
    drivers["Linux"]["Chrome"] = {};
}
drivers["Linux"]["Chrome"]["Inno GamePad.. Inno GamePad.. (STANDARD GAMEPAD Vendor: 045e Product: 028e)"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "l2",
        "7": "r2",
        "8": "se",
        "9": "st",
        "10": "l3",
        "11": "r3",
        "12": "du",
        "13": "dd",
        "14": "dl",
        "15": "dr",
        "16": "ho"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "analog", "key": "r3", "dimension": "x"},
        "3": {"type": "analog", "key": "r3", "dimension": "y"}
    }
};

linux-firefox-xbox360-0.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (!("Linux" in drivers)){
    drivers["Linux"] = {};
}
if (!("Firefox" in drivers["Linux"])){
    drivers["Linux"]["Firefox"] = {};
}
drivers["Linux"]["Firefox"]["045e-028e-Microsoft X-Box 360 pad"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "se",
        "7": "st",
        "8": "ho",
        "9": "l3",
        "10": "r3"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "zbutton", "key": "l2", "dimension": "z"},
        "3": {"type": "analog", "key": "r3", "dimension": "x"},
        "4": {"type": "analog", "key": "r3", "dimension": "y"},
        "5": {"type": "zbutton", "key": "r2", "dimension": "z"},
        "6": {"type": "dpad", "key": {"<": "dl", ">": "dr"}, "dimension": "x"},
        "7": {"type": "dpad", "key": {"<": "du", ">": "dd"}, "dimension": "y"}
    }
};

windows-chrome-xbox360-0.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (!("Windows" in drivers)){
    drivers["Windows"] = {};
}
if (!("Chrome" in drivers["Windows"])){
    drivers["Windows"]["Chrome"] = {};
}
drivers["Windows"]["Chrome"]["Xbox 360 Controller (XInput STANDARD GAMEPAD)"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "l2",
        "7": "r2",
        "8": "se",
        "9": "st",
        "10": "l3",
        "11": "r3",
        "12": "du",
        "13": "dd",
        "14": "dl",
        "15": "dr",
        "16": "ho"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "analog", "key": "r3", "dimension": "x"},
        "3": {"type": "analog", "key": "r3", "dimension": "y"}
    }
};

windows-firefox-xbox360-0.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
if (!("Windows" in drivers)){
    drivers["Windows"] = {};
}
if (!("Firefox" in drivers["Windows"])){
    drivers["Windows"]["Firefox"] = {};
}
drivers["Windows"]["Firefox"]["xinput"] = {
    "style": "xbox360",
    "buttons": {
        "0": "cr",
        "1": "ci",
        "2": "sq",
        "3": "tr",
        "4": "l1",
        "5": "r1",
        "6": "l2",
        "7": "r2",
        "8": "se",
        "9": "st",
        "10": "l3",
        "11": "r3",
        "12": "du",
        "13": "dd",
        "14": "dl",
        "15": "dr"
    },
    "axes": {
        "0": {"type": "analog", "key": "l3", "dimension": "x"},
        "1": {"type": "analog", "key": "l3", "dimension": "y"},
        "2": {"type": "analog", "key": "r3", "dimension": "x"},
        "3": {"type": "analog", "key": "r3", "dimension": "y"}
    }
};

generic-gamepad.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function GenericGamepad(gamepad, driver){
    var _driver = "" + gamepad.id;
    var _index = "" + gamepad.index;
    var _id = _driver + "[" + _index + "]";
    var _style = null;
    var _buttons = {};
    var _analogs = {};
    var _dpads = {};
    var _zbuttons = {};
    var _values = {};
    if (driver != null){
        _style = driver.style;
        for (var i in driver.buttons){
            _buttons[i] = new GenericGamepadButton(i, driver.buttons[i], gamepad.buttons[i].value);
        };
        for (var i in driver.axes){
            var axis = driver.axes[i];
            if (axis.type == "analog"){
                _analogs[i] = new GenericGamepadAnalog(i, axis.key, axis.dimension, gamepad.axes[i]);
            } else if (axis.type == "dpad"){
                _dpads[i] = new GenericGamepadDPad(i, axis.key, gamepad.axes[i]);
            } else if (axis.type == "zbutton"){
                _zbuttons[i] = new GenericGamepadZButton(i, axis.key, gamepad.axes[i]);
            } else if (axis.type == "value"){
                _values[i] = new GenericGamepadValue(i, axis.key, gamepad.axes[i]);
            }
        };
    }
    this.getDriver = function(){ return _driver; };
    this.getIndex = function(){ return _index;} ;
    this.getId = function(){ return _id; };
    this.getStyle = function(){ return _style; };
    this.getButtons = function(){ return _buttons; };
    this.getAnalogs = function(){ return _analogs; };
    this.getDPads = function(){ return _dpads; };
    this.getZButtons = function(){ return _zbuttons; };
    this.getValues = function(){ return _values; };
}

generic-gamepad-button.js
1
2
3
4
5
6
7
8
9
function GenericGamepadButton(index, reference, value){
    var _index = index;
    var _reference = reference;
    var _value = value;
    this.getIndex = function(){ return _index; };
    this.getReference = function(){ return _reference; };
    this.getValue = function(){ return _value; };
    this.isPressed = function(){ return this.getValue() > 0; };
}

generic-gamepad-analog.js
1
2
3
4
5
6
7
8
9
10
function GenericGamepadAnalog(index, reference, dimension, value){
    var _index = index;
    var _reference = reference;
    var _dimension = dimension;
    var _value = value;
    this.getIndex = function(){ return _index; };
    this.getReference = function(){ return _reference; };
    this.getDimension = function(){ return _dimension; };
    this.getValue = function(){ return _value; };
}

generic-gamepad-dpad.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function GenericGamepadDPad(index, references, value){
    var _index = index;
    var _references = references;
    var _value = value;
    this.getIndex = function(){ return _index; };
    this.getReferences = function(){ return _references; };
    this.getValue = function(){ return _value; };
    this.getDirection = function(){
        if (this.getValue() < 0){
            return "<";
        } else if (this.getValue() > 0){
            return ">";
        } else {
            return "=";
        }
    };
}

generic-gamepad-zbutton.js
1
2
3
4
5
6
7
8
9
function GenericGamepadZButton(index, reference, value){
    var _index = index;
    var _reference = reference;
    var _value = value;
    this.getIndex = function(){ return _index; };
    this.getReference = function(){ return _reference; };
    this.getValue = function(){ return _value; };
    this.isPressed = function(){ return this.getValue() > -1; };
}

generic-gamepad-value.js
1
2
3
4
5
6
7
8
9
function GenericGamepadValue(index, references, value){
    var _index = index;
    var _references = references;
    var _value = value.toFixed(7);
    _value = _value.substring(0, _value.length - 1);
    this.getIndex = function(){ return _index; };
    this.getReferences = function(){ return _references; };
    this.getValue = function(){ return _value; };
}

user-agent.js
網上可能有更準確的 JavaScript User Agent Detector 來檢查作業系統或瀏覽器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function UserAgent(){
    var userAgent = navigator.userAgent.toLowerCase();
    var _os = null;
    if (userAgent.indexOf("win") >= 0){
        _os = "Windows";
    } else if (userAgent.indexOf("android") >= 0){
        _os = "Android";
    } else if (userAgent.indexOf("linux") >= 0){
        _os = "Linux";
    }
    var _browser = null;
    if (userAgent.indexOf("firefox") >= 0){
        _browser = "Firefox";
    } else if (userAgent.indexOf("chrome") >= 0){
        _browser = "Chrome";
    }
    this.getOS = function(){ return _os; };
    this.getBrowser = function(){ return _browser; };
}

gamepad-detector.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
function GamepadDetector(gamepadContainer, drivers){
    var userAgent = new UserAgent();
    console.log("Your OS is " + userAgent.getOS());
    console.log("Your browser is " + userAgent.getBrowser());
    var _gamepads = {};
    var _getDrivers = function(){
        var os = userAgent.getOS();
        var browser = userAgent.getBrowser();
        if (os in drivers && browser in drivers[os]){
            return drivers[os][browser];
        } else {
            return null;
        }
    };
    var _setGamepad = function(gamepad){
        var availableDrivers = _getDrivers();
        if (gamepad instanceof Gamepad && availableDrivers != null && gamepad.id in availableDrivers){
            var gamepad = new GenericGamepad(gamepad, availableDrivers[gamepad.id]);
            var id = gamepad.getId();
            var gamepadTemplate = document.getElementById(id);
            _gamepads[id] = gamepad;
            if (gamepadTemplate == null){
                gamepadTemplate = document.getElementById("xbox360-gamepad-template").cloneNode(true);
                gamepadTemplate.setAttribute("id", id);
                gamepadContainer.appendChild(gamepadTemplate);
            }
        }
    };
    var _removeGamepad = function(gamepad){
        if (gamepad instanceof Gamepad){
            var id = gamepad.id + "[" + gamepad.index + "]";
            var template = document.getElementById(id);
            if (template != null){
                template.parentNode.removeChild(template);
                delete _gamepads[id];
            }
        }
    };
    var _updateGamepads = function(){
        for (var i in _gamepads){
            var gamepad = _gamepads[i];
            var template = document.getElementById(gamepad.getId());
            var buttons = gamepad.getButtons();
            var analogs = gamepad.getAnalogs();
            var dpads = gamepad.getDPads();
            var zbuttons = gamepad.getZButtons();
            var values = gamepad.getValues();
            _updateGamepadButtons(template, buttons);
            _updateGamepadAnalogs(template, analogs);
            _updateGamepadDPads(template, dpads);
            _updateGamepadZButtons(template, zbuttons);
            _updateGamepadValues(template, values);
        }
        var gamepads = navigator.getGamepads();
        for (var i in gamepads){
            _setGamepad(gamepads[i]);
        }
        window.requestAnimationFrame(function(){
            _updateGamepads();
        });
    };
    var _referenceToElement = function(template, reference){
        return template.getElementsByClassName("template " + reference)[0];
    }
    var _updateGamepadButtons = function(template, buttons){
        for (var i in buttons){
            var button = buttons[i];
            _updateGamepadButton(template, button);
        }
    };
    var _updateGamepadButton = function(template, button){
        var element = _referenceToElement(template, button.getReference());
        if (element != null){
            element.setAttribute("fill-opacity", ((button.isPressed()) ? 1 : 0));
        }
    };
    var _updateGamepadAnalogs = function(template, analogs){
        for (var i in analogs){
            var analog = analogs[i];
            _updateGamepadAnalog(template, analog);
        }
    };
    var _updateGamepadAnalog = function(template, analog){
        var element = _referenceToElement(template, analog.getReference());
        if (element != null){
            var shadowElement = _referenceToElement(template, analog.getReference() + "-shadow");
            var baseElement = _referenceToElement(template, analog.getReference() + "-base");
            var name = "c" + analog.getDimension();
            var baseValue = baseElement.getAttribute(name);
            var value = baseValue - (-10 * analog.getValue());
            element.setAttribute(name, value);
            shadowElement.setAttribute(name, value);
        }
    };
    var _updateGamepadDPads = function(template, dpads){
        for (var i in dpads){
            var dpad = dpads[i];
            _updateGamepadDPad(template, dpad);
        }
    };
    var _updateGamepadDPad = function(template, dpad){
        var references = dpad.getReferences();
        var direction = dpad.getDirection();
        for (var i in references){
            var element = _referenceToElement(template, references[i]);
            if (element != null){
                element.setAttribute("fill-opacity", 0);
            }
        }
        if (direction != "="){
            var element = _referenceToElement(template, references[direction]);
            if (element != null){
                element.setAttribute("fill-opacity", 1);
            }
        }
    };
    var _updateGamepadZButtons = function(template, zbuttons){
        for (var i in zbuttons){
            var zbutton = zbuttons[i];
            _updateGamepadZButton(template, zbutton);
        }
    };
    var _updateGamepadZButton = function(template, zbutton){
        var element = _referenceToElement(template, zbutton.getReference());
        if (element != null){
            element.setAttribute("fill-opacity", ((zbutton.isPressed()) ? 1 : 0));
        }
    };
    var _updateGamepadValues = function(template, values){
        for (var i in values){
            var value = values[i];
            _updateGamepadValue(template, value);
        }
    };
    var _updateGamepadValue = function(template, value){
        _resetGamepadDPads(template);
        var references = value.getReferences();
        for (var i in references){
            var reference = references[i];
            if (i == value.getValue()){
                for (var j in reference){
                    var element = template.getElementsByClassName("template " + reference[j])[0];
                    if (element != null){
                        element.setAttribute("fill-opacity", 1);
                    }
                }
            }
        }
    };
    var _resetGamepadDPads = function(template){
        var defaultDPadReferences = ["du", "dd", "dl", "dr"];
        for (var i in defaultDPadReferences){
            var defaultDPadReference = defaultDPadReferences[i];
            var element = template.getElementsByClassName("template " + defaultDPadReference)[0];
            if (element != null){
                element.setAttribute("fill-opacity", 0);
            }
        }
    };
    this.start = function(){
        _updateGamepads();
    };
    window.addEventListener("gamepadconnected", function(event){
        var gamepad = event.gamepad;
        var availableDrivers = _getDrivers();
        if (gamepad instanceof Gamepad && availableDrivers != null && gamepad.id in availableDrivers){
            _setGamepad(gamepad);
            console.log("Gamepad: \"" + gamepad.id + "[" + gamepad.index + "]" + "\" is connected.");
        }
    });
    window.addEventListener("gamepaddisconnected", function(event){
        var gamepad = event.gamepad;
        if (gamepad instanceof Gamepad){
            _removeGamepad(gamepad);
            console.log("Gamepad: \"" + gamepad.id + "[" + gamepad.index + "]" + "\" is disconnected.");
        }
    });
}

initial.js
1
2
3
4
5
window.addEventListener("load", function(event){
    var gamepadContainer = document.getElementById("gamepad-container");
    var gamepadDetector = new GamepadDetector(gamepadContainer, drivers);
    gamepadDetector.start();
});

xbox360.svg
(網上有很多 XBox360 Gamepad SVG ,但在下將對視覺上不太影響的元件刪除,並加入需要的功能)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<svg width="320" height="203.21" version="1.1" xmlns="http://www.w3.org/2000/svg">
    <g stroke="#101010">
        <g fill="#c0c0c0">
            <path d="m303.27 102.26s-12.013-35.979-18.784-45.907c-8.9447-13.117-22.467-27.89-27.536-29.977-5.0685-2.0867-21.467-2.4459-25.641-0.65743-4.1739 1.7887-17.793 13.976-28.228 16.956-10.436 2.9808-71.548 1.4905-79.896 0.29821-8.3483-1.1923-12.558-1.4293-19.713-6.1988-7.1557-4.7695-10.724-10.626-13.11-11.819-2.3849-1.1923-19.854-1.0256-24.326 0.76262-4.4724 1.7887-19.31 15.957-30.341 34.141-11.032 18.184-22.799 58.132-23.923 67.441-2.0872 17.29-1.0871 31.389-0.56118 34.281 0.52594 2.8927 4.4597 22.785 19.178 28.977 19.07 8.0225 46.197-8.4519 65.61-16.057 20.23-7.9244 24.991-7.2567 39.112-7.2567 28.026 0 25.933-0.29794 53.966-0.29794 28.623 0 52.44 15.072 65.559 20.139s22.73 8.706 35.253 0.65744c12.522-8.0485 17.556-23.48 19.345-38.981s-5.9629-46.504-5.9629-46.504z"/>
            <ellipse cx="114.96" cy="145.87" rx="33.507" ry="27.215"/>
            <ellipse cx="203.74" cy="146.24" rx="30.847" ry="22.899"/>
            <path d="m71.356 78.168c-17.88-3.1638-34.006 5.3862-36.019 19.098-2.0125 13.711 10.85 27.391 28.73 30.555 17.88 3.1638 34.006-5.3862 36.019-19.098 2.0128-13.711-10.85-27.391-28.73-30.555z"/>
            <ellipse cx="114.54" cy="149.66" rx="28.466" ry="23.566"/>
        </g>
        <g fill="#404040">
            <ellipse cx="203.28" cy="150.55" rx="21.336" ry="15.425"/>
            <ellipse cx="115.01" cy="150.17" rx="24.069" ry="19.822"/>
            <ellipse cx="68.214" cy="107.63" rx="21.336" ry="15.425"/>
        </g>
        <path d="m199.43 47.373c-1.5347 0.37973-72.974 1.0892-78.042-0.22379-13.028-3.3745-24.604-6.6677-24.604-6.6677v0.59379s7.9628 2.6781 24.38 6.9685c6.3442 1.658 72.615 1.3414 78.265 0.22353 15.358-3.0386 27.393-7.1544 27.393-7.1544v-0.8941c0-2.63e-4 -12.441 3.4547-27.393 7.1542z" fill="#101010" stroke-width=".5"/>
        <path d="m227.55 54.903s-18.029 9.9795-31.862 15.539c-1.3575 0.54567-17.687 1.1203-34.671 0.96353-17.012-0.15673-34.679-1.0464-37.025-2.1787-12.269-5.9227-28.463-15.059-28.463-15.059v0.83362s11.384 6.8494 27.596 14.972c2.9963 1.5013 20.379 2.0483 37.795 2.2445 16.441 0.18487 32.855-0.076 35.478-1.0514 15.831-5.8874 31.76-15.71 31.76-15.71z" fill="#101010" stroke-width=".5"/>
    </g>
    <g>
        <ellipse cx="161.42" cy="104.72" rx="18.546" ry="15.049" fill="#40f000"/>
        <rect x="160.34" y="89.567" width="1.8737" height="31.574" fill="#c0c0c0"/>
        <rect x="141.55" y="104.42" width="39.447" height="1.8737" fill="#c0c0c0"/>
    </g>
    <ellipse cx="114.46" cy="152.67" rx="22.284" ry="18.066" fill="#808080" stroke-width="0"/>
    <path d="m135.97 147.57s-7.5015-0.17541-10.542-3.4323c-3.0405-3.2572-2.9148-8.4643-2.9148-8.4643s-4.2086-1.0687-8.0464-1.1755c-3.978-0.11045-7.765 1.1794-7.765 1.1794s0.43416 5.5456-2.606 8.8027c-3.0405 3.2569-10.854 2.7849-10.854 2.7849s-1.0519 2.6179-1.0519 5.3326c0 2.9229 1.2273 6.0457 1.2273 6.0457s7.6806-0.51805 10.504 2.305c2.8233 2.8225 2.5185 8.6313 2.5185 8.6313s4.5573 1.227 8.733 1.227c4.2922 0 8.7322-1.753 8.7322-1.753s-0.68951-2.7191 2.5682-7.4958c3.2574-4.7769 9.6311-4.3509 9.6311-4.3509s0.57854-2.1548 0.63192-4.6112c0.0553-2.5358-0.76525-5.0257-0.76525-5.0257z" fill="#808080" stroke-width="0"/>
    <g>
        <ellipse cx="114.46" cy="152.67" rx="22.284" ry="18.066" fill-opacity="0" stroke="#101010"/>
        <path d="m135.97 147.57s-7.5015-0.17541-10.542-3.4323c-3.0405-3.2572-2.9148-8.4643-2.9148-8.4643s-4.2086-1.0687-8.0464-1.1755c-3.978-0.11045-7.765 1.1794-7.765 1.1794s0.43416 5.5456-2.606 8.8027c-3.0405 3.2569-10.854 2.7849-10.854 2.7849s-1.0519 2.6179-1.0519 5.3326c0 2.9229 1.2273 6.0457 1.2273 6.0457s7.6806-0.51805 10.504 2.305c2.8233 2.8225 2.5185 8.6313 2.5185 8.6313s4.5573 1.227 8.733 1.227c4.2922 0 8.7322-1.753 8.7322-1.753s-0.68951-2.7191 2.5682-7.4958c3.2574-4.7769 9.6311-4.3509 9.6311-4.3509s0.57854-2.1548 0.63192-4.6112c0.0553-2.5358-0.76525-5.0257-0.76525-5.0257z" fill-opacity="0" stroke="#101010"/>
        <g fill="#ff0000" fill-opacity=".01">
            <path class="template du" d="m125.42 144.14c-3.0405-3.2572-2.9148-8.4643-2.9148-8.4643s-4.2086-1.0687-8.0464-1.1755c-3.978-0.11045-7.765 1.1794-7.765 1.1794s0.43416 5.5456-2.606 8.8027z"/>
            <path class="template dd" d="m103.92 160.95c2.8233 2.8225 2.5185 8.6313 2.5185 8.6313s4.5573 1.227 8.733 1.227c4.2922 0 8.7322-1.753 8.7322-1.753s-0.68951-2.7191 2.5682-7.4958z"/>
            <path class="template dl" d="m104.09 144.48c-3.0405 3.2569-10.854 2.7849-10.854 2.7849s-1.0519 2.6179-1.0519 5.3326c0 2.9229 1.2273 6.0457 1.2273 6.0457s7.6806-0.51805 10.504 2.305z"/>
            <path class="template dr" d="m135.97 147.57s-7.5015-0.17541-10.542-3.4323l1.0443 17.42c3.2574-4.7769 9.6311-4.3509 9.6311-4.3509s0.57854-2.1548 0.63192-4.6112c0.0553-2.5358-0.76525-5.0257-0.76525-5.0257z"/>
        </g>
        <g fill="#101010">
            <path d="m113.1 89.186v2.6737h1.5837q0.79673 0 1.178-0.32749 0.38615-0.33238 0.38615-1.0118 0-0.68431-0.38615-1.0069-0.38126-0.32749-1.178-0.32749zm0-3.0012v2.1996h1.4615q0.72341 0 1.0754-0.26884 0.35681-0.27372 0.35681-0.83095 0-0.55234-0.35681-0.82606-0.35194-0.27372-1.0754-0.27372zm-0.98736-0.8114h2.5222q1.1291 0 1.7401 0.46924t0.61099 1.3344q0 0.66965-0.31283 1.0656-0.31283 0.39592-0.91893 0.49368 0.7283 0.15641 1.1291 0.65498 0.40571 0.49368 0.40571 1.2366 0 0.97759-0.66477 1.5104-0.66476 0.53278-1.8916 0.53278h-2.6199z"/>
            <path d="m121.42 89.919q-1.09 0-1.5104 0.24929-0.42036 0.24928-0.42036 0.8505 0 0.47902 0.31283 0.76252 0.31771 0.27861 0.86027 0.27861 0.74786 0 1.1976-0.5279 0.45457-0.53278 0.45457-1.4126v-0.20041zm1.7939-0.37148v3.1234h-0.89938v-0.83095q-0.30794 0.49857-0.7674 0.73808-0.45946 0.23462-1.1242 0.23462-0.84072 0-1.3393-0.46924-0.49368-0.47413-0.49368-1.266 0-0.92382 0.61588-1.3931 0.62077-0.46924 1.8476-0.46924h1.2611v-0.08798q0-0.62077-0.41058-0.95804-0.4057-0.34216-1.1438-0.34216-0.46924 0-0.91404 0.11242t-0.85539 0.33727v-0.83095q0.49368-0.19063 0.95803-0.2835 0.46436-0.09776 0.90427-0.09776 1.1878 0 1.7743 0.61588 0.58655 0.61588 0.58655 1.8672z"/>
            <path d="m129.01 87.407v0.84072q-0.38125-0.21018-0.7674-0.31283-0.38126-0.10754-0.77229-0.10754-0.87494 0-1.3588 0.55722-0.48391 0.55234-0.48391 1.5544 0 1.002 0.48391 1.5593 0.4839 0.55234 1.3588 0.55234 0.39103 0 0.77229-0.10265 0.38615-0.10753 0.7674-0.31772v0.83095q-0.37636 0.17597-0.78207 0.26395-0.40081 0.08798-0.85538 0.08798-1.2366 0-1.965-0.77718t-0.7283-2.0969q0-1.3393 0.73319-2.1067 0.73808-0.76741 2.0187-0.76741 0.41547 0 0.81139 0.08798 0.39592 0.0831 0.7674 0.25417z"/>
            <path d="m130.55 85.065h0.90427v4.492l2.6835-2.3609h1.1487l-2.9034 2.5613 3.0256 2.9132h-1.1731l-2.7812-2.6737v2.6737h-0.90427z"/>
            <path d="m194.34 85.613v0.96292q-0.56212-0.26884-1.0607-0.40081-0.49857-0.13198-0.96293-0.13198-0.8065 0-1.2464 0.31283-0.43503 0.31283-0.43503 0.8896 0 0.4839 0.28839 0.73319 0.29328 0.2444 1.1047 0.39592l0.59632 0.1222q1.1047 0.21018 1.6277 0.74297 0.52789 0.5279 0.52789 1.4175 0 1.0607-0.71364 1.6081-0.70875 0.54745-2.0823 0.54745-0.51812 0-1.1047-0.11731-0.58166-0.11731-1.2073-0.34704v-1.0167q0.60122 0.33727 1.178 0.50835 0.57677 0.17108 1.134 0.17108 0.84562 0 1.3051-0.33238 0.45947-0.33238 0.45947-0.94826 0-0.53767-0.33239-0.84073-0.32748-0.30305-1.0802-0.45458l-0.60121-0.11731q-1.1047-0.21996-1.5984-0.6892-0.49369-0.46924-0.49369-1.3051 0-0.96781 0.67942-1.525 0.68432-0.55722 1.8819-0.55722 0.51323 0 1.046 0.09287t1.09 0.27861z"/>
            <path d="m197.17 85.642v1.5544h1.8525v0.69898h-1.8525v2.9719q0 0.66965 0.18086 0.86028 0.18575 0.19063 0.74786 0.19063h0.92381v0.75274h-0.92381q-1.0411 0-1.4371-0.38615-0.39592-0.39103-0.39592-1.4175v-2.9719h-0.65987v-0.69898h0.65987v-1.5544z"/>
            <path d="m202.7 89.919q-1.09 0-1.5104 0.24929-0.42037 0.24928-0.42037 0.8505 0 0.47902 0.31283 0.76252 0.31772 0.27861 0.86028 0.27861 0.74785 0 1.1975-0.5279 0.45458-0.53278 0.45458-1.4126v-0.20041zm1.7939-0.37148v3.1234h-0.89938v-0.83095q-0.30794 0.49857-0.76741 0.73808-0.45947 0.23462-1.1242 0.23462-0.84072 0-1.3393-0.46924-0.49368-0.47413-0.49368-1.266 0-0.92382 0.61587-1.3931 0.62077-0.46924 1.8476-0.46924h1.2611v-0.08798q0-0.62077-0.41059-0.95804-0.4057-0.34216-1.1438-0.34216-0.46924 0-0.91405 0.11242-0.4448 0.11242-0.85538 0.33727v-0.83095q0.49368-0.19063 0.95803-0.2835 0.46436-0.09776 0.90427-0.09776 1.1878 0 1.7743 0.61588 0.58655 0.61588 0.58655 1.8672z"/>
            <path d="m209.52 88.037q-0.15153-0.08798-0.33238-0.12709-0.17597-0.04399-0.39103-0.04399-0.76252 0-1.1731 0.49857-0.40569 0.49368-0.40569 1.4224v2.8839h-0.90427v-5.4745h0.90427v0.8505q0.28349-0.49857 0.73807-0.73808 0.45457-0.2444 1.1047-0.2444 0.0929 0 0.2053 0.01467 0.11242 0.0098 0.24928 0.03422l5e-3 0.92382z"/>
            <path d="m211.36 85.642v1.5544h1.8525v0.69898h-1.8525v2.9719q0 0.66965 0.18085 0.86028 0.18574 0.19063 0.74786 0.19063h0.92381v0.75274h-0.92381q-1.0411 0-1.4371-0.38615-0.39592-0.39103-0.39592-1.4175v-2.9719h-0.65987v-0.69898h0.65987v-1.5544z"/>
        </g>
    </g>
    <ellipse class="template l3-base" cx="68.619" cy="114.47" rx="18.699" ry="15.084" fill-opacity="0" stroke-opacity="0"/>
    <ellipse class="template r3-base" cx="203.68" cy="157.39" rx="18.699" ry="15.084" fill-opacity="0" stroke-opacity="0"/>
    <g stroke="#101010">
        <ellipse class="template l3-shadow" cx="68.619" cy="114.47" rx="18.699" ry="15.084" fill="#808080"/>
        <ellipse class="template r3-shadow" cx="203.68" cy="157.39" rx="18.699" ry="15.084" fill="#808080"/>
        <ellipse class="template l3" cx="68.619" cy="114.47" rx="18.699" ry="15.084" fill="#ff0000" fill-opacity=".01"/>
        <ellipse class="template r3" cx="203.68" cy="157.39" rx="18.699" ry="15.084" fill="#ff0000" fill-opacity=".01"/>
        <ellipse cx="161.42" cy="104.94" rx="14.537" ry="11.796" fill="#c0c0c0"/>
    </g>
    <g fill="#ff0000" fill-opacity=".01" stroke="#101010">
        <ellipse class="template ho" cx="161.42" cy="104.94" rx="14.537" ry="11.796"/>
        <path class="template se" d="m130.75 100.18h-5.6318c-2.8501 0-5.1608 2.3102-5.1608 5.16 0 2.8496 2.3107 5.1598 5.1608 5.1598h5.6318c2.8504 0 5.1608-2.3102 5.1608-5.1598 0-2.8498-2.3105-5.16-5.1608-5.16z"/>
        <path class="template st" d="m198.17 100.18h-5.6318c-2.8504 0-5.1608 2.3102-5.1608 5.16 0 2.8496 2.3105 5.1598 5.1608 5.1598h5.6318c2.8501 0 5.1608-2.3102 5.1608-5.1598 0-2.8498-2.3107-5.16-5.1608-5.16z"/>
    </g>
    <path d="m130.23 109.15-6.7534-3.8991 6.7534-3.8991z" fill="#101010" stroke-width="0"/>
    <path d="m192.84 109.15 6.7534-3.8991-6.7534-3.8991z" fill="#101010" stroke-width="0"/>
    <g stroke="#101010">
        <g>
            <path d="m85.932 11.521c-0.66795-1.9639-16.536-0.80338-18.017 1.0387-0.91304 1.4571-0.09651 5.5345-0.86386 10.76-1.2136 8.2679-3.1586 15.811-3.1586 15.811l19.472-1.307s2.4746-3.8554 2.8772-8.6118c0.52805-6.2345 0.88017-14.194-0.30952-17.692z" fill="#c0c0c0"/>
            <path d="m256.24 23.32c-0.76709-5.2258 0.0495-9.3032-0.8636-10.76-1.4805-1.8421-17.349-3.0026-18.017-1.0387-1.1897 3.4975-0.83757 11.457-0.30979 17.692 0.40235 4.7564 2.8772 8.6118 2.8772 8.6118l19.472 1.307c2.6e-4 0-1.9444-7.5436-3.1586-15.811z" fill="#c0c0c0"/>
            <path class="template l2" d="m85.932 11.521c-0.66795-1.9639-16.536-0.80338-18.017 1.0387-0.91304 1.4571-0.09651 5.5345-0.86386 10.76-1.2136 8.2679-3.1586 15.811-3.1586 15.811l19.472-1.307s2.4746-3.8554 2.8772-8.6118c0.52805-6.2345 0.88017-14.194-0.30952-17.692z" fill="#ff0000" fill-opacity=".01"/>
            <path class="template r2" d="m256.24 23.32c-0.76709-5.2258 0.0495-9.3032-0.8636-10.76-1.4805-1.8421-17.349-3.0026-18.017-1.0387-1.1897 3.4975-0.83757 11.457-0.30979 17.692 0.40235 4.7564 2.8772 8.6118 2.8772 8.6118l19.472 1.307c2.6e-4 0-1.9444-7.5436-3.1586-15.811z" fill="#ff0000" fill-opacity=".01"/>
        </g>
        <g fill="#101010" stroke-width=".5">
            <path d="m84.542 11.444c-0.21179 0.55406-0.11986 8.958-0.50917 14.962-0.38931 6.0036-1.5096 11.476-1.5096 11.476l0.84265-0.05361s1.3018-7.9841 1.447-11.33c0.14523-3.3461 0.38857-14.254 0.62477-14.712s0.35696-0.46991 0.35696-0.46991-0.15093-0.14534-0.35804-0.23618c-0.20712-0.09084-0.50332-0.1817-0.50332-0.1817s-0.17949-0.0079-0.39128 0.54619z"/>
            <path d="m238.6 11.402c0.22796 0.49025 0.11986 8.958 0.50916 14.962 0.38932 6.0036 1.6549 11.515 1.6549 11.515l-0.84568-0.04755s-1.4441-8.0295-1.5893-11.376c-0.14523-3.3461-0.38857-14.254-0.62478-14.712-0.23619-0.458-0.2394-0.39999-0.2394-0.39999s0.033-0.04764 0.20184-0.16469c0.16878-0.11705 0.63093-0.26527 0.63093-0.26527s0.0744-0.0018 0.30232 0.48834z"/>
            <path d="m69.996 36.297v-5.2154h0.93269v4.5999h3.4711v0.61546z"/>
            <path d="m77.308 36.297v-4.5999h-2.3221v-0.61546h5.5865v0.61546h-2.3317v4.5999z"/>
            <path d="m243.03 36.297v-5.2154h3.125c0.62819 6e-6 1.1058 0.04685 1.4327 0.14052 0.32691 0.09369 0.58812 0.25911 0.78365 0.49628 0.1955 0.23718 0.29325 0.49925 0.29326 0.78622-1e-5 0.36999-0.16187 0.68187-0.48557 0.93564-0.32372 0.25378-0.82373 0.41505-1.5 0.48383 0.24679 0.08775 0.43429 0.17432 0.5625 0.2597 0.27243 0.185 0.53044 0.41624 0.77403 0.69372l1.226 1.4195h-1.1731l-0.93269-1.0851c-0.27244-0.31306-0.4968-0.55261-0.67307-0.71863-0.17629-0.16602-0.33414-0.28223-0.47356-0.34864s-0.28125-0.11265-0.42548-0.13874c-0.10577-0.0166-0.27885-0.02491-0.51923-0.02491h-1.0817v2.316zm0.93269-2.9136h2.0048c0.42628 3e-6 0.75962-0.0326 1-0.09783 0.24038-0.06522 0.42307-0.16957 0.54808-0.31306 0.12499-0.14348 0.18749-0.29942 0.1875-0.46782-1e-5 -0.24665-0.121-0.44943-0.36298-0.60834-0.242-0.1589-0.62421-0.23835-1.1466-0.23836h-2.2308z"/>
            <path d="m251.92 36.297v-4.5999h-2.3221v-0.61546h5.5865v0.61546h-2.3317v4.5999z"/>
        </g>
        <g>
            <path d="m96.177 41.23c-1.9418-2.0278-16.973-0.09887-30.962 1.8093-12.702 1.733-20.944 5.508-22.885 7.333-1.9415 1.825-5.5845 9.6561-2.9997 9.8875 2.8935 0.25876 14.47-4.8802 27.206-6.1733 14.719-1.4945 27.467 0.84388 28.597-0.0053 2.1574-1.622 2.9855-10.824 1.0437-12.851z" fill="#c0c0c0"/>
            <path d="m280.96 50.372c-1.9418-1.8248-10.183-5.6-22.885-7.333-13.989-1.9081-29.02-3.837-30.962-1.8093-1.9418 2.0275-1.1134 11.229 1.044 12.851 1.1292 0.84914 13.877-1.4892 28.597 0.0053 12.736 1.293 24.313 6.432 27.206 6.1733 2.5848-0.23142-1.0582-8.0627-3-9.8875z" fill="#c0c0c0"/>
            <path class="template l1" d="m96.177 41.23c-1.9418-2.0278-16.973-0.09887-30.962 1.8093-12.702 1.733-20.944 5.508-22.885 7.333-1.9415 1.825-5.5845 9.6561-2.9997 9.8875 2.8935 0.25876 14.47-4.8802 27.206-6.1733 14.719-1.4945 27.467 0.84388 28.597-0.0053 2.1574-1.622 2.9855-10.824 1.0437-12.851z" fill="#ff0000" fill-opacity=".01"/>
            <path class="template r1" d="m280.96 50.372c-1.9418-1.8248-10.183-5.6-22.885-7.333-13.989-1.9081-29.02-3.837-30.962-1.8093-1.9418 2.0275-1.1134 11.229 1.044 12.851 1.1292 0.84914 13.877-1.4892 28.597 0.0053 12.736 1.293 24.313 6.432 27.206 6.1733 2.5848-0.23142-1.0582-8.0627-3-9.8875z" fill="#ff0000" fill-opacity=".01"/>
        </g>
        <g fill="#101010" stroke-width=".5">
            <path d="m74.814 48.817v-5.2154h0.93269v4.5999h3.4711v0.61546z"/>
            <path d="m237.11 48.817v-5.2154h3.125c0.6282 6e-6 1.1058 0.04685 1.4327 0.14052 0.32692 0.09369 0.58813 0.25911 0.78365 0.49628 0.19551 0.23718 0.29326 0.49925 0.29327 0.78622-1e-5 0.36999-0.16187 0.68187-0.48558 0.93564-0.32372 0.25377-0.82372 0.41505-1.5 0.48383 0.24679 0.08775 0.43429 0.17432 0.5625 0.2597 0.27243 0.185 0.53044 0.41624 0.77403 0.69372l1.226 1.4195h-1.1731l-0.93269-1.0851c-0.27244-0.31306-0.4968-0.5526-0.67307-0.71863-0.17629-0.16602-0.33414-0.28223-0.47356-0.34864-0.13943-0.06641-0.28125-0.11265-0.42549-0.13874-0.10577-0.0166-0.27885-0.02491-0.51922-0.02491h-1.0817v2.316zm0.93269-2.9136h2.0048c0.42627 3e-6 0.75961-0.0326 0.99999-0.09783 0.24039-0.06522 0.42308-0.16957 0.54809-0.31306 0.12499-0.14349 0.18748-0.29943 0.18749-0.46782-1e-5 -0.24665-0.12099-0.44944-0.36298-0.60834s-0.6242-0.23835-1.1466-0.23836h-2.2308z"/>
            <path d="m80.295 48.817v-5.2154h2.6442c0.53846 6e-6 0.97035 0.05278 1.2957 0.15831 0.32531 0.10554 0.58012 0.26801 0.76442 0.48739 0.18429 0.21939 0.27644 0.44885 0.27644 0.68839-6e-6 0.22294-0.08174 0.43284-0.24519 0.62969-0.16347 0.19686-0.41026 0.35576-0.74038 0.47671 0.42628 0.0925 0.754 0.25022 0.98317 0.47316 0.22916 0.22294 0.34374 0.4862 0.34375 0.78978-6e-6 0.24429-0.06971 0.47138-0.20913 0.68127-0.13943 0.2099-0.3117 0.37177-0.51683 0.48561s-0.46234 0.19982-0.77163 0.25792c-0.3093 0.05811-0.6883 0.08716-1.137 0.08716zm0.93269-3.0239h1.524c0.41346 4e-6 0.70993-0.02016 0.88942-0.06048 0.23717-0.05217 0.41586-0.13874 0.53606-0.2597 0.12019-0.12095 0.18028-0.27274 0.18029-0.45537-4e-6 -0.17313-0.0561-0.32551-0.16827-0.45715-0.11218-0.13162-0.27244-0.22175-0.48077-0.27037-0.20834-0.04861-0.56571-0.07293-1.0721-0.07293h-1.4087zm0 2.4085h1.7548c0.30128 1e-6 0.51282-0.0083 0.63461-0.02491 0.21474-0.02846 0.39422-0.07589 0.53846-0.1423 0.14423-0.06641 0.26282-0.16305 0.35577-0.28994 0.09295-0.12688 0.13942-0.27334 0.13942-0.43936-5e-6 -0.19448-0.06731-0.36346-0.20192-0.50695-0.13462-0.14349-0.32132-0.24428-0.5601-0.30239-0.23878-0.0581-0.58253-0.08716-1.0312-0.08716h-1.6298z"/>
            <path d="m244.17 48.817v-5.2154h2.6442c0.53846 6e-6 0.97035 0.05278 1.2957 0.15831 0.32532 0.10554 0.58013 0.26801 0.76443 0.48739 0.18428 0.21939 0.27643 0.44885 0.27644 0.68839-1e-5 0.22294-0.0817 0.43284-0.2452 0.62969-0.16346 0.19686-0.41026 0.35576-0.74038 0.47671 0.42628 0.0925 0.754 0.25022 0.98318 0.47316 0.22916 0.22294 0.34374 0.4862 0.34375 0.78978-1e-5 0.24429-0.0697 0.47138-0.20914 0.68127-0.13943 0.2099-0.31171 0.37177-0.51682 0.48561-0.20514 0.11384-0.46235 0.19982-0.77164 0.25792-0.30929 0.05811-0.6883 0.08716-1.137 0.08716zm0.93268-3.0239h1.524c0.41345 4e-6 0.70992-0.02016 0.88942-0.06048 0.23717-0.05217 0.41585-0.13874 0.53605-0.2597 0.12019-0.12095 0.18029-0.27274 0.18029-0.45537 0-0.17313-0.0561-0.32551-0.16827-0.45715-0.11218-0.13162-0.27244-0.22175-0.48077-0.27037-0.20833-0.04861-0.5657-0.07293-1.0721-0.07293h-1.4086zm0 2.4085h1.7548c0.30128 1e-6 0.51281-0.0083 0.6346-0.02491 0.21475-0.02846 0.39424-0.07589 0.53847-0.1423s0.26281-0.16305 0.35577-0.28994c0.0929-0.12688 0.13942-0.27334 0.13942-0.43936 0-0.19448-0.0673-0.36346-0.20192-0.50695-0.13463-0.14349-0.32132-0.24428-0.5601-0.30239-0.23878-0.0581-0.58253-0.08716-1.0312-0.08716h-1.6298z"/>
        </g>
    </g>
    <g stroke-width="0">
        <path d="m289.47 97.57c-1.562-4.6186-7.6393-6.679-13.574-4.6023-5.9342 2.0764-9.4786 7.5042-7.9165 12.123 1.562 4.6186 7.6393 6.6787 13.574 4.6023 5.9345-2.077 9.4791-7.5044 7.9165-12.123z" fill="#ff0000"/>
        <path d="m265.53 120.5c-0.96406-5.229-7.0434-8.5797-13.579-7.4839s-11.052 6.2227-10.088 11.452c0.96406 5.229 7.0434 8.5797 13.579 7.4839 6.5354-1.0956 11.052-6.2227 10.088-11.452z" fill="#00ff00"/>
        <path d="m266.83 81.205c1.064 5.3891-3.2446 10.779-9.6235 12.038-6.3788 1.2594-12.412-2.0884-13.476-7.4776-1.064-5.3891 3.2446-10.779 9.6235-12.038 6.3789-1.2594 12.412 2.0884 13.476 7.4776z" fill="#ffff00"/>
        <path d="m239.89 103.1c-0.42864-5.0343-5.7664-8.7554-11.922-8.3115s-10.798 4.885-10.37 9.9193c0.42891 5.0343 5.7667 8.7554 11.922 8.3115 6.1559-0.44416 10.798-4.885 10.37-9.9193z" fill="#0000ff"/>
        <path class="template ci" d="m289.47 97.57c-1.562-4.6186-7.6393-6.679-13.574-4.6023-5.9342 2.0764-9.4786 7.5042-7.9165 12.123 1.562 4.6186 7.6393 6.6787 13.574 4.6023 5.9345-2.077 9.4791-7.5044 7.9165-12.123z" fill="#0000ff" fill-opacity=".01"/>
    </g>
    <g fill="#ff0000" fill-opacity=".01" stroke-width="0">
        <path class="template cr" d="m265.53 120.5c-0.96406-5.229-7.0434-8.5797-13.579-7.4839s-11.052 6.2227-10.088 11.452c0.96406 5.229 7.0434 8.5797 13.579 7.4839 6.5354-1.0956 11.052-6.2227 10.088-11.452z"/>
        <path class="template tr" d="m266.83 81.205c1.064 5.3891-3.2446 10.779-9.6235 12.038-6.3788 1.2594-12.412-2.0884-13.476-7.4776-1.064-5.3891 3.2446-10.779 9.6235-12.038 6.3789-1.2594 12.412 2.0884 13.476 7.4776z"/>
        <path class="template sq" d="m239.89 103.1c-0.42864-5.0343-5.7664-8.7554-11.922-8.3115s-10.798 4.885-10.37 9.9193c0.42891 5.0343 5.7667 8.7554 11.922 8.3115 6.1559-0.44416 10.798-4.885 10.37-9.9193z"/>
    </g>
    <g fill-opacity="0" stroke="#101010">
        <path d="m274.15 106.56v-10.113l5.2642-0.17121c1.0435-0.03393 1.9158-3.94e-4 2.5466 0.20425 0.63079 0.20466 1.1591 0.4512 1.5165 0.87658 0.35735 0.4254 0.53602 0.87034 0.53603 1.3348-1e-5 0.4323-0.15849 0.8393-0.47543 1.221-0.31698 0.38171-0.82976 0.75832-1.4699 0.99285 0.82656 0.17936 1.4963 0.4167 1.9406 0.84898 0.44435 0.4323 0.70077 0.90853 0.70078 1.4972-1e-5 0.47368-0.16941 0.94826-0.43976 1.3553-0.27035 0.40699-0.6044 0.72087-1.0022 0.94162-0.39775 0.22074-0.93074 0.45593-1.5305 0.56861-0.59974 0.11267-1.3693 0.24388-2.239 0.27172zm1.8085-5.8635 3.0921-0.17121c0.80049-0.0443 1.4108-0.14181 1.7589-0.21999 0.45989-0.10117 0.84061-0.33751 1.0737-0.57206 0.23305-0.23453 0.34958-0.52886 0.34959-0.88298-1e-5 -0.33571-0.10876-0.63118-0.32628-0.88643-0.21753-0.25523-0.56251-0.3615-0.96647-0.45579-0.40398-0.09427-1.1329-0.09719-2.1131-0.03869l-2.8684 0.17121zm0 4.6701 3.5396-0.17121c0.58351-0.0282 1.0286-0.11882 1.2648-0.15102 0.41639-0.0552 0.79867-0.21564 1.0783-0.34441 0.27966-0.12876 0.50961-0.31617 0.68985-0.5622 0.18023-0.24604 0.30458-0.56427 0.30459-0.88618-1e-5 -0.37711-0.16476-0.67053-0.42577-0.94877-0.26104-0.27822-0.65729-0.40519-1.1203-0.51787-0.46301-0.11267-1.165-0.11139-2.0339-0.0663l-3.2972 0.17121z"/>
        <path d="m246.78 127.13 5.2485-10.113h1.9484l5.5934 10.113h-2.0602l-1.5941-3.0628h-5.7146l-1.5009 3.0628zm3.9433-4.1528h4.6332l-1.4263-2.8007c-0.43504-0.85078-0.75822-1.5498-0.96952-2.0971-0.17403 0.64844-0.41951 1.2923-0.73646 1.9315z"/>
        <path d="m254.45 88.67v-4.2838l-5.2671-5.829h2.2001l2.6942 3.049c0.49718 0.57026 0.96019 1.1405 1.389 1.7108 0.41017-0.52886 0.90736-1.1244 1.4916-1.7867l2.6475-2.9732h2.1068l-5.4536 5.829v4.2838z"/>
        <path d="m222.37 109.24 5.2858-5.2703-4.6612-4.8426h2.1534l2.4797 2.5938c0.51583 0.53806 0.8825 0.95197 1.1 1.2417 0.30453-0.3679 0.66499-0.7519 1.0814-1.152l2.7501-2.6834h1.967l-4.801 4.7667 5.1739 5.3462h-2.2374l-3.4399-3.6078c-0.19267-0.20694-0.39155-0.43229-0.59663-0.67604-0.30454 0.36792-0.52205 0.62086-0.65256 0.75882l-3.4306 3.525z"/>
    </g>
</svg>

實作效果可以到在到 https://hkgoldenmra.bitbucket.io/html5-gamepad-detector/index.html 測試
原始碼亦存放到 https://bitbucket.org/hkgoldenmra/hkgoldenmra.bitbucket.io/src/master/html5-gamepad-detector/
可以在 Terminal 輸入
下載

在使用 HTML5 Gamepad API 前,在下曾使用 Java 直接存取 /dev/input/js* 的資訊,並在 Terminal 顯示手掣狀態
但之後打算有較豐富的視覺效果,而使用 Java Swing 作圖像界面視示工具,但 Java Swing 在更新手掣的按鈕顯示時有延遲,效果並不流暢
另外, Windows 要存取手掣資訊需要使用 dll 的原生功能,導致 Java 都不能真正跨平台地存取手掣資訊
改用 HTML5 Gamepad API 除了基於跨平台性,還有因為瀏覽器能簡化多執行緒 (Multi-Threading) 的處理,減省很多多執行緒問題

對於 HTML5 Gamepad API ,桌面作業系統中 Firefox 的支援效果及流暢度比 Chrome 較好
但行動作業系統中 Chrome 則比 Firefox 好 (可能是因為 Android 及 Chrome 都是 Google 產品)

參巧資料
W3C 的 Gamepad API 的草擬文件, Gamepad@W3C https://www.w3.org/TR/gamepad/
Gamepad API 基本內容, Gamepad API - Web APIs | MDN https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API
Gamepad API 使用方法, Using the Gamepad API - Web APIs | MDN https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
顯示基本 Gamepad 按鈕及轉軸資料, HTML5 Gamepad Tester https://html5gamepad.com/

沒有留言 :

張貼留言