こくぶん研究室

ゲームパッドを使う

はじめに

スマホはタッチだけ?

スマホ用ゲームパッド
スマホ用ゲームパッド

スマホのアプリやゲームを作っていると、タッチパネルだけでなく、ゲームパッドを使いたくなるときがあります。特に VR アプリはスマホをゴーグルの中に入れてしまうので、タッチ操作ができなくなってしまいます。そこで近ごろはスマホ用のゲームパッドがあります。

スマホ専用でなくても、Bluetooth 接続のゲームパッドであればスマホでも使えるものも多いようです。

さぁ 遊んでみましょう

Web アプリでもゲームパッドが使えます。スマホでゲームパッドのデータを取得してみましょう。さらに、このサイトの「スマホ VR を作ろう」で作った VR コンテンツをゲームパッドで操作できるように改造します。

なお、iOS ではゲームパッドの取り扱いが独特で、一般的なゲームパッドを接続することが容易ではありません。そのため今回の内容は Android スマホ限定です。

▲TOP

今回の内容

目標

まずはゲームパッドの値を取得して表示します。

今回の目標1
今回の目標1

さらに「スマホ VR を作ろう」で作った VR コンテンツにゲームパッドのプログラムを組み込んで、VR 空間を動き回れるようにします。

今回の目標2
今回の目標2

使うもの

ステップ

  1. ゲームパッドの値を取得
  2. VR でゲームパッドを使う

▲TOP

ゲームパッドの値を取得

スマホとゲームパッドのペアリング

お手持ちの Bluetooth ゲームパッドの取説のとおりに、スマホとゲームパッドをペアリングしてください。

例えば ELECOM の JC-VRR01 の場合、「リモコンモード」と「ゲームパッドモード」があるので、「ゲームパッド」モードで電源を入れて、ペアリングボタンを押します。Android スマホの「設定」=>「Bluetooth」と進むと「ELECOM VR Controller」と出てくるので、それをタップすれば接続 OK です。

スマホとゲームパッドのペアリング
スマホとゲームパッドのペアリング

プログラミング

Visual Studio Code(または好みのエディタ)を立ち上げて、以下のコードを入力しましょう。こちらで作ったテンプレートをもとに書き始めると効率的です。ファイルは、C:\xampp\htdocs フォルダの中に「pad1」というフォルダを作って、その中に「index.html」として保存してください。それぞれのコードが何をしているのか考えながら入力していきましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ゲームパッドの値を取得</title>
</head>

<body>
<div id="log">ここにデータを表示</div>

<script>
// ゲームパッドの状態を繰り返し取得する loop 関数
function loop() {
    requestAnimationFrame(loop);                        // loop 関数を繰り返す
    var pad = navigator.getGamepads();                  // ゲームパッドの状態を取得
    // データの表示(1個めのゲームパッド(pad[0])のみ表示)
    var log = document.getElementById("log");           // データ表示用の div 要素を取得
    log.innerHTML = "a0: " + pad[0].axes[0] + "<br>"    // 左アナログ(横持ちで右が+、縦持ちで下が+)
                  + "a1: " + pad[0].axes[1] + "<br>"    // 左アナログ(横持ちで下が+、縦持ちで左が+)
                  + "a2: " + pad[0].axes[2] + "<br>"    // 右アナログ(横持ちで右が+、縦持ちで下が+)
                  + "a3: " + pad[0].axes[3] + "<br>"    // 右アナログ(横持ちで下が+、縦持ちで左が+)
                  // ボタンは .value で 0/1、.pressed で true/false が得られる
                  + "b0: " + pad[0].buttons[0].value + "<br>"    // A
                  + "b1: " + pad[0].buttons[1].value + "<br>"    // B
                  + "b2: " + pad[0].buttons[2].value + "<br>"    // X
                  + "b3: " + pad[0].buttons[3].value + "<br>"    // Y
                  + "b4: " + pad[0].buttons[4].value + "<br>"    // L1
                  + "b5: " + pad[0].buttons[5].value + "<br>"    // R1
                  + "b6: " + pad[0].buttons[6].value + "<br>"    // L2
                  + "b7: " + pad[0].buttons[7].value + "<br>"    // R2
                  + "b8: " + pad[0].buttons[8].value + "<br>"    // Select
                  + "b9: " + pad[0].buttons[9].value + "<br>"    // Start
                  + "b10: " + pad[0].buttons[10].value + "<br>"  // L3
                  + "b11: " + pad[0].buttons[11].value + "<br>"  // R3
                  + "b12: " + pad[0].buttons[12].value + "<br>"  // 十字キー 上
                  + "b13: " + pad[0].buttons[13].value + "<br>"  // 十字キー 下
                  + "b14: " + pad[0].buttons[14].value + "<br>"  // 十字キー 左
                  + "b15: " + pad[0].buttons[15].value + "<br>"  // 十字キー 右
                  + "b16: " + pad[0].buttons[16].value + "<br>"; // Home
}

