--
--

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
04
2018

GamepadAPIを使ってゲームを市販のコントローラーで操作できるようにしてみる:基礎編

Category技術

お久しぶりです! なるとりっくのシステム担当です!


今回はタイトル通りゲームをコントローラーで操作できるようにしてみたいと思います。
GamepadAPIを使うということで、Webベースのノベルゲームが対象になっています。

今回は基礎編という事で、基本的なGamepadAPIの使い方の紹介と導入をしていきます。
環境としてはOS:MacOS、ブラウザ:Chrome、同時に接続するコントローラーは一台まで、という前提でやっていきますので、環境によっては微妙に違うところも出てくるかもしれません。
Gamepadはちょうど手元にあったこちらの
「Nintendo Switch Proコントローラー スプラトゥーン2エディション」を使って行きます。

Gamepad APIの使い方

こちらのリファレンスによると、Gamepad APIには大まかに分けて三つの機能があります。
  • Gamepad接続イベントの取得
  • Gamepad切断イベントの取得
  • Gamepadの状態の取得
の三つです。
一つずつ使って行ってみましょう。


Gamepad接続イベントの取得

 
    window.addEventListener("gamepadconnected", function(e) {
        console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
        e.gamepad.index, e.gamepad.id,
        e.gamepad.buttons.length, e.gamepad.axes.length);
    });
サンプルの通り↑こちらのjsを記述し、MacとコントローラーをBluetoothでつなぐと・・・

 
    Gamepad connected at index 0: Pro Controller (Vendor: 057e Product: 2009). 16 buttons, 10 axes.
コンソールにこのような↑ログが出力され、無事に接続イベントが取得できました。簡単ですね。

Gamepad切断イベントの取得

    window.addEventListener("gamepaddisconnected", function(e) {
        console.log("Gamepad disconnected from index %d: %s",
        e.gamepad.index, e.gamepad.id);
    });
切断もサンプル通りのjs↑を記述してMacとコントローラーのBluetooth接続を解除すると・・・

    Gamepad disconnected from index 0: Pro Controller (Vendor: 057e Product: 2009)
コンソールにこのような↑ログが出力され、無事に切断されたことがわかります。


Gamepadの状態の取得

続いてGamepadの状態の取得をしていきます。これもリファレンスのサンプルを参考にやっていきます。
例えばAボタン、Bボタン、上下左右キーの状態を取得するなら、

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function stickFloor (s) {
  return Math.floor(s * 10) / 10;
}

(function gameLoop() {
    var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    if (gamepads && gamepads.length > 0 && gamepads[0]) {
        var gamepad = gamepads[0];

        if (buttonPressed(gamepad.buttons[0])) {
            console.log("Bボタン押下中")
        }

        if (buttonPressed(gamepad.buttons[1])) {
            console.log("Aボタン押下中")
        }

        if (stickFloor(gamepad.axes[9]) == -1) {
            console.log("上キー押下中");
        }

        if (stickFloor(gamepad.axes[9]) == -0.5) {
            console.log("右キー押下中");
        }

        if (stickFloor(gamepad.axes[9]) == 0.1) {
            console.log("下キー押下中");
        }

        if (stickFloor(gamepad.axes[9]) == 0.7) {
            console.log("左キー押下中");
        }
    }

    requestAnimationFrame(gameLoop);
})();

こんな感じになります。
Aボタンの状態はgamepad.buttons[1]で、
Bボタンの状態はgamepad.buttons[0]で、
十字キーの状態はgamepad.axes[9]で取得できます。
十字キーの状態は「1.2857143878936768」のような数値で取得出来るので、小数点第一位以下を斬り捨ててシンプルな数値にした後にそれぞれ対応する値と比較し判定します。
例えば十字キーの左を押下すると「0.7142857313156128」という数値が取得出来るので、小数点第一位以下を斬り捨てると0.7になります。
これで各ボタンを押している最中は「**ボタン押下中」というログが表示され続けます。

gamepadAPIではコントローラーのボタンを押下したらイベントが発生するのではなく、requestAnimationFrameを使用しフレーム毎にコントローラーの状態を取得しに行ってボタンが押されているかどうか確認する方式になっています。
なのでこのままではボタンが連打されっぱなしのような状態になってしまうので、一回の押下で一回のみ反応するように制御を加えます。

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function stickFloor (s) {
  return Math.floor(s * 10) / 10;
}

