HTML5 ゲーム開発 第 1 部

Sacha Morgese
henngeblog
Published in
14 min readOct 23, 2018

8月のMTSを用意していた時に勉強になったこと

校正: Toshiya Doi

この投稿について

ゲームが好きな人は少なくとも一生に一回は「自分のゲームを作って見たい」と思うかもしれない。私は何回も思ったことがある。実を言うと、中学校の頃、親友と一緒に紙でゲームを作るために、数え切れないほどの時間を使っていた。変なキャラクターと可笑しい武器を作ったり、罠を仕掛けたりするのは楽しかった。 高校でプログラミングを勉強することにする理由の一つになった。
しかし、2000年代初頭のインターネットは、今とは大分違うものでした。若者だった私はどうゲーム開発を勉強すれば良いのか分からなかったし、率先力もなかったので諦めた。

数ヶ月前、HTML5 Games: Novice to Ninjaと言う本を貰って、Kindle Fireに入れていたが、2−3ヶ月くらいその本の存在を忘れていた。7月末MTSで発表することになったら思い出してきた。
社内にフロントエンドエンジニアが2人しかいないので、JavaScriptが分からなくても楽しめるトピックについて話したいと思っていた。全ての選択肢を検討したら、JSとフロントエンドの経験がある私がHTML5でゲーム開発を行うのは手間あまりかからないかなと思って、やってみた。

もちろん間違えていた。ゲーム開発はやっぱり難しい。数時間でできると思ったことが数週間かかった。素晴らしいものを作ろうと思っていたが、結局簡単なデモしか発表できなかった。必要な時間と知識を軽く見すぎた。あと、何か勉強したら使う分だけじゃなくて、だいたい全て理解したいタイプだ。普通の人から見たら時間の無駄かもしれないけど、すぐ出発して何回も地図を見るために止まるより、長く準備して旅行中で止まらない方が好き。
色々難しかったが、価値がある気がする。まだ分からないことが多いけど、「わかった!」と思える瞬間がたくさんあった。

基本概念

ゲーム開発は難しいが、基本概念は割と難しくない。ゲームを作るのはアニメーションと似ている。フレームを次々に描いたら、動いているように見える。もちろん、アニメーションは何回再生しても変わらないが、ゲームはユーザーが操作できるので、何が描かれるのは決まってなくて毎回違う。

ゲーム開発では、全てのフレームを描く代わりに、コンピューターに何を描いてもらいたいか指示して、ユーサー操作や衝突を検討した後で描いてもらう。だいたい1秒に60回コンピューターがループでユーザー入力を受け取って、全てを少し動かして、衝突判定してフレームを描く。このループの概念は昔のASCIIゲームから現代のゲーム機のAAA作品まで全てに当てはまる。

現代的なゲーム開発ツール(例:Unity)はめんどくさいタスクをエンジンに任せて、ユーザーをゲームデザインに集中させてくれる。しかし、裏では上で説明したようなことが起こっているので、いざゲームの細部を作り込もうと思うと、やはりこのループの基本概念を理解していなければならない。

今まで行ったことはどんなゲームにも当てはまるが、HTML5ゲーム開発は?

HTML5ゲーム開発

簡単に言うと、HTML5ゲーム開発とはブラウザで実行できるフロントエンドの技術を利用するゲームの開発だ。HTML、CSS、JSを使うことができたら、もうブラウザで実行できる簡単なゲームを作ることができる。しかし、それはずっと前からできる。すごく面白いゲームを作ることができても、HTML5ゲーム開発と言えば、Canvas APIの話になる。

CanvasはFlashの代わりとしてAppleが開発した使いやすくて力強いAPIだ。CanvasはJavaScriptを通してピクセル一つ一つのグラフィックスを自由に描画できる。2次元グラフィックスでも3次元グラフィックスでも使えるが、このブログで私が経験ある2次元のゲーム開発のみに集中する。

では、HTML5のメリットは?

主にはJavaScript。一番流行ってる開発言語の一つなので、コミュニティが大きく、StackOverflowなどでこれだけ多くのサポートを受けられる言語は他にない。ブラウザで実行できるゲームは、アップデートをリリースしたらユーザーがダウンロードせずにすぐに遊べるようになるし、自動的に更新するブラウザはいつも最新のAPIを利用したりできる。また、ブラウザさえあれば、他のツールをインストールしなくても遊べることは素晴らしくない?