// loop 関数を呼ぶ処理
window.addEventListener("gamepadconnected", loop);  // ゲームパッドが接続されたら loop 開始
if(!window.ongamepadconnected) { loop(); }          // Chrome はイベントが起きないので直接 loop 開始
</script>
</body>
</html>

動作確認

XAMPP Control Panel で Apache を Start させてください(不明な場合はこちらを参考にしてください)。

プログラムを書いているパソコンと、動作確認するスマホが、同じ LAN につながっていることを確認します(不明な場合はこちらを参考にしてください)。

Windows の「コマンド プロンプト」で ipconfig して、パソコンの IP アドレスを調べてください(不明な場合はこちらを参考にしてください)。

Android スマホのブラウザ(Chrome か Firefox)を開き、アドレス欄に以下のように入力します。以下の「10.11.52.81」の部分は上で調べた IP アドレスです。

以下のように表示されます。

アクセスすると
アクセスすると

ゲームパッドのボタンを押したりスティックをぐりぐり動かして、値が表示されることを確認しましょう。

解説

長く見えるコードですが、基本的に 16行目の navigator.getGamepads(); だけでゲームパッドの値が取得できます。

    var pad = navigator.getGamepads();                  // ゲームパッドの状態を取得

変数 pad には複数のゲームパッドの情報が入ります(複数のゲームパッドをつないでいれば)。pad[0], pad[1],... のように ID を指定して各ゲームパッドの情報を取り出すことができます。今回は 1個だけゲームパッドを使うことにして、pad[0] だけ用います。

pad[0].axes[軸の番号] でアナログスティックの値(-1.0 ~ +1.0)、pad[0].buttons[ボタンの番号].value でボタンの値が取得できます。詳しくはコード中のコメント文を参考にしてください。

ELECOM JC-VRR01 の場合
ELECOM JC-VRR01 の場合

なお、pad = navigator.getGamepads(); とすると、変数 pad にはその瞬間のゲームパッドのデータが入ります。アプリやゲームの中では常に変化しているゲームパッドの状態を知りたいのが普通です。そこで、loop という関数を作り(名前はなんでも構いません)、その関数の最初で requestAnimationFrame(loop); とすることで、この loop 関数を繰り返し実行させています。

最後の 2行(44・45行目)で、この loop 関数を呼んでいます。

// loop 関数を呼ぶ処理
window.addEventListener("gamepadconnected", loop);  // ゲームパッドが接続されたら loop 開始
if(!window.ongamepadconnected) { loop(); }          // Chrome はイベントが起きないので直接 loop 開始

ゲームパッドが見つかると "gamepadconnected" イベントが発生します。44行目で、そのイベントが発生したら loop 関数を呼んでいます。しかし Chrome ではこの "gamepadconnected" イベントが発生しません(window オブジェクト内に ongamepadconnected を持っていない)。そこで、45行目で ongamepadconnected が無かったら Chrome だと判断して、イベント発生時ではなく、じかに loop 関数を呼んでいます。少しややこしい話ですが、ブラウザが違っても動くようにするためのトリックです。

参考

完成品をこちらに置いてありますからスマホで開いてみてください。

▲TOP

2. VR でゲームパッドを使う

このサイトの「スマホ VR を作ろう」で作った VR コンテンツにゲームパッドのプログラムを組み込んで、VR 空間を歩き回れるようにします。以下の図の例では、ヒューちゃんの後ろに回り込んでいます。

今から作るもの
今から作るもの

プログラミング

Visual Studio Code(または好みのエディタ)を立ち上げて、さきほどの pad1 の index.html と、こちらで作った vr3 の index.html をもとに以下のコードを入力しましょう。<title> タグ ~ </script> タグ の部分のみを掲載します(このままコピペするだけでは動きません)。ファイルは、C:\xampp\htdocs フォルダの中に「pad2」というフォルダを作って、その中に「index.html」として保存してください。それぞれのコードが何をしているのか考えながら入力していきましょう。

<title>VR でゲームパッドを使う</title>
<!-- A-Frame を使うための外部スクリプトの読み込み -->
<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
</head>

