HTML5 ゲーム ガイド

ダニエル X. Moore
ダニエル XMoore

はじめに

Canvas と HTML5 を使用してゲームを作成する場合、このチュートリアルに従うことで、すぐに使えるようになります。

このチュートリアルは、JavaScript について中級レベル以上の知識があることを前提としています。

最初にゲームをプレイするか、直接記事に移動してゲームのソースコードを表示してください。

キャンバスを作成する

何かを描画するには、キャンバスを作成する必要があります。これは jQuery を使用するガイドです。

var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;

var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
                      "' height='" + CANVAS_HEIGHT + "'></canvas>");
var canvas = canvasElement.get(0).getContext("2d");
canvasElement.appendTo('body');

ゲームループ

スムーズで継続的なゲームプレイをシミュレートするために、ゲームを更新して、人間の頭や目に見えるよりも速く画面を再描画する必要があります。

var FPS = 30;
setInterval(function() {
  update();
  draw();
}, 1000/FPS);

今のところは、更新メソッドと描画メソッドを空白のままにしておきます。重要なのは、setInterval() が関数を定期的に呼び出すことです。

function update() { ... }
function draw() { ... }

Hello World

ゲームループが開始したので、描画メソッドを更新して、実際に画面にテキストを描画します。

function draw() {
  canvas.fillStyle = "#000"; // Set color to black
  canvas.fillText("Sup Bro!", 50, 50);
}

静止したテキストの場合は便利ですが、ゲームループがすでに設定されているので、簡単に移動できます。

var textX = 50;
var textY = 50;

function update() {
  textX += 1;
  textY += 1;
}

function draw() {
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

試してみましょう。手順に沿うと動いているはずですが、画面に描画された以前の時間はそのまま残ります。その理由を考えてみてください。これは、画面をクリアしていないためです。 そのため、描画メソッドに画面をクリアするコードを追加しましょう。

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

画面上をテキストが動き回るようになったので、本格的なゲームまであと半分です。制御を強化し、ゲームプレイを改善し、グラフィックを改良するだけです。チュートリアルはこれだけではありません

プレーヤーの作成

プレーヤー データを保持し、描画などを処理するオブジェクトを作成します。ここでは、すべての情報を保持するシンプルなオブジェクト リテラルを使用して、プレーヤー オブジェクトを作成します。

var player = {
  color: "#00A",
  x: 220,
  y: 270,
  width: 32,
  height: 32,
  draw: function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  }
};

ここでは、プレーヤーを表すためにシンプルな色付きの長方形を使用します。ゲームを描画するときは、キャンバスをクリアしてプレーヤーの描画を行います。

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  player.draw();
}

キーボード操作

jQuery ホットキーの使用

jQuery Hotkeys プラグインを使用すると、複数のブラウザでのキー処理がはるかに簡単になります。ブラウザ間の keyCodecharCode に関する解読不能な問題で泣き叫ぶのではなく、次のようにイベントをバインドできます。

$(document).bind("keydown", "left", function() { ... });

どのキーがどのコードを持っているかを詳細に気にする必要はありません。必要なのは、「プレーヤーが上ボタンを押したときに、何かを行う」などの指示です。jQuery Hotkeys を使用すると、それが簡単になります。

プレーヤーの移動

JavaScript がキーボード イベントを処理する方法は、完全にイベント ドリブンです。つまり、キーが停止しているかどうかを確認する組み込みクエリがないため、独自のクエリを使用する必要があります。

「イベント ドリブンなキー処理方法を使用しない理由」と疑問に思われるかもしれません。これは、キーボードのリピート レートがシステムによって異なり、ゲームループのタイミングの制約を受けないため、ゲームプレイがシステムごとに大きく異なるためです。一貫性のあるエクスペリエンスを実現するには、キーボード イベント検出をゲームループと緊密に統合することが重要です。

幸いなことに、イベント クエリを可能にする 16 行の JS ラッパーが含まれています。これは key_status.js と呼ばれ、keydown.left などを確認することで、いつでもキーのステータスをクエリできます。

これで、キーが押されているかどうかをクエリできるようになりました。このシンプルな更新メソッドを使用して、プレーヤーを移動できます。

function update() {
  if (keydown.left) {
    player.x -= 2;
  }

  if (keydown.right) {
    player.x += 2;
  }
}

早速試してみましょう。

プレーヤーを画面外に移動できます。 プレーヤーの位置をクランプして、プレーヤーを境界内に収めましょう。また、プレーヤーの動作が遅いように見えるので、速度を上げてみましょう。