やってみよう!

では、ゲーム開発の最初の一足として、Canvasの使い方の例を見よう。
Canvasはシンプルに描画するためのAPIだが、先ほど言った通りに、次々にフレームを書いたら、動いているように見える。

まず、四角を描いてみよう。HTML5を作成しよう。Bodyを見たら、boardというクラスを持ってるDIVの中にcanvasタグがある。

---------------------------- index.html ----------------------------<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Game</title>
<style>
body {
margin: 30px auto;
text-align: center;
color: #999;
}

#board canvas {
border: 1px solid #333;
background-color: #000;
-webkit-box-shadow: 0px 0px 24px 0px rgba(0,0,0,1);
-moz-box-shadow: 0px 0px 24px 0px rgba(0,0,0,1);
box-shadow: 0px 0px 24px 0px rgba(0,0,0,1);
border-radius: 10px;
}
</style>
</head>
<body>
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
<script src="script.js"></script>
</body>
</html>

次はスクリプトファイルを作成しよう。

----------------------------- script.js ----------------------------const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
console.log(ctx)

ブラウザで実行したら、上に作ったcanvasが見える。四角を描画しよう。

----------------------------- script.js ----------------------------[...]const { width: w, height: h } = canvas;let x = w / 2;
let y = h / 2;
// 色彩を設定
ctx.fillStyle = 'yellow'
// 四角作成
ctx.fillRect(x, y, 50, 50)

出た!黄色の四角!なぜ?
fillStyleで色彩を設定して、四角を描いた。fillStyleでCSSプロパティ、rgbやhslも使えます。最初の二つめの引数は座標(0, 0は左上の角)、最後の二つめは幅と高さ。座標を変えたら、四角の位置も変わります。最後の二つの引数を変えたらサイズが変わります。

中央寄せして見よう!

----------------------------- script.js ----------------------------[...]const { width: w, height: h } = canvas;

let rectW = 50; //四角幅
let rectH = 50; //四角高さ
let x = w / 2 - rectW / 2;
let y = h / 2 - rectH / 2;
// 色彩を設定
ctx.fillStyle = 'yellow'
// 四角作成
ctx.fillRect(x, y, rectW, rectH)

まずはcanvasから幅と高さを受け取って、wとh定数に割り当てる。少し計算したら黄色の四角は中央寄せになる。

もちろん四角だけじゃなく、たくさんの形できるが、四角以外に特別なコマンドはない。他の形はpathを使って描画できるが、この投稿では話さない。なぜかと言うと、pathを使うのは四角よりも複雑なので、説明する時間はあまりない。また、画像とSVGを挿入することもできる。気になったら、MDNのCanvas API ページ を参考にしてください。(tutorialもある)。

我ループ、故に我あり

Canvasを使って、描画できるようになったから、ループの番だ。ゲームじゃなければ各フレームのアウトプットを勝手に決めて描画できるが、ゲームを作りたいと思っているのであまり意味がない。ここで必要なのは、1秒に60回呼ばれる関数を書いて、毎回ユーザーインプットを受け取って、四角の位置を処理して、描画することである。

どうやってループを作るか?もし、Whileなどを使ったら、ブラウザが固まって何もできないだろう。JSだからSetTimeoutとSetIntervalが使えるが、ゲーム開発の場合には一貫性がない(参考:here)。60fpsを目指す場合、16.7ms毎にフレームを描く必要があるので、一貫性のあるツールをつかわないといけない。運の良いことに、requestAnimationFrameという関数がある。引数として関数を受け取って、次のフレームを呼ぶ。ループじゃなくても、main関数で呼び出したら、ループのように動く。

----------------------------- script.js ----------------------------function loop() {
// 次のフレイムでも実行したいからメイン関数でも呼び出す
requestAnimationFrame
(loop)
// 見えなくなったら、リセットする
if(x >= w + rectW) {
x = 0 - rectW
}
// 各フレイムはxに1を足して、四角が右に1px進む
else {
x += 1
}
// 色彩を設定
ctx.fillStyle = 'yellow'
// 四角作成
ctx.fillRect(x, y, rectW, rectH)
}
// 次のフレイムloop関数を呼び出す
requestAnimationFrame(loop)