<body>
<!-- A-Frame の VR は必ず a-scene タグで囲み、その中に様々なオブジェクト(entities)を置く -->
<a-scene>
    <!-- メディアを入れておく a-assets タグ -->
    <a-assets>
        <a-asset-item id="modelObj" src="huchan.obj"></a-asset-item>    <!-- objファイル -->
        <a-asset-item id="modelMtl" src="huchan.mtl"></a-asset-item>    <!-- mtlファイル -->
        <img id="groundTexture" src="floor.jpg">    <!-- 地面のテクスチャ -->
        <img id="skyTexture" src="sechelt.jpg">     <!-- 背景のテクスチャ -->
    </a-assets>

    <!-- obj 形式の CG モデルを置く a-obj-model タグ、src 属性で obj、mtl 属性で mtl を指定 -->
    <a-obj-model src="#modelObj" mtl="#modelMtl"
        position="0 2 -5"
        scale="0.6 0.6 0.6">
        <!-- アニメーションするための a-animation タグ(a-entity の子要素として指定) -->
        <!-- attribute="動かす属性"  to="動かす先"  dur="持続時間"  begin="アニメ開始イベント" -->
        <a-animation attribute="position"
            to="0 2.5 -5"
            dur="2000"
            direction="alternate"
            repeat="indefinite">
        </a-animation>
        <a-animation attribute="scale"
            to="0.8 0.8 0.8"
            dur="300"
            begin="mouseenter">
        </a-animation>
        <a-animation attribute="scale"
            to="0.6 0.6 0.6"
            dur="300"
            begin="mouseleave">
        </a-animation>
        <a-animation attribute="rotation"
            to="360 360 0"
            dur="2000"
            begin="click">
        </a-animation>
    </a-obj-model>

    <!-- 地面と背景 src="#テクスチャ" -->
    <a-plane src="#groundTexture"
        rotation="-90 0 0"
        width="15"
        height="15">
    </a-plane>
    <a-sky src="#skyTexture"
        color="rgb(127, 127, 127)">
    </a-sky>

    <!-- カメラを操作する a-camera タグ -->
    <a-camera  id="cam">        <!-- ★a-camera に id を設定 -->
        <a-cursor></a-cursor>   <!-- インタラクションするための a-cursor(視線カーソル)タグ -->
    </a-camera>
</a-scene>

<script>
// ★ゲームパッドの状態を繰り返し取得する loop 関数
function loop() {
    requestAnimationFrame(loop);                    // loop 関数を繰り返す
    var pad = navigator.getGamepads();              // ゲームパッドの状態を取得
    var cam = document.getElementById("cam");       // a-camera 要素(カメラ)の取得
    var pos = cam.getAttribute("position");         // カメラの position 属性を取得
    var rot = cam.getAttribute("rotation");         // カメラの rotation 属性を取得
    var angle;                                      // カメラの角度
    if(pad[0].axes[0] <= 0) {                       // 前進時と後退時で加算する角度の正負を逆転
        angle = rot.y + 90 + pad[0].axes[1] * 45;   // 前進時はスティック値で角度を加算
    } else {
        angle = rot.y + 90 - pad[0].axes[1] * 45;   // 後退時はスティック値で角度を減算
    }
    var speed = 0.1 * pad[0].axes[0];               // スティックの前後の値で速度を決定
    pos.x -= Math.cos(angle * Math.PI / 180) * speed;   // 横方向の位置 X の計算
    pos.z += Math.sin(angle * Math.PI / 180) * speed;   // 前後方向の位置 Z の計算
    cam.setAttribute("position", pos);              // カメラの position 属性に反映
}

// loop 関数を呼ぶ処理
window.addEventListener("gamepadconnected", loop);  // ゲームパッドが接続されたら loop 開始
if(!window.ongamepadconnected) { loop(); }          // Chrome はイベントが起きないので直接 loop 開始
</script>

動作確認

このアプリを試すには、VR 空間に置く obj 形式の CG モデルを用意します。こちら(モデル本体の obj ファイル)こちら(マテリアル設定の mtl ファイル)をダウンロードしてください(パソコンのブラウザでリンクを右クリックして「名前をつけてリンク先を保存」)。ダウンロードした 2個のファイルは「pad2」フォルダの中に入れてください(index.html と同じ階層に)。

また、プリミティブ(床と空)に貼り付けるテクスチャの画像ファイルを用意する必要があります。以下の 2個の画像をダウンロードしてください(パソコンのブラウザで画像を右クリックして「名前をつけて画像を保存」などの方法で)。ダウンロードした 2個の画像を「pad2」フォルダの中に入れてください(index.html と同じ階層に)。

さらに、ゲームパッドと Android スマホを Bluetooth でペアリングしておきましょう。

準備ができたら、Android スマホのブラウザ(Chrome か Firefox)を開き、アドレス欄に以下のように入力します。以下の「10.11.52.81」の部分はさきほど調べた IP アドレスです。

スマホで以下のように表示されます。ここまではこちらで作った vr3 と同じです。

アクセスすると
アクセスすると

さぁ、ゲームパッドのアナログスティックを様々な方向に倒してみましょう。これまでは同じ場所に立ったまま上下左右を見ることしかできませんでしたが、VR 空間の中を動き回れるようになりました。以下の例は「ヒューちゃん」の後ろに回り込んだところです。