function update() {
  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

入力を追加するのも同じくらい簡単なので、なんらかの発射体を追加してみましょう。

function update() {
  if (keydown.space) {
    player.shoot();
  }

  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

player.shoot = function() {
  console.log("Pew pew");
  // :) Well at least adding the key binding was easy...
};

ゲーム オブジェクトの追加

発射体

では、実際に発射体を追加してみましょう。まず、すべてを次の場所に保存するためのコレクションが必要です。

var playerBullets = [];

次に、箇条書きのインスタンスを作成するためのコンストラクタが必要です。

function Bullet(I) {
  I.active = true;

  I.xVelocity = 0;
  I.yVelocity = -I.speed;
  I.width = 3;
  I.height = 3;
  I.color = "#000";

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.active = I.active && I.inBounds();
  };

  return I;
}

プレーヤーが発射したら、弾丸インスタンスを作成し、弾丸のコレクションに追加します。

player.shoot = function() {
  var bulletPosition = this.midpoint();

  playerBullets.push(Bullet({
    speed: 5,
    x: bulletPosition.x,
    y: bulletPosition.y
  }));
};

player.midpoint = function() {
  return {
    x: this.x + this.width/2,
    y: this.y + this.height/2
  };
};

次に、箇条書きの更新を update ステップ関数に追加する必要があります。箇条書きのコレクションが無期限にいっぱいにならないように、箇条書きのリストをフィルタして、アクティブな箇条書きのみを含めます。また、敵と衝突した弾丸を取り除くこともできます。

function update() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.update();
  });

  playerBullets = playerBullets.filter(function(bullet) {
    return bullet.active;
  });
}

最後に箇条書きを描きます。

function draw() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.draw();
  });
}

今度は、弾丸を追加したときとほぼ同じ方法で敵を追加します。

  enemies = [];

function Enemy(I) {
  I = I || {};

  I.active = true;
  I.age = Math.floor(Math.random() * 128);

  I.color = "#A2B";

  I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2;
  I.y = 0;
  I.xVelocity = 0
  I.yVelocity = 2;

  I.width = 32;
  I.height = 32;

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64);

    I.age++;

    I.active = I.active && I.inBounds();
  };

  return I;
};

function update() {
  ...

  enemies.forEach(function(enemy) {
    enemy.update();
  });

  enemies = enemies.filter(function(enemy) {
    return enemy.active;
  });

  if(Math.random() < 0.1) {
    enemies.push(Enemy());
  }
};

function draw() {
  ...

  enemies.forEach(function(enemy) {
    enemy.draw();
  });
}

画像の読み込みと描画

すべてのボックスが飛び回るのを見るのは素晴らしいことですが、画像があればさらにクールになります。通常、キャンバスに画像の読み込みや描画をするのは面倒な作業です。このような苦痛と悲しみを防ぐために、シンプルなユーティリティ クラスを使用できます。

player.sprite = Sprite("player");

player.draw = function() {
  this.sprite.draw(canvas, this.x, this.y);
};

function Enemy(I) {
  ...

  I.sprite = Sprite("enemy");

  I.draw = function() {
    this.sprite.draw(canvas, this.x, this.y);
  };

  ...
}

衝突検出

さまざまなディーリーが画面上を動き回っていますが、互いにやり取りしていません。爆発のタイミングを通知するために なんらかの衝突検出を追加する必要があります

単純な長方形の衝突検出アルゴリズムを使用します。

function collides(a, b) {
  return a.x < b.x + b.width &&
         a.x + a.width > b.x &&
         a.y < b.y + b.height &&
         a.y + a.height > b.y;
}

次の点を確認します。

  1. プレーヤーの弾丸 => 敵の船
  2. プレーヤー => 敵の宇宙船

update メソッドから呼び出せる、衝突を処理するメソッドを作成します。

function handleCollisions() {
  playerBullets.forEach(function(bullet) {
    enemies.forEach(function(enemy) {
      if (collides(bullet, enemy)) {
        enemy.explode();
        bullet.active = false;
      }
    });
  });

  enemies.forEach(function(enemy) {
    if (collides(enemy, player)) {
      enemy.explode();
      player.explode();
    }
  });
}

function update() {
  ...
  handleCollisions();
}

次に、プレーヤーと敵に爆発メソッドを追加する必要があります。 これにより、削除するようフラグが立てられ、爆発が追加されます。

function Enemy(I) {
  ...

  I.explode = function() {
    this.active = false;
    // Extra Credit: Add an explosion graphic
  };

  return I;
};

player.explode = function() {
  this.active = false;
  // Extra Credit: Add an explosion graphic and then end the game
};

サウンド

エクスペリエンスの仕上げに、心地よい効果音をいくつか追加しましょう。 音声は画像と同様に HTML5 では使いにくいかもしれませんが、Google の魔法のような表現の Sound.js のおかげで、音声を非常にシンプルにすることができます。

player.shoot = function() {
  Sound.play("shoot");
  ...
}

function Enemy(I) {
  ...

  I.explode = function() {
    Sound.play("explode");
    ...
  }
}

API は今ではテアフリーになりましたが、現在のところ、サウンドを追加することは、アプリケーションをクラッシュさせる最も簡単な方法です。サウンドがブラウザタブ全体をカットしたり消えたりすることは珍しくないため、用意しておきましょう。

お別れの言葉

繰り返しになりますが、ゲーム全体のデモをご覧ください。ソースコードを zip 形式でダウンロードすることもできます。

JavaScript と HTML5 で簡単なゲームを作成するための基礎を学んでいただけたでしょうか。適切な抽象化レベルでプログラミングすることで、API のより難しい部分から身を守り、将来の変化に対しても復元力を高めることができます。

参照