ホリパッド TURBO for Nintendo Switch™ / PC を買いました。PCにも使えるということで、PCにつないでみました。マウスの代わりには使えないんですね。Xboxのコントローラーとして認識されました。
ブラウザでもJavascriptを書けば使えるということなので3D地図のプラットフォームのCesiumで試しました。結果は、試したCesiumで使えました。ボタンにどの機能を割り当てるかはちゃんと考えないといけないというのが実感できました。
環境
- Windows 10
- Microsoft Edge
- ホリパッド TURBO for Nintendo Switch™ / PC
ゲームパッド API の使用によると Gamepad API はブラウザによって対応が若干ことなるようなので、他のブラウザでは動かないかもしれません。Edgeでは動きましたが、そのうち動かなくなるかもしれません。
コードはエラーチェックとかいろいろ手を抜いていることをご了承ください。
参考にしたページ
Cesium JS
Gamepad API
ホリパッド TURBO
ホリパッド TURBO for Nintendo Switch™ / PC 取扱説明書
Gamepad API と ホリパッド TURBO の対応
ホリパッド TURBO for Nintendo Switch™ / PC の Gamepad API のボタン(buttons)と軸(axes)の対応。(個人調べ)
ボタンは16個認識されました。でもキャプチャーボタンは、PC 使用不可と取扱説明書に書いてあったとおり認識されませんでした。
ボタン# | ホリパッドTURBOボタン |
---|---|
0 | A |
1 | B |
2 | X |
3 | Y |
4 | L |
5 | R |
6 | ZL |
7 | ZR |
8 | - |
9 | + |
10 | Lスティック |
11 | Rスティック |
12 | 十字ボタン 上 |
13 | 十字ボタン 下 |
14 | 十字ボタン 左 |
15 | 十字ボタン 右 |
16 | HOME |
LとRのスティックも認識されましたが、上下と左右で別々に認識されるんですね。
軸# | ホリパッドTURBOスティック |
---|---|
0 | Lスティック 左右 -1.0: 左、+1.0: 右 |
1 | Lスティック 上下 -1.0: 上、+1.0: 下 |
2 | Rスティック 左右 -1.0: 左、+1.0: 右 |
3 | Rスティック 上下 -1.0: 上、+1.0: 下 |
Gamepad API では、この「ボタン#」と「軸#」を使って識別しています。
Javascriptのコード
そんなに長くならなかったのでindex.htmlに全部入れたのを載せます。
地図は、国土地理院の地理院タイル 電子国土基本図(オルソ画像) を使いましたが特に深い意味はないです。素直に、Cesium Ion のを使っても見れると思います。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>cesium</title>
<link rel="icon" href="data:,">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.124/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.124/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script type="module">
// Cesium
// https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/
// Cesium Ion のアクセストークンを設定する
// Cesium.Ion.defaultAccessToken = 'your_access_token';
// ビューのホームとして富士山を設定する
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(138.63, 35.47, 138.83, 35.27);
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0;
const viewer = new Cesium.Viewer('cesiumContainer', {
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
// 国土地理院提供の地理院タイル(オルソ画像)を利用する設定
new Cesium.OpenStreetMapImageryProvider({
url: 'https://cyberjapandata.gsi.go.jp/xyz/ort/',
fileExtension: 'jpg',
credit: new Cesium.Credit('<a href="https://maps.gsi.go.jp/development/ichiran.html">地理院タイル 電子国土基本図(オルソ画像)</a>', true)
}),
),
geocoder: false, // 検索ボタン
homeButton: true, // ホームボタン
baseLayerPicker: false, // レイヤボタン
requestRenderMode: true, // https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#enabling-request-render-mode
terrain: Cesium.Terrain.fromWorldTerrain(), // 地形
});
function flyToMountFuji() {
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(138.73, 34.87, 20000.0),
orientation: {
heading: 0.0,
pitch: Cesium.Math.toRadians(-20.0),
roll: 0.0,
}
});
}
flyToMountFuji();
// Gamepad
var loopRequired = false;
function addgamepad(e) {
// console.log('addgamepad', e.gamepad);
if (!loopRequired) {
requestAnimationFrame(updateStatus);
loopRequired = true;
}
}
function axisValue(val) {
return Math.round(val * 1000);
}
function updateStatus() {
loopRequired = false;
for (const gamepad of navigator.getGamepads()) {
if (!gamepad) continue;
loopRequired = true;
// for (const [i, button] of gamepad.buttons.entries()) {
// if (button.pressed) {
// console.log('button pressed', i, button);
// }
// }
// for (const [i, axis] of gamepad.axes.entries()) {
// var val = axisValue(axis)
// if (val != 0) {
// console.log('axis x 1000', i, val);
// }
// }
if (gamepad.buttons[2].pressed) { // Xボタン
var amount = 10.0;
if (gamepad.buttons[7].pressed) { // ZRボタン
amount *= 20.0;
}
viewer.camera.zoomIn(amount);
}
if (gamepad.buttons[3].pressed) { // Yボタン
var amount = 10.0;
if (gamepad.buttons[7].pressed) { // ZRボタン
amount *= 20.0;
}
viewer.camera.zoomOut(amount);
}
val = axisValue(gamepad.axes[0]) // Lスティック左右
if (val != 0) {
var angle = 1e-11 * Math.abs(val)*val;
if (gamepad.buttons[6].pressed) { // LRボタン
angle *= 20.0;
}
viewer.camera.rotateRight(angle);
}
var val = axisValue(gamepad.axes[1]) // Lスティック上下
if (val != 0) {
var angle = 1e-11 * Math.abs(val)*val;
if (gamepad.buttons[6].pressed) { // LRボタン
angle *= 20.0;
}
viewer.camera.rotateUp(angle);
}
var val = axisValue(gamepad.axes[2]) // Rスティック左右
if (val != 0) {
var angle = 1e-6 * val;
viewer.camera.lookRight(angle);
}
var val = axisValue(gamepad.axes[3]) // Rスティック上下
if (val != 0) {
var angle = 1e-6 * val;
if (gamepad.buttons[7].pressed) { // ZRボタン
angle *= 20.0;
}
viewer.camera.lookUp(angle);
}
}
if (loopRequired) {
requestAnimationFrame(updateStatus);
}
}
window.addEventListener("gamepadconnected", addgamepad);
</script>
</div>
</body>
</html>
前半はCesium部分です。ブラウザでアクセスすると富士山が見えるように設定しました。
後半が Gamepad API部分です。ゲームパッド API の使用 にあったように定期的にゲームコントローラの gamepad オブジェクトを取得し、ボタンやステックの値を見ているだけになります。ゲームコントローラが接続された時のイベントgamepadconnected
をみて updateStatus
を定期的に呼び出すかどうかを判断しています。ゲームパッド API の使用 では ゲームコントローラが切断された時のイベントgamepaddisconnected
もあったのですが使わなかったので入っていません。
ボタンやステックが操作されたときに、Cesium のカメラの位置を変更しています。動かす値とかは動かしてなんとなく決めた値です。この編はカメラの位置やZoomとかで変化させたほうがよいと思ったのですがそのあたりがわからず、動かして決めました。Lステックでは viewer.camera.rotateXXX
を使っています。これは地球に沿って移動するというものです。
おわり
Lステックで左右に倒すと横にスライドするように動きます。飛行機のように旋回して動くような感じにはならなかった。 rotateXXX
がよくなかったのかも…