ゲームコントローラでp5.jsのスケッチを動かしてみる


2018年07月13日
With
ゲームコントローラでp5.jsのスケッチを動かしてみる はコメントを受け付けていません。

これまで、p5.jsでインタラクティブ性のあるスケッチを作るときには、

・マウス…mouseClicked()とか、mouseX/mouseYとか
・キーボード…keyPressed()とか、keyIsDown()とか
・DOMのUI部品…スライドバーとかドロップダウンリストとか

などを使っていたんですが、先日ふと「そういやブラウザでゲームコントローラの情報をとれるAPIってあったよな」と思いあたり、ちょっと使い方を調べてみることにしました。Google先生が教えてくれたページの中から、

Gamepad APIの利用 – ウェブデベロッパーガイド | MDN

ゲームパッドでブラウザゲームを操作できるGamepad APIが楽しい【俺聞け11】

このあたりを参考にしながらコードを書いてみたところ、思いのほか簡単にイケてしまったので、今回はそれを。

使おうと思えば使える「Gamepad API」

HTML5においてゲーム開発に使えそうな機能(canvasとか)が標準仕様に盛り込まれた流れの一環で、ブラウザからOSが管理しているゲームコントローラ(ゲームパッド)の状態を取得できる「Gamepad API」というものが登場しました。

現状、このGamepad APIは実験段階の機能であり、今後仕様そのものや、ブラウザ側での実装状況についても変わっていく可能性があるとのこと。ただ、Chrome、Firefox、Edgeといった主なモダンブラウザは対応している(Safariはダメ)ので、使おうと思えば使えそうです。

…えーと、とりあえずp5.jsと組み合わせて、本体につないであるコントローラの状態を見たいだけなら、「draw()」の中で「Navigator.GetGamepads()」というメソッドを都度呼び出して何らかの変数に収めてやり、その中を見ればいい…ということなのかな?

ひとまず、どんな形で値がとれるのかを確認するために、こんなスケッチを作ってみました。

●Gamepad API checker(クリックすると別タブが開きます。動作確認はWindows 10+Chrome上でのみ実施)

このスケッチを立ち上げて、PCに接続しているゲームコントローラから何か入力してやると、画面上に「id」と「各ボタン、レバーの状態」が表示されます。コントローラをつないだだけではダメで、スケッチを開いた後でレバーなりボタンなりを1度操作しないといけないようです。あと、2台以上接続されている場合、このスケッチでは1台目の値しか見ていませんのでご注意を。

ちなみに↑の画面は僕が使っている「Xbox 360 Wireless Controller for Windows」をつないだところ。試しに、PS4の「DUALSHOCK 4」につなぎ替えてみたら、こちらもちゃんと値がとれました。こんな感じ。

さっきの「Navigator.GetGamepads()」を代入した変数は、4つの要素を持つ配列になっていて、添字の0~3が、1~4台目までのコントローラに対応します。未接続の状態はnullですが、接続されるとそれぞれの中に「id」や「buttons」「axes」といったプロパティが用意されると。で、buttons、axesもさらに配列になっていて、それぞれに対応したスイッチの状態をbooleanやvalueとして見ることができる…ということのようです。DUALSHOCK 4だと、Xbox 360 Controllerよりもbuttonsの数が1つ多く出るのですが、これはタッチパッド部の押下に対応していました。

どうやら、Windowsの場合は、XInputに対応したコントローラであれば、特に問題なくGamepad APIから見えるみたいですね。うちにあるのだと、Xbox 360コントローラ(有線&無線)、DUALSHOCK 4、HORIの「リアルアーケードPro.V HAYABUSA」あたりがOKでした。一方、ちょっと古くてDirectInputにしか対応してないようなやつだとダメみたいです。

お手持ちのパッドをつないで、いろいろ試してみていただければ。あと、Macのゲームコントローラ事情はよく分からないので、今回のスケッチでは特に考慮していません。たぶんイケるのではないかと思いますが、試してみて動かなかったらゴメンナサイ。

var pads, pad0;
function setup() {
	createCanvas(600, 600);
	textSize(14);
	stroke(80);
	strokeWeight(.3);
}

function draw() {
	background(240);
	pads = navigator.getGamepads();
	pad0 = pads[0];
	text('ID: ', 10, 20);
	if (pad0) {
		text(pad0.id, 35, 20);
		for (var i = 0; i < pad0.buttons.length; i++) {
			text('button' + i + ': ' + pad0.buttons[i].value, 10, 40 + 20 * i);
		}
		for (var i = 0; i < pad0.axes.length; i++) {
			text('axes' + i + ': ' + pad0.axes[i], 10, 40 + pad0.buttons.length * 20 + 20 * i);
		}
	}
}