var is_pressed_b = false;
var is_pressed_a = false;
var is_pressed_cross_key_top = false;
var is_pressed_cross_key_right = false;
var is_pressed_cross_key_bottom = false;
var is_pressed_cross_key_left = false;
(function gameLoop() {
    var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    if (gamepads && gamepads.length > 0 && gamepads[0]) {
        var gamepad = gamepads[0];

        if (buttonPressed(gamepad.button[0])) {
            if (is_pressed_b != true) {
                is_pressed_b = true;
                console.log("Bボタンを押下しました!")
            }
        }
        else {
            is_pressed_b = false;
        }

        if (buttonPressed(gamepad.buttons[1])) {
            if (is_pressed_a != true) {
                is_pressed_a = true;
                console.log("Aボタンを押下しました!")
            }
        }
        else {
            is_pressed_a = false;
        }

        if (stickFloor(gamepad.axes[9]) == -1) {
            if (is_pressed_cross_key_top != true) {
                is_pressed_cross_key_top = true;
                console.log("上キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_top = false;
        }

        if (stickFloor(gamepad.axes[9]) == -0.5) {
            if (is_pressed_cross_key_right != true) {
                is_pressed_cross_key_right = true;
                console.log("右キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_right = false;
        }

        if (stickFloor(gamepad.axes[9]) == 0.1) {
            if (is_pressed_cross_key_bottom != true) {
                is_pressed_cross_key_bottom = true;
                console.log("下キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_bottom = false;
        }

        if (stickFloor(gamepad.axes[9]) == 0.7) {
            if (is_pressed_cross_key_left != true) {
                is_pressed_cross_key_left = true;
                console.log("左キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_left = false;
        }
    }

    requestAnimationFrame(gameLoop);
})();

これで一回の押下につき一回だけ処理をさせることができました。
ただこのままですとボタン押下時の処理を一箇所に記述していくことになってしまい使いづらいので分離させます。
どうやるのかと言いますと、カスタムイベントを作成・発火し、それを受信する事でクリック時のイベントと同じようなコードで処理を記述できるようになります。
ついでにgamepadのボタンの番号の定数化もしてしまいしょう。
そうすると以下の様になります。

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function stickFloor (s) {
  return Math.floor(s * 10) / 10;
}

const BUTTON_INDEX_B = 0;
const BUTTON_INDEX_A = 1;
const AXES_CROSS_KEY = 9;
var is_pressed_b = false;
var is_pressed_a = false;
var is_pressed_cross_key_top = false;
var is_pressed_cross_key_right = false;
var is_pressed_cross_key_bottom = false;
var is_pressed_cross_key_left = false;
(function gameLoop() {
    var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    if (gamepads && gamepads.length > 0 && gamepads[0]) {
        var gamepad = gamepads[0];

        if (buttonPressed(gamepad.button[BUTTON_INDEX_B])) {
            if (is_pressed_b != true) {
                is_pressed_b = true;
                dispatchEvent(new Event('gamepad_b_press'));
            }
        }
        else {
            is_pressed_b = false;
        }

        if (buttonPressed(gamepad.buttons[BUTTON_INDEX_A])) {
            if (is_pressed_a != true) {
                is_pressed_a = true;
                dispatchEvent(new Event('gamepad_a_press'));
            }
        }
        else {
            is_pressed_a = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == -1) {
            if (is_pressed_cross_key_top != true) {
                is_pressed_cross_key_top = true;
                dispatchEvent(new Event('gamepad_cross_key_t_press'));
            }
        }
        else {
            is_pressed_cross_key_top = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == -0.5) {
            if (is_pressed_cross_key_right != true) {
                is_pressed_cross_key_right = true;
                dispatchEvent(new Event('gamepad_cross_key_r_press'));
            }
        }
        else {
            is_pressed_cross_key_right = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == 0.1) {
            if (is_pressed_cross_key_bottom != true) {
                is_pressed_cross_key_bottom = true;
                dispatchEvent(new Event('gamepad_cross_key_b_press'));
            }
        }
        else {
            is_pressed_cross_key_bottom = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == 0.7) {
            if (is_pressed_cross_key_left != true) {
                is_pressed_cross_key_left = true;
                dispatchEvent(new Event('gamepad_cross_key_l_press'));
            }
        }
        else {
            is_pressed_cross_key_left = false;
        }
    }

    requestAnimationFrame(gameLoop);
})();
addEventListener("gamepad_b_press", function() {
    console.log("Bボタンを押下しました!")
})
addEventListener("gamepad_a_press", function() {
    console.log("Aボタンを押下しました!")
})
addEventListener("gamepad_cross_key_t_press", function() {
    console.log("上キーを押下しました!");
})
addEventListener("gamepad_cross_key_r_press", function() {
    console.log("右ボタンを押下しました!")
})
addEventListener("gamepad_cross_key_b_press", function() {
    console.log("下ボタンを押下しました!")
})
addEventListener("gamepad_cross_key_l_press", function() {
    console.log("左ボタンを押下しました!")
})

ボタンを押下したときに直接行うのは「ボタンを押下した」というカスタムイベントを作成し発火するだけで、そのカスタムイベントを受信するかどうかは受け手の自由です。
これでGamepadに関する処理とボタン押下時の処理を分離することができました。
不要になったイベントはremoveEventListenerしておくのを忘れずに。

こんな感じにしたら使いやすいのではないかと思います。
今回はここまで!

スポンサーサイト

0 Comments

Leave a comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。