四角が動いてる!ベーシックなアニメーションを作れた!すごくない?
あ、ちょっとまって、動いてるだけじゃなくて、何か跡をつけてる。もちろん!新しいフレイムを描く前、スクリーンを消さないといけない!

----------------------------- script.js ----------------------------[...]// 色彩(黒)を設定
ctx.fillStyle = 'black'
// 背景を黒に!
ctx.fillRect(0, 0, w, h)
// 色彩(黄色)を設定
ctx.fillStyle = 'yellow'

いいね!x += で数字を変えたらスピードが上がる!

ちょっと時間かかったけど何か動いてる。まだ操作できないけど… キーボード操作を作ろう!

----------------------------- script.js ----------------------------class KeyControls {
constructor() {
// 押したキーを保管するオブジェクト
this.keys = {};
// キーを押したら…
document.addEventListener(
'keydown',
e => {
if ([37, 38, 39, 40].indexOf(e.which) >= 0) {
// 矢印押したらスクロールしないように
e.preventDefault();
}
// 押したキーをtrueにする
this.keys[e.which] = true;
},
false,
);

document.addEventListener(
'keyup',
e => {
// 話したキーをfalseにする
this.keys[e.which] = false;
},
false,
);
}

// スペース(キー32)を押したらtrueを返す
get action() {
return this.keys[32];
}

get x() {
// ←、またはAを押されたら −1 返す
if (this.keys[37] || this.keys[65]) return -1;
// →またはDを押されたら 1 返す
if (this.keys[39] || this.keys[68]) return 1;
return 0;
}

get y() {
// ↑、またはWを押されたら −1 返す
if (this.keys[38] || this.keys[87]) return -1;
// ↓、またはSを押されたら −1 返す
if (this.keys[40] || this.keys[83]) return 1;
return 0;
}
}

難しく見えるかもしれないが、ただのキーハンドリングするクラスだ。キーを押したら、this.keys.keyCodeがtrueになって、離したらfalseになる。また、xとyのgettersを呼び出したら1か−1を返す。

script.jsで実行しよう

----------------------------- script.js ----------------------------class KeyControls {
[...]
}
const controls = new KeyControls

const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');

const { width: w, height: h } = canvas;

let rectW = 50;
let rectH = 50;
let x = w / 2 - rectW / 2;
let y = h / 2 - rectH / 2;
let color = 0;

function loop() {
// 次のフレイムでも実行したいからメイン関数でも呼び出す
requestAnimationFrame
(loop)
x += controls.x;
y += controls.y;

// アクションキー(スペース)を押すと色が変わる
if (controls.action) {
color += 10;
if (color > 360) {
color -= 360;
}
}

// 黒を設定して、背景を”消す”
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, w, h)

// HSLで色彩を設定する
ctx.fillStyle = `hsl(${color}, 50%, 50%)`;
ctx.fillRect(x, y, 50, 50);
}

requestAnimationFrame(loop)

やっとできた!四角は中央寄せになってるが、xとyがcontrolsに結び付けられてる。色は0になってるが、スペースを押したら変わる!(参考:CSS property hsl)

自由に四角を動かすことができるようになった!おめでとう!もちろんゲームと言えないがどんなゲームでもここから始まる!おめでとう!

最後に

時間がかかって、作ったものがあまりよくないけど、良いゲームを作ることがこの投稿の目的ではない。第 1 部なので、基本概念を理解してもらえばいいなと思って、一通り全て紹介できたと思う。

ループとか各フレームを描くなどと聞いて、基本概念を思い出すことができるなら、基本が理解できていると思う。もちろん、まだ扱ってないことの方が多いが、どんなに大きなゲームになってもこの概念は現れる。

この次のステップとして、webpackのようなモジュールバンドラや、オブジェクト指向開発の説明をしたいけど、第一部の投稿としてはこのあたりで止まって良いと思う。次は画像を挿入したり、シューター向けエンジンの作り方を説明する。

読んでくれてありがとう!続きを楽しみにして!

--

--