ゲームパッドで VR 空間を歩き回る
ゲームパッドで VR 空間を歩き回る

さらに、右下のメガネ(VRゴーグル)のアイコンをタップして VR モードにして、VR ゴーグルに入れて楽しんでみましょう。

なお、今回の例では ELECOM の JC-VRR01 を縦にして(下図のように)手に持った時に思い通りに動くようにしてあります。他のゲームパッドの場合は、いろいろな向きに持ちかえてみて、思い通りに動く向きを探してください。最終的には、自分が使っているゲームパッドにあわせてプログラムを書き換えましょう。

ゲームパッドの持ち方(方向)
ゲームパッドの持ち方(方向)

解説

今回のプログラムの仕組みをひとことで言うと、VR 空間の「カメラ」の「位置」をゲームパッドで動かすようにしました。

まず、57行目で a-camera タグに id を設定しました。あとからこのカメラを JavaScript で操作するためです。

    <!-- カメラを操作する a-camera タグ -->
    <a-camera  id="cam">        <!-- ★a-camera に id を設定 -->
        <a-cursor></a-cursor>   <!-- インタラクションするための a-cursor(視線カーソル)タグ -->
    </a-camera>

改造のメインは 64行目以降の JavaScript で書いた loop 関数の中身です。

// ★ゲームパッドの状態を繰り返し取得する loop 関数
function loop() {
    requestAnimationFrame(loop);                    // loop 関数を繰り返す
    var pad = navigator.getGamepads();              // ゲームパッドの状態を取得
    var cam = document.getElementById("cam");       // a-camera 要素(カメラ)の取得
    var pos = cam.getAttribute("position");         // カメラの position 属性を取得
    var rot = cam.getAttribute("rotation");         // カメラの rotation 属性を取得
    var angle;                                      // カメラの角度
    if(pad[0].axes[0] <= 0) {                       // 前進時と後退時で加算する角度の正負を逆転
        angle = rot.y + 90 + pad[0].axes[1] * 45;   // 前進時はスティック値で角度を加算
    } else {
        angle = rot.y + 90 - pad[0].axes[1] * 45;   // 後退時はスティック値で角度を減算
    }
    var speed = 0.1 * pad[0].axes[0];               // スティックの前後の値で速度を決定
    pos.x -= Math.cos(angle * Math.PI / 180) * speed;   // 横方向の位置 X の計算
    pos.z += Math.sin(angle * Math.PI / 180) * speed;   // 前後方向の位置 Z の計算
    cam.setAttribute("position", pos);              // カメラの position 属性に反映
}

67行目で a-camera 要素(カメラ)を JavaScript で扱えるように取得しています。カメラの位置(position 属性)は、getAttribute() というメソッドで取得することができます。同様に rotation 属性(カメラの回転)の値も取得しています。

70~78 行目が最も重要な(そしてややこしい)部分です。ここで、スティックを倒した量に応じて VR 空間を動き回らせています。詳しい説明は割愛しますが、大ざっぱに書くと以下のような流れです。

高校(数学Ⅱ?)で学んだ「三角関数」「単位円」「弧度法」などのキーワードを思い出しながら、時にはネットで調べながら、紙に図を描きながら、考えてみましょう。

最後に、計算した位置の値を setAttribute() というメソッドで a-camera 要素(カメラ)の position 属性に戻してやります。

参考

完成品をこちらに置いてありますからスマホで開いてみてください。

補足

背景のテクスチャ画像はこちら、床のテクスチャ画像はこちらのもの(A-Frame のサンプル用)をダウンロードして、サイズなどを調整しました。

▲TOP

まとめ

ゲームパッドのプログラミングと VR コンテンツへの応用、いかがでしたか?

実は、「ボタンやレバーを使わないこと」こそがスマホの特徴です。昔からあるボタンやレバーではなく、様々なセンサやタッチパネルを使うほうが、より「スマホらしい」アプリです。しかし、タッチパネルが使えない状況になる VR や、ゲームでより多くの操作がしたい場合など、ゲームパッドが使えるとなにかと便利です。

今回の後半では、ゲームパッドで VR 空間を動き回るという応用で、ちょっとややこしい数学を使いました。座標の値をいろいろ操作する場合、どうしても三角関数やベクトルなどの考え方が必要になります。このあたりはハッキリいって「習うより慣れよ」ですから、あまり難しく考えず、使っているうちに「定番の方法」として身につけていきましょう。

さぁ、タッチパネルやたくさんのセンサがついたスマホに、さらにゲームパッドが加わりました。あらゆる方法で操作できるアプリやゲームを作って楽しみましょう!

補足

上で作ったコードはすべてこちら(GitHub)に置いてありますから、参考にしてください。

▲TOP