👾

HTML + Canvas + TypeScript で作成するインベーダーゲーム

2025/03/18に公開

TypeScriptで作るインベーダーゲーム

デモページ
GitHubリポジトリ

概要

TypeScriptを使用してシンプルなインベーダーゲームを作成しました。プレイヤーは矢印キーで左右に移動し、スペースキーで弾を発射します。敵を倒すとスコアが加算され、すべての敵を倒すと次のウェーブへ進みます。

それぞれの役割

1. HTML (index.html)

  • <canvas> 要素を定義し、ゲームの描画領域を作成。
  • game.js(コンパイルされた TypeScript)を読み込んでゲームを動作させる。

2. Canvas API (canvas.getContext("2d"))

  • canvas 要素を取得し、2D 描画用のコンテキストを使用。
  • fillText()fillRect() などの関数を使って、プレイヤーや敵、弾を描画。

3. TypeScript (game.ts)

  • PlayerEnemyBullet などのクラスを定義し、オブジェクト指向で管理。
  • イベントリスナーでプレイヤーの操作を処理。
  • requestAnimationFrame() を使ってゲームループを実装。

初期セットアップ

$ pnpm install -g typescript

コード解説

html.index

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TypeScript Space Invaders</title>
    <link
      rel="icon"
      type="image/vnd.microsoft.ico"
      href="assets/image/favicon.ico"
    />
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <canvas id="gameCanvas"></canvas>
    <script src="game.js"></script>
  </body>
</html>

game.ts

1. キャンバスの設定

const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 600;

canvas要素を取得し、描画コンテキストを取得します。このctxを使ってゲーム画面の描画を行います。

2. ゲームの状態管理

let gameOver = false;
let moveLeft = false;
let moveRight = false;
let player;
let enemies = [];
let score = 0;
let wave = 1;
let previousEnemyCount = 3;
let spawningNewWave = false;
  • gameOver: ゲームオーバー状態かどうかを管理するフラグ。
  • moveLeft, moveRight: プレイヤーが移動しているかどうかを判定するフラグ。
  • player: プレイヤーオブジェクト。
  • enemies: 敵の配列。
  • score: 現在のスコア。
  • wave: 現在のウェーブ。
  • previousEnemyCount: 前のウェーブで登場した敵の数。
  • spawningNewWave: 新しいウェーブの敵を生成中かどうかを管理するフラグ。

3. プレイヤークラス

class Player {
    constructor() {
        this.speed = 5;
        this.bullets = [];
        this.x = canvas.width / 2;
        this.y = canvas.height - 40;
    }
    move() {
        if (moveLeft) this.x -= this.speed;
        if (moveRight) this.x += this.speed;
        this.x = Math.max(0, Math.min(canvas.width - 40, this.x));
    }
    shoot() {
        this.bullets.push(new Bullet(this.x, this.y - 10));
    }
    update() {
        this.move();
        this.bullets = this.bullets.filter(bullet => bullet.y > 0);
        this.bullets.forEach(bullet => bullet.update());
    }
    draw() {
        ctx.fillText("🚀", this.x, this.y);
        this.bullets.forEach(bullet => bullet.draw());
    }
}
  • move(): 左右の移動を処理し、画面外に出ないよう制限を加えます。
  • shoot(): 弾を発射し、bullets配列に追加します。
  • update(): プレイヤーの移動と弾の更新を処理します。
  • draw(): プレイヤーの表示と発射した弾の描画を行います。

4. 弾クラス

class Bullet {
    constructor(x, y) {
        this.speed = 5;
        this.width = 5;
        this.height = 10;
        this.x = x - this.width / 2;
        this.y = y;
    }
    update() {
        this.y -= this.speed;
    }
    draw() {
        ctx.fillStyle = "red";
        ctx.fillRect(this.x, this.y, this.width, this.height);
    }
}
  • update(): 弾を上方向に移動させます。
  • draw(): 弾の描画を行います。

5. 敵クラス

class Enemy {
    constructor(x, y, speed) {
        this.width = 40;
        this.height = 30;
        this.direction = 1;
        this.bullets = [];
        this.x = x;
        this.y = y;
        this.speed = speed;
    }
    update() {
        this.x += this.speed * this.direction;
        if (this.x < 0 || this.x + this.width > canvas.width) {
            this.direction *= -1;
            this.y += this.height;
        }
        if (Math.random() < 0.01) this.shoot();
        this.bullets.forEach(bullet => bullet.update());
    }
    shoot() {
        this.bullets.push(new EnemyBullet(this.x + this.width / 2, this.y + this.height));
    }
    draw() {
        ctx.fillText("👾", this.x, this.y);
        this.bullets.forEach(bullet => bullet.draw());
    }
}
  • update(): 敵の移動を制御し、ランダムに弾を発射します。
  • shoot(): 弾を発射し、bullets配列に追加します。
  • draw(): 敵と弾の描画を行います。

6. ゲームの進行

function spawnEnemies() {
    enemies = [];
    let startY = 50;
    let enemySpeed = 1 + wave * 0.2;
    for (let i = 0; i < previousEnemyCount; i++) {
        let x = (i % 6) * 80 + 50;
        let y = startY + Math.floor(i / 6) * 40;
        enemies.push(new Enemy(x, y, enemySpeed));
    }
}
  • spawnEnemies(): ウェーブごとに敵を生成します。

7. ゲームループ

function gameLoop() {
    if (gameOver) {
        ctx.fillText("GAME OVER", canvas.width / 2, canvas.height / 2);
        return;
    }
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    player.update();
    player.draw();
    enemies.forEach(enemy => enemy.update());
    enemies.forEach(enemy => enemy.draw());
    requestAnimationFrame(gameLoop);
}
  • gameLoop(): 画面を更新し、各要素の描画と状態更新を行います。

コンパイル

$ tsc

まとめ

TypeScriptを使って基本的なインベーダーゲームを作成しました。
レベルの調整がとても難しいです。
ゲーム制作者には頭が下がります。

Discussion