ちなみに、よりグラフィカルにGamepad APIの取得状況を確認できるサンプルとしては、こんなものもありましたのでご参考までに。

Gamepad-Sample

で、何となく使い方がわかったので、以前作ったp5.jsのスケッチと組み合わせてみました。

●Gamepad API sampler for p5.js(クリックすると別タブが開きます。ゲームコントローラ必須。動作確認はWindows 10+Chrome+Xbox 360 wireless controller for Windowsで実施)

とりあえず、コントローラが使える状態で、スティックだのボタンだのをガチャガチャいじってみてください。操作によってスケッチ上の要素が変化します。

・左スティック…★の表示位置
・Lボタン…★の回転スピード(押している間は高速)
・Rボタン…★の輪の回転スピード(押している間は高速)
・Rトリガー…★の大きさ(アナログ対応)
・Aボタン…軌跡の残し方(押している間は長く残る)

コード上は↑こんな対応になっています。何も考えずにガチャガチャイジってもイイですし、意図的に操作を組み合わせて狙ったパターンを出してみるのも面白いです。

スケッチの操作にゲームパッドを使うと、

・多数の要素を同時に切り替える操作を感覚的にできる
・スティックやトリガーのアナログ値がとれる

といったあたりで、他のデバイス(キーボードとかマウスとか)では難しいような、独特の操作系が作れそうです。

コードはこんな感じ。ちなみに、これは以前「blendMode」の使い方を探索していたときに作ったスケッチの改造版になります。ゲームコントローラがない場合は、マウスで★の表示位置だけを変えられるようにしてあります。

let starObj;
let bgAlpha = 10;
let bgRot = 100;

function setup() {
	createCanvas(600, 600);
	colorMode(HSB, 360, 100, 100, 100);
	noStroke();
	starObj = new starPoints();
}

function draw() {
	blendMode(BLEND);
	background(0, bgAlpha);
	translate(width / 2, height / 2);
	rotate(frameCount / bgRot);
	for (let d = 0; d < 12; d++) {
		rotate(radians(30));
		fill(d * 30, 100, 100, 40);
		blendMode(SCREEN);
		drawStar(d, starObj);
	}
	let pads = navigator.getGamepads();
	let pad0 = pads[0];
	if (pad0) {
		updateStatus(pad0, starObj);
	} else {
		starObj.x = mouseX - width / 2;
		starObj.y = mouseY - height / 2;
	}
}

function drawStar(d, obj) {
	push();
	translate(obj.x, obj.y);
	let rotVec;
	if (d % 2 !== 0) {
		rotVec = 1;
	} else {
		rotVec = -1;
	}
	rotate(frameCount / obj.speed * rotVec);
	scale(obj.scale);
	beginShape();
	for (let i = 0; i < obj.ptArr.length; i++) {
		vertex(obj.ptArr[i].px, obj.ptArr[i].py);
	}
	endShape();
	pop();
}

function updateStatus(pad, obj) {
	obj.x = map(pad.axes[0], -0.9, 0.9, -width / 2, width / 2);
	obj.y = map(pad.axes[1], -0.9, 0.9, -height / 2, height / 2);
	obj.speed = map(pad.buttons[4].value, 0, 1, 80, 8);
	obj.scale = map(pad.buttons[7].value, 0, 1, 1, 20);
	bgAlpha = map(pad.buttons[0].value, 0, 1, 10, 0.1);
	bgRot = map(pad.buttons[5].value, 0, 1, 100, 20);
	return;
}

class starPoints {
	constructor() {
		this.x = 0;
		this.y = 0;
		this.rad = 15;
		this.scale = 1;
		this.speed = 1000;
		this.ptArr = [];
		let ptflg;
		for (let i = 0; i < 360; i += 36) {
			if (i % 72 !== 0) {
				ptflg = .38;
			} else {
				ptflg = 1;
			}
			this.ptArr.push({
				px: cos(radians(i)) * this.rad * ptflg,
				py: sin(radians(i)) * this.rad * ptflg
			})
		}
	}
}

Gamepad APIのように、ブラウザに用意されている機能をサクっと取り込んで使えてしまうあたりは、JavaScriptベースである「p5.js」の面目躍如といった感じがして、なんか楽しいですね。