Gamepadをブラウザで使う
どうも最近だとjavascriptから簡単にゲームパッドの入力を取れるらしい。DC Motorの制御をゲームパッドからやるなど随分と良さそうなので、 まずはここを攻略することにする。 (本記事はChromeでしか稼動確認をしていないのでご注意ください)
Gamepadの値をjavascriptでとる際のポイント
- html5の仕様に組み込まれている。ただしブラウザ毎実装が違うので注意
- 今回はChrome以外完全無視で実装した
- ゲームを作っている人たちには当たり前なのだと思うのだが、以下の理解に時間がかかった
-
Gamepadの入力はEventDrivenではない。ループをブラウザの画面描画のタイミングで回す必要がある。[requestAnimationFrame]などを使って
- Gamepadのオブジェクト[navigator.getGamepads()[0]]はゲームパッドの状態へのポインタではない。つまり、ループの度にオブジェクトそのものの再取得が必要。きちんとオブジェクトを取れているのにボタンを押しても値が変わらずすごくこまった
- アナログスティックの実装が若干わかりにくい
- 押し込んだときの挙動は、buttonsプロパティのvalueやpressedから取得する
- 一方、どれくらい倒しているかは axes でとる。。。がvalueをつけてはダメ
-
実装したコード
このようなコードにした。大したものではないが、日本語で掲載されている意味があるかもしれない。誰かに役にたつかもしれないので載せておく。
- コントローラ毎にセットされる値は違うみたいだが、私が持っているxbox 360コントローラは以下で値が取れそう
- ボタンの値は gp.buttons[0から16].value でとる
- アナログスティックの値は gp.axes[bi] でとる
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css?family=Press+Start+2P');
body{
color: rgba(255,255,255,.75);
font-family: 'Press Start 2P', cursive;
background-color: rgb(25,25,25);
font-size: 1.0em;
padding: 2.0em;
line-height: 1.8rem;
}
.buttons{
}
.button{
background-color: #4CAF50;
border:rgba(255,255,255,.75) solid;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
.bb {
background-color: rgb(39, 28, 192);
}
.by {
background-color: rgb(241, 255, 44);
border-top:rgba(255,255,255,.75) solid;
}
.br {
background-color: rgb(247, 68, 68);
}
.bg {
background-color: #4CAF50;
}
.parent {
display: flex;
justify-content:left;
}
.child1{
width: 1000px;
}
.child2 {
border: rgb(248, 45, 45) solid;
border: #4CAF50 solid;
padding:10px;
font-size: 90%;
}
</style>
</head>
<body>
<script>
function setMessage(selector,text) {
$(selector).html(text);
}
function addMessage(selector,text) {
$(selector).html($(selector).html()+"<br/>"+text);
}
function mainLoop(){
var bi;
var gp = navigator.getGamepads()[0];
if (gp) {
setMessage("#status","---status-------")
for( bi=0; bi < gp.buttons.length; bi++ ){
addMessage("#status", "b:"+bi + "> " + gp.buttons[bi].value + ":" + gp.buttons[bi].pressed)
}
for( bi=0; bi < gp.axes.length; bi++ ){
addMessage("#status", "a:"+bi + "> " + gp.axes[bi])
}
}
lp = raf(mainLoop);
}
var gp = false;
var lp;
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var rafs = window.mozCancelRequestAnimationFrame || window.webkitCancelRequestAnimationFrame || window.cancelRequestAnimationFrame;
$(document).ready(function() {
window.addEventListener("gamepadconnected", function(e) {
gp = navigator.getGamepads()[e.gamepad.index];
addMessage("#console","[NAME:"+ gp.id +"], [# OF BUTTONS:"+gp.buttons.length+"], [# OF AXES:"+gp.axes.length+"]");
mainLoop();
});
window.addEventListener("gamepaddisconnected", function(e) {
gp = false;
addMessage("#console","connection terminated");
rafs(lp);
});
});
</script>
<div class="parent">
<div class="child1">
<div class="buttons">
<span id="btnX" class="button bb">X</span><span id="btnX" class="button by">Y</span><span id="btnX" class="button br">A</span><span id="btnX" class="button bg">B</span>
</div>
<span id="status">status<br/>----------</span>
</div>
<div class="child2">
<span id="console">console<br/>----------</span>
</div>
</body>
</html>
感想
コントローラの状態取得一つとっても、業務要件の違いからくる思想の違いがわかって面白かった。私が理解に時間がかかったポイントはまさに、お堅い企業向けのシステムばかり作ってきて、リアルタイム性よりはひとつひとつ確実に入力してゆく世界に染まった私の癖そのものであった。
例えば、ボタンの入力がイベントドリブンではないのはボタンの同時押しを容易に検知できたり、入力を受け取れるタイミングになって初めて受け取ると言うふうに、リアルタイム環境における合理性があるのだろう。ゲームパッドオブジェクトがポインタ的な参照ではなく、取得時の状態のコピーであるのも、前回の入力と今回の入力、さらに未来の入力を比較しやすくするためのものだろう。
触れて実装してみれば一目瞭然の世界観の違いがこの仕様に反映されているのである。
仕様策定という政治や大規模システムなどの堅い世界と、ゲームという柔らかい世界のバランスをとりながら仕様を策定した方々の能力に驚愕せざるを得ない。