HTML5 を使ったシンプルな 2 D ゲームの作り方 (矢印キーとタッチによる制御の実装)
去年末に出演した schoo (スクー) さんの授業で使用したサンプルアプリをもとにした、HTML5 を使ったシンプルな 2 D ゲームの作り方を紹介しています。
どんなゲームを作るのかは 1 回目の記事の中に実際に動作するゲームが埋め込んであるのでぜひ遊んでみてください。なお、開発に必要な画像データは 2 回目の記事からダウンロードできまるので、実際にゲーム開発を体験したい方はそちらから入手してください。
- HTML5 を使ったシンプルな 2 D ゲームの作り方(序)
- HTML5 を使ったシンプルな 2 D ゲームの作り方(準備編)
- HTML5 を使ったシンプルな 2 D ゲームの作り方 (画像のロード)
- HTML5 を使ったシンプルな 2 D ゲームの作り方 (アニメーションの実装)
前回の記事では Canvas に表示した画像にアニメーションを実装しましたが、今回はこれをキーボードの矢印キー (カーソルキー) と、画面のタッチでコントロールできるようにしたいと思います。具体的には雪だるまの画像を左右に動かす機能です。
なお、タッチによるコントロールは、モニターがタッチ対応のものでないと使用できませんのでご注意ください。
デバイスからのイベントの取得
キーボードや、タッチ対応の画面などからの入力を取得するには、それを受け取るためのイベントハンドラーを実装する必要があります。
キーボードのキーについてのアクションは kdown、keyup 等のイベントで、タッチは touchstart、touchend 等のイベントで取得することができるので、これらを使用してイベントハンドラーを定義していきます。
キーイベントの取得と判断
キーボードのキーを押下した際のアクションは、keydown イベントで取得することができますが、押されたキーを判断し、必要なキーのアクションを必要な処理に割り当てる必要があります。押されたキーを示すキーコードは、以下のようにイベントハンドラに渡された引数の which プロパティで取得できるので、これを使用して押されたキーの種別を判断することができます。
document.addEventListener("keydown", function (evnt) {
var keyCode = evnt.which;
};
今回のゲームで雪だるまを動かすのに使用するキーは、カーソルキーの左と右です。キーコードは数字ですが、数字そのままだとコードを読んだ時にわかりづらいので、以下のようにキーの名前をつけた変数を定義し、値をセットしておきます。この変数は、名前のついているすべての関数の外側/即時実行関数の直下に記述してください。
(function () {
//矢印キーのコード
var LEFT_KEY_CODE = 37;
var RIGHT_KEY_CODE = 39;
//雪だるまの横位置に加算する変数
var key_value = 0;
余談ですが、Internet Explorer 11 では定数を定義するための const が使用できるようになっていますが、書き換えを防止するということ以外では JavaScript ではあまりメリットがない気がするのでそのまま var を使用しています。
キー押下時のイベントを取得する keydown イベントのハンドラーを記述します。イベントハンドラーの中では、左矢印ボタンが押されたら、雪だるま画像 の横位置に加算する値を –3 に (左に移動)、右矢印キーを押されたら 3 を (右に移動) に設定します。
//キーイベントの取得 (キーダウン)
document.addEventListener("keydown", function (evnt) {
if (evnt.which == LEFT_KEY_CODE) {
key_value = -3;
} else if (evnt.which == RIGHT_KEY_CODE) {
key_value = 3;
}
});
//雪だるまが進みっぱなしにならないように、
//キーが上がったら 0 に
document.addEventListener("keyup", function () {
key_value = 0;
});
雪だるま の横位置を加算しすぎて Canvas からはみ出さないように、横位置 (X) の最大値を設定します。移動できる X の最大値は画像が Canvas の右端についた状態となるので、以下のような計算式で求めることができます。
X = Canvas の幅 - 雪だるまの画像の幅
分かりやすくするためと、再利用を考えて関数を定義します。関数名は getRightLimitPosition とします。
//Player (雪だるまを動かせる右の限界位置)
function getRightLimitPosition(containerWidth, itemWidth) {
return containerWidth - itemWidth;
}
この getRightLimitPosition 関数を、loadAssets 関数中の、雪だるまの画像をロードしている箇所に追記し、img_snow_man の limit_rightPosition プロパティに返り値をセットするようにします。limit_rightPosition プロパティは、あらかじめ定義はされていませんか、JavaScript では値がセットされたと同時にプロパティが作られるので問題ありません。
//雪だるま画像のロード
img_snow_man = new Image();
img_snow_man.src = '/img/snow_man.png';
img_snow_man.onload = function () {
img_snow_man._x = getCenterPostion(canvas.clientWidth, img_snow_man.width);
img_snow_man._y = canvas.clientHeight - img_snow_man.height;
//右側に動かせる最大値を設定
img_snow_man.limit_rightPosition = getRightLimitPosition(canvas.clientWidth,
img_snow_man.width);
ctx.drawImage(img_snow_man, img_snow_man._x, img_snow_man._y);
};
アニメーション フレーム内の処理を行っている renderFrame 関数に、雪だるま画像の横位置を書き換えるコードを追記します。
function renderFrame() {
//img_snow の y 値(縦位置) が canvas からはみ出たら先頭に戻す
if (img_snow._y > canvas.clientHeight) { img_snow._y = 0 };
//canvas をクリア
ctx.clearRect(0, 0, canvas.width, canvas.height);
//img_snow の y 値を増分
img_snow._y += 2;
//img_snow_man の x 値が動作範囲内かどうか
if ((img_snow_man._x < img_snow_man.limit_rightPosition && key_value > 0)
|| (img_snow_man._x >= 3 && key_value < 0)) {
//img_snow_man の x 値を増分
img_snow_man._x += key_value;
}
//画像を描画
ctx.drawImage(img_snow, img_snow._x, img_snow._y);
ctx.drawImage(img_snow_man, img_snow_man._x, img_snow_man._y);
//ループを開始
requestId = window.requestAnimationFrame(renderFrame);
}
default.html を選択し、キーボードの [F5] キーを押下してページを実行します。画像が表示されたら Canvas 部分をクリックし、左右のカーソルキーを押下してみてください。キーに合わせて雪だるまの画像が左右に動作します。
タッチイベントの取得と判断
タッチデバイスからのイベントは touchstart、touchend イベントで取得することができます。雪だるまの動きをどう左右に割り振るかは、Canvas の左側をタッチしたら左へ、右側をタッチしたら右へ動くようにします。
//Canvas へのタッチイベント設定
canvas.addEventListener("touchstart", function (evnt) {
if ((canvas.clientWidth / 2) > evnt.touches[0].clientX) {
key_value = -3;
} else {
key_value = 3;
}
});
//雪だるまが進みっぱなしにならないように、
//タッチが完了したら 0 に
canvas.addEventListener("touchend", function (evnt) {
key_value = 0;
});
なお、このコードが正常に動作するには、変数 canvas に HTML エレメントの Canvas のインスタンスが格納されている必要があるので、loadAssets 関数の実行が完了している必要があります。よって、DOMContentLoaded イベント ハンドラー内の loadAssets 関数の呼び出し箇所に上記のコードを記述すればいいのですが、コードが煩雑になるので、新たに setHandlers という関数を作成してその中に前述のキーイベントのハンドラーもまとめ、DOMContentLoaded イベントハンドラーからは、その setHandlers 関数を呼ぶようにします。具体的には、以下のコードとなります。
//DOM のロードが完了したら実行
document.addEventListener("DOMContentLoaded", function () {
loadAssets();
setHandlers();
});
//雪だるまを動かすためのイベントハンドラーをまとめた関数
function setHandlers() {
//キーイベントの取得 (キーダウン)
document.addEventListener("keydown", function (evnt) {
if (evnt.which == LEFT_KEY_CODE) {
key_value = -3;
} else if (evnt.which == RIGHT_KEY_CODE) {
key_value = 3;
}
});
//雪だるまが進みっぱなしにならないように、 キーが上がったら 0 に
document.addEventListener("keyup", function () {
key_value = 0;
});
//Canvas へのタッチイベント設定
canvas.addEventListener("touchstart", function (evnt) {
if ((canvas.clientWidth / 2) > evnt.touches[0].clientX) {
key_value = -3;
} else {
key_value = 3;
}
});
//雪だるまが進みっぱなしにならないように、 タッチが完了したら 0 に
canvas.addEventListener("touchend", function (evnt) {
key_value = 0;
});
}
ここまでの main.js 全体のコードは以下のとおりです。
(function () {
//矢印キーのコード
var LEFT_KEY_CODE = 37;
var RIGHT_KEY_CODE = 39;
var key_value = 0;
//全体で使用する変数
var canvas = null;
var ctx = null;
var img_snow = null;
var img_snow_man = null;
//DOM のロードが完了したら実行
document.addEventListener("DOMContentLoaded", function () {
loadAssets();
setHandlers();
});
//雪だるまを動かすためのイベントハンドラーをまとめた関数
function setHandlers() {
//キーイベントの取得 (キーダウン)
document.addEventListener("keydown", function (evnt) {
if (evnt.which == LEFT_KEY_CODE) {
key_value = -3;
} else if (evnt.which == RIGHT_KEY_CODE) {
key_value = 3;
}
});
//雪だるまが進みっぱなしにならないように、 キーが上がったら 0 に
document.addEventListener("keyup", function () {
key_value = 0;
});
//Canvas へのタッチイベント設定
canvas.addEventListener("touchstart", function (evnt) {
if ((canvas.clientWidth / 2) > evnt.touches[0].clientX) {
key_value = -3;
} else {
key_value = 3;
}
});
//雪だるまが進みっぱなしにならないように、 タッチが完了したら 0 に
canvas.addEventListener("touchend", function (evnt) {
key_value = 0;
});
}
function loadAssets() {
//HTML ファイル上の canvas エレメントのインスタンスを取得
canvas = document.getElementById('bg');
//アニメーションの開始
canvas.addEventListener("click", renderFrame);
//2D コンテキストを取得
ctx = canvas.getContext('2d');
//image オブジェクトのインスタンスを生成
img_snow = new Image();
//image オブジェクトに画像をロード
img_snow.src = '/img/snow.png';
/*画像読み込み完了のイベントハンドラーに Canvas に
画像を表示するメソッドを記述 */
img_snow.onload = function () {
img_snow._x = getCenterPostion(canvas.clientWidth, img_snow.width);
img_snow._y = 0;
//canvas 上で image を描画
ctx.drawImage(img_snow, img_snow._x, img_snow._y);
};
//雪だるま画像のロード
img_snow_man = new Image();
img_snow_man.src = '/img/snow_man.png';
img_snow_man.onload = function () {
img_snow_man._x = getCenterPostion(canvas.clientWidth, img_snow_man.width);
img_snow_man._y = canvas.clientHeight - img_snow_man.height;
img_snow_man.limit_rightPosition = getRightLimitPosition(canvas.clientWidth,
img_snow_man.width);
ctx.drawImage(img_snow_man, img_snow_man._x, img_snow_man._y);
};
};
function renderFrame() {
//img_snow の y 値(縦位置) が canvas からはみ出たら先頭に戻す
if (img_snow._y > canvas.clientHeight) { img_snow._y = 0 };
//canvas をクリア
ctx.clearRect(0, 0, canvas.width, canvas.height);
//img_snow の y 値を増分
img_snow._y += 2;
//img_snow_man の x 値が動作範囲内かどうか
if ((img_snow_man._x < img_snow_man.limit_rightPosition && key_value > 0)
|| (img_snow_man._x >= 3 && key_value < 0)) {
//img_snow_man の x 値を増分
img_snow_man._x += key_value;
}
//画像を描画
ctx.drawImage(img_snow, img_snow._x, img_snow._y);
ctx.drawImage(img_snow_man, img_snow_man._x, img_snow_man._y);
//ループを開始
requestId = window.requestAnimationFrame(renderFrame);
}
//中央に配置する画像の X 座標を求める関数
function getCenterPostion(containerWidth, itemWidth) {
return (containerWidth / 2) - (itemWidth / 2);
};
//Player (雪だるまを動かせる右の限界位置)
function getRightLimitPosition(containerWidth, itemWidth) {
return containerWidth - itemWidth;
}
})();
default.html を選択し、キーボードの [F5] キーを押下してページを実行し、画像が表示されたら Canvas 部分をクリックしてください。
以下は、実際に動作するサンプルです。ゲーム画面をクリックしてアニメーションが動き出したら、カーソルキー、タッチで 雪だるま が左右に動作するのを確認してください。
まとめ
今回は、ゲーム中の雪だるまの画像をキーボードの矢印キー (カーソルキー) と、画面のタッチでコントロールできるようにしました。
次はゲームにおいて重要な要素である衝突判定を実装します。