1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScript一本で作る!ラク子さんインベーダーゲーム開発完全解説 - Canvas APIで実現する本格ブラウザゲーム入門

Posted at

こんにちは、@YushiYamamotoです!今回は、HTML5のCanvas要素とJavaScriptを使って、ブラウザで動作するスペースインベーダー風ゲームの作り方を解説します。このチュートリアルでは、プログラミング初心者の方でも理解できるよう、基本から丁寧に説明していきますので、ぜひ最後までお付き合いください。

See the Pen Untitled by Yushi Yamamoto (@yamamotoyushi) on CodePen.

はじめに 🎮

ゲーム開発は、プログラミングの知識を実践的に学べる最高の方法の一つです。特にスペースインベーダーのような古典的なゲームは、ゲーム開発の基本的な概念を学ぶのに最適です。

今回のチュートリアルでは、以下の内容を学びます:

  • HTML5 Canvasの基本的な使い方
  • JavaScriptでのゲームループの実装方法
  • キーボード入力の処理
  • 衝突判定のロジック
  • オブジェクト指向プログラミングの実践

それでは、一緒にゲーム開発の世界に飛び込みましょう!

開発環境の準備 🛠️

まずは開発環境を整えましょう。必要なものは以下だけです:

  • テキストエディタ(VSCode、Sublime Textなど)
  • モダンなWebブラウザ(Chrome、Firefox、Edgeなど)

特別なフレームワークやライブラリは必要ありません。シンプルなHTML、CSS、JavaScriptファイルだけで開発します。

VSCodeを使用している方は、「Live Server」拡張機能をインストールしておくと便利です。この拡張機能を使えば、HTMLファイルを右クリックして「Live Serverで開く」を選択するだけで、ブラウザで即座に確認できます。

プロジェクトの基本構造 📁

まずは、以下の3つのファイルを作成しましょう:

  1. index.html - ゲームのHTMLファイル
  2. style.css - スタイルシート
  3. game.js - ゲームのロジック

HTML(index.html)

    <div class="game-container">
        <div id="start-screen" class="screen">
            <h3>ラク子さんインベーダー</h3>
            <div class="difficulty-selection">
                <p>難易度を選択してください</p>
                <button id="easy-btn" class="difficulty-btn">初級</button>
                <button id="medium-btn" class="difficulty-btn">中級</button>
                <button id="hard-btn" class="difficulty-btn">上級</button>
            </div>
            <div class="high-score">
                <h3>ハイスコア</h3>
                <p id="high-score-display">0</p>
            </div>
            <div class="instructions">
                <h4>操作方法</h4>
                <p>左右矢印キー: 移動</p>
                <p>スペースキー: 弾を発射</p>
                <p>モバイル: 画面タップで移動・発射</p>
            </div>
        </div>
        
        <div id="game-screen" class="screen hidden">
            <div class="game-info">
                <div class="score">スコア: <span id="score">0</span></div>
                <div class="level">レベル: <span id="level">1</span></div>
                <div class="lives">ライフ: <span id="lives">3</span></div>
            </div>
            <canvas id="game-canvas"></canvas>
            <div class="mobile-controls">
                <button id="mobile-left"></button>
                <button id="mobile-fire">発射</button>
                <button id="mobile-right"></button>
            </div>
        </div>
        
        <div id="game-over-screen" class="screen hidden">
            <h1>ゲームオーバー</h1>
            <div class="final-score">
                <h2>最終スコア: <span id="final-score">0</span></h2>
                <h3>ハイスコア: <span id="game-over-high-score">0</span></h3>
            </div>
            <button id="retry-btn">もう一度プレイ</button>
            <button id="back-to-menu-btn">メニューに戻る</button>
        </div>
    </div>
    
    <audio id="shoot-sound" src="https://www.soundjay.com/buttons/sounds/button-09.mp3" preload="auto"></audio>
    <audio id="enemy-hit-sound" src="https://www.soundjay.com/buttons/sounds/button-14.mp3" preload="auto"></audio>
    <audio id="player-hit-sound" src="https://www.soundjay.com/buttons/sounds/button-35.mp3" preload="auto"></audio>
    <audio id="game-over-sound" src="https://www.soundjay.com/buttons/sounds/button-21.mp3" preload="auto"></audio>

CSS(style.css)

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Arial', sans-serif;
}

body {
    background-color: #f0f0f0;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    overflow: hidden;
}

.game-container {
    position: relative;
    width: 100%;
    max-width: 800px;
    height: 90vh;
    max-height: 800px;
    background-color: #fff;
    border-radius: 10px;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
    overflow: hidden;
}

.screen {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 20px;
    background-color: #4a86e8; /* らくらくサイトのブランドカラー */
    color: white;
    text-align: center;
}

.hidden {
    display: none;
}

h1 {
    font-size: 2.5rem;
    margin-bottom: 30px;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}

h2 {
    font-size: 1.8rem;
    margin-bottom: 20px;
}

.difficulty-btn, #retry-btn, #back-to-menu-btn {
    background-color: #ff9900; /* アクセントカラー */
    color: white;
    border: none;
    border-radius: 5px;
    padding: 10px 20px;
    margin: 10px;
    font-size: 1.2rem;
    cursor: pointer;
    transition: background-color 0.3s, transform 0.2s;
}

.difficulty-btn:hover, #retry-btn:hover, #back-to-menu-btn:hover {
    background-color: #ffb84d;
    transform: scale(1.05);
}

#game-screen {
    padding: 0;
    background-color: #000;
    justify-content: flex-start;
}

.game-info {
    width: 100%;
    display: flex;
    justify-content: space-between;
    padding: 10px 20px;
    background-color: #4a86e8;
    color: white;
    font-size: 1.2rem;
}

#game-canvas {
    width: 100%;
    height: calc(100% - 50px);
    background-color: #000;
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
    h1 {
        font-size: 2rem;
    }
    
    h2 {
        font-size: 1.5rem;
    }
    
    .difficulty-btn, #retry-btn, #back-to-menu-btn {
        padding: 8px 16px;
        font-size: 1rem;
    }
}

ゲームの設計と実装 🎯

ゲーム開発では、まず全体の設計を考えることが重要です。スペースインベーダーゲームの基本的な要素は以下の通りです:

  1. プレイヤー(ラク子さん)
  2. 敵(インベーダー)
  3. 弾(プレイヤーと敵の両方)
  4. スコアとライフの管理
  5. ゲームループ(アニメーションと更新)

これらの要素をオブジェクト指向プログラミングの原則に従って実装していきます。

ゲームのフロー図

ゲームの基本的な流れは以下のようになります:

初期化 → 難易度選択 → ゲームループ(更新 → 描画)→ ゲームオーバー → リトライ/メニューに戻る

JavaScriptの実装(game.js)

まずは、ゲームの設定と基本構造を定義します:

/**
 * ラク子さんインベーダーゲーム
 * オブジェクト指向プログラミングの原則に従って実装
 */

// ゲームの設定とグローバル変数
const GameSettings = {
    // 難易度設定
    difficulty: {
        easy: { enemySpeed: 1, enemySpawnRate: 2000, bulletSpeed: 7 },
        medium: { enemySpeed: 1.5, enemySpawnRate: 1500, bulletSpeed: 8 },
        hard: { enemySpeed: 2, enemySpawnRate: 1000, bulletSpeed: 9 }
    },
    // 現在の難易度
    currentDifficulty: null,
    // キャンバスサイズ
    canvas: {
        width: 0,
        height: 0
    },
    // ゲームの状態
    gameState: {
        score: 0,
        level: 1,
        lives: 3,
        isGameOver: false,
        isPaused: false
    },
    // ハイスコア
    highScore: 0,
    // 敵の種類
    enemyTypes: ['normal', 'fast', 'tough'],
    // 敵の出現確率 (レベルに応じて変化)
    enemySpawnProbability: {
        normal: 0.7,
        fast: 0.2,
        tough: 0.1
    }
};

次に、ゲームのメインクラスを実装します:

// ゲームクラス
class Game {
    constructor() {
        // DOM要素の取得
        this.canvas = document.getElementById('game-canvas');
        this.ctx = this.canvas.getContext('2d');
        this.startScreen = document.getElementById('start-screen');
        this.gameScreen = document.getElementById('game-screen');
        this.gameOverScreen = document.getElementById('game-over-screen');
        this.scoreElement = document.getElementById('score');
        this.levelElement = document.getElementById('level');
        this.livesElement = document.getElementById('lives');
        this.finalScoreElement = document.getElementById('final-score');
        this.highScoreElement = document.getElementById('high-score-display');
        this.gameOverHighScoreElement = document.getElementById('game-over-high-score');
        
        // ゲームオブジェクト
        this.player = null;
        this.enemies = [];
        this.bullets = [];
        this.particles = [];
        
        // ゲームループ用の変数
        this.lastTime = 0;
        this.enemySpawnTimer = 0;
        this.animationFrameId = null;
        
        // キー入力の状態
        this.keys = {
            ArrowLeft: false,
            ArrowRight: false,
            Space: false
        };
        
        // 敵の画像を生成
        this.createEnemyImages();
        
        // イベントリスナーの設定
        this.setupEventListeners();
        
        // ローカルストレージからハイスコアを読み込む
        this.loadHighScore();
        
        // 初期画面の表示
        this.showStartScreen();
    }
    
    // 敵の画像を生成するメソッド
    createEnemyImages() {
        // 実装は省略(後述)
    }
    
    // イベントリスナーを設定するメソッド
    setupEventListeners() {
        // 難易度選択ボタン
        document.getElementById('easy-btn').addEventListener('click', () => this.startGame('easy'));
        document.getElementById('medium-btn').addEventListener('click', () => this.startGame('medium'));
        document.getElementById('hard-btn').addEventListener('click', () => this.startGame('hard'));
        
        // リトライボタン
        document.getElementById('retry-btn').addEventListener('click', () => this.restartGame());
        
        // メニューに戻るボタン
        document.getElementById('back-to-menu-btn').addEventListener('click', () => this.showStartScreen());
        
        // キーボード入力
        window.addEventListener('keydown', (e) => {
            if (e.code in this.keys) {
                this.keys[e.code] = true;
                
                // スペースキーが押されたら弾を発射
                if (e.code === 'Space' && !GameSettings.gameState.isPaused && !GameSettings.gameState.isGameOver) {
                    this.player.shoot();
                }
                
                // デフォルトの動作を防止(スペースバーでのスクロールなど)
                e.preventDefault();
            }
        });
        
        window.addEventListener('keyup', (e) => {
            if (e.code in this.keys) {
                this.keys[e.code] = false;
            }
        });
        
        // ウィンドウのリサイズ
        window.addEventListener('resize', () => this.resizeCanvas());
    }
    
    // ゲームを開始するメソッド
    startGame(difficulty) {
        // 難易度の設定
        GameSettings.currentDifficulty = GameSettings.difficulty[difficulty];
        
        // ゲーム状態の初期化
        GameSettings.gameState.score = 0;
        GameSettings.gameState.level = 1;
        GameSettings.gameState.lives = 3;
        GameSettings.gameState.isGameOver = false;
        GameSettings.gameState.isPaused = false;
        
        // UI更新
        this.scoreElement.textContent = GameSettings.gameState.score;
        this.levelElement.textContent = GameSettings.gameState.level;
        this.livesElement.textContent = GameSettings.gameState.lives;
        
        // 画面の切り替え
        this.startScreen.classList.add('hidden');
        this.gameOverScreen.classList.add('hidden');
        this.gameScreen.classList.remove('hidden');
        
        // キャンバスのリサイズ
        this.resizeCanvas();
        
        // プレイヤーの作成
        this.player = new Player(this);
        
        // 敵とその他のオブジェクトの初期化
        this.enemies = [];
        this.bullets = [];
        this.particles = [];
        
        // ゲームループの開始
        this.lastTime = performance.now();
        this.enemySpawnTimer = 0;
        this.gameLoop(this.lastTime);
    }
    
    // ゲームループ
    gameLoop(timestamp) {
        // 経過時間の計算
        const deltaTime = timestamp - this.lastTime;
        this.lastTime = timestamp;
        
        // ゲームが一時停止中またはゲームオーバーの場合は更新しない
        if (!GameSettings.gameState.isPaused && !GameSettings.gameState.isGameOver) {
            // 敵の生成
            this.enemySpawnTimer += deltaTime;
            if (this.enemySpawnTimer >= GameSettings.currentDifficulty.enemySpawnRate) {
                this.spawnEnemy();
                this.enemySpawnTimer = 0;
            }
            
            // プレイヤーの更新
            this.player.update(deltaTime);
            
            // 敵の更新
            for (let i = this.enemies.length - 1; i >= 0; i--) {
                this.enemies[i].update(deltaTime);
                
                // 画面外に出た敵を削除
                if (this.enemies[i].y > GameSettings.canvas.height) {
                    this.enemies.splice(i, 1);
                }
            }
            
            // 弾の更新と衝突判定
            this.updateBullets(deltaTime);
            
            // レベルアップの判定
            if (GameSettings.gameState.score >= GameSettings.gameState.level * 1000) {
                this.levelUp();
            }
        }
        
        // 描画
        this.render();
        
        // 次のフレームをリクエスト
        this.animationFrameId = requestAnimationFrame((timestamp) => this.gameLoop(timestamp));
    }
    
    // 弾の更新と衝突判定
    updateBullets(deltaTime) {
        for (let i = this.bullets.length - 1; i >= 0; i--) {
            this.bullets[i].update(deltaTime);
            
            // 画面外に出た弾を削除
            if (this.bullets[i].y  GameSettings.canvas.height) {
                this.bullets.splice(i, 1);
                continue;
            }
            
            // 弾と敵の衝突判定
            if (this.bullets[i].isPlayerBullet) {
                for (let j = this.enemies.length - 1; j >= 0; j--) {
                    if (this.checkCollision(this.bullets[i], this.enemies[j])) {
                        // 敵にダメージを与える
                        this.enemies[j].takeDamage();
                        
                        // 敵が倒れた場合
                        if (this.enemies[j].health <= 0) {
                            // スコアの加算
                            GameSettings.gameState.score += this.enemies[j].score;
                            this.scoreElement.textContent = GameSettings.gameState.score;
                            
                            // パーティクルの生成
                            this.createExplosion(this.enemies[j].x, this.enemies[j].y, this.enemies[j].color);
// 敵の削除
                            this.enemies.splice(j, 1);
                        }
                        
                        // 弾の削除
                        this.bullets.splice(i, 1);
                        break;
                    }
                }
            } else {
                // 敵の弾とプレイヤーの衝突判定
                if (this.checkCollision(this.bullets[i], this.player)) {
                    // プレイヤーにダメージを与える
                    this.playerHit();
                    
                    // 弾の削除
                    this.bullets.splice(i, 1);
                }
            }
        }
    }
    
    // 衝突判定を行うメソッド
    checkCollision(obj1, obj2) {
        return obj1.x  obj2.x &&
               obj1.y  obj2.y;
    }
    
    // 描画メソッド
    render() {
        // キャンバスのクリア
        this.ctx.fillStyle = '#000';
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
        
        // 背景の星を描画
        this.drawStars();
        
        // プレイヤーの描画
        this.player.draw(this.ctx);
        
        // 敵の描画
        for (const enemy of this.enemies) {
            enemy.draw(this.ctx);
        }
        
        // 弾の描画
        for (const bullet of this.bullets) {
            bullet.draw(this.ctx);
        }
        
        // パーティクルの描画
        for (const particle of this.particles) {
            particle.draw(this.ctx);
        }
    }
}

ゲームのコアメカニズム解説 💡

1. ゲームループの仕組み

ゲーム開発の中心となるのが「ゲームループ」です。これは、ゲームの状態を更新し、画面を描画する処理を繰り返すループのことです。

┌─────────────────┐
│    ゲーム開始    │
└────────┬────────┘
         ↓
┌─────────────────┐
│  ゲームループ開始 │
└────────┬────────┘
         ↓
┌─────────────────┐
│    状態の更新    │◄───┐
└────────┬────────┘    │
         ↓             │
┌─────────────────┐    │
│    画面の描画    │    │
└────────┬────────┘    │
         ↓             │
┌─────────────────┐    │
│次のフレームをリクエスト│    │
└────────┬────────┘    │
         └─────────────┘

JavaScriptでは、requestAnimationFrame()メソッドを使用してこのループを実装します。このメソッドは、ブラウザのリフレッシュレート(通常は60FPS)に合わせて関数を呼び出します。

gameLoop(timestamp) {
    // 経過時間の計算
    const deltaTime = timestamp - this.lastTime;
    this.lastTime = timestamp;
    
    // ゲームの状態を更新
    this.update(deltaTime);
    
    // 画面を描画
    this.render();
    
    // 次のフレームをリクエスト
    this.animationFrameId = requestAnimationFrame((timestamp) => this.gameLoop(timestamp));
}

2. deltaTimeの重要性

deltaTime(前回のフレームからの経過時間)を使用することで、異なるフレームレートでも一貫した動きを実現できます。例えば、プレイヤーの移動を以下のように実装します:

// 正しい実装(フレームレートに依存しない)
player.x += player.speed * (deltaTime / 1000);

// 間違った実装(フレームレートに依存する)
player.x += player.speed;

3. 衝突判定のロジック

ゲームでは、オブジェクト同士の衝突を検出する必要があります。最も簡単な方法は、「軸並行境界ボックス(AABB)」と呼ばれる手法です:

checkCollision(obj1, obj2) {
    return obj1.x  obj2.x &&
           obj1.y  obj2.y;
}

この方法は計算コストが低く、シンプルなゲームには十分です。より複雑な形状の衝突判定には、円の衝突判定や多角形の衝突判定などの方法があります。

ゲームオブジェクトの実装 🎮

プレイヤークラス

プレイヤーは、ユーザーが操作するキャラクターです。左右に移動し、弾を発射する機能を持ちます。

class Player {
    constructor(game) {
        this.game = game;
        this.width = 60;
        this.height = 60;
        this.x = GameSettings.canvas.width / 2 - this.width / 2;
        this.y = GameSettings.canvas.height - this.height - 20;
        this.speed = 5;
        this.shootCooldown = 0;
        this.image = new Image();
        this.image.src = 'https://rakuraku-site.prodouga.com/img/RakuRaku.avif';
    }
    
    update(deltaTime) {
        // 左右の移動
        if (this.game.keys.ArrowLeft) {
            this.x -= this.speed;
        }
        if (this.game.keys.ArrowRight) {
            this.x += this.speed;
        }
        
        // 画面外に出ないように制限
        if (this.x  GameSettings.canvas.width - this.width) {
            this.x = GameSettings.canvas.width - this.width;
        }
        
        // 射撃のクールダウン
        if (this.shootCooldown > 0) {
            this.shootCooldown -= deltaTime;
        }
    }
    
    draw(ctx) {
        // 画像が読み込まれていない場合は代替の描画を行う
        if (!this.image.complete) {
            ctx.fillStyle = '#4a86e8';
            ctx.fillRect(this.x, this.y, this.width, this.height);
            return;
        }
        
        // 画像を描画
        ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
    }
    
    shoot() {
        // クールダウン中は発射できない
        if (this.shootCooldown > 0) return;
        
        // 弾の生成
        const bullet = new Bullet(
            this.x + this.width / 2 - 2.5,
            this.y,
            true
        );
        this.game.bullets.push(bullet);
        
        // クールダウンの設定
        this.shootCooldown = 300;
    }
}

敵クラス

敵は、画面上部から下に向かって移動し、ランダムに弾を発射します。敵には複数の種類があり、それぞれ異なる特性を持ちます。

class Enemy {
    constructor(game, type) {
        this.game = game;
        this.type = type;
        this.width = 40;
        this.height = 40;
        this.x = Math.random() * (GameSettings.canvas.width - this.width);
        this.y = -this.height;
        
        // 敵の種類に応じたプロパティの設定
        switch (type) {
            case 'normal':
                this.speed = GameSettings.currentDifficulty.enemySpeed;
                this.health = 1;
                this.score = 100;
                this.color = '#ff9900';
                this.imageIndex = 0;
                break;
            case 'fast':
                this.speed = GameSettings.currentDifficulty.enemySpeed * 1.5;
                this.health = 1;
                this.score = 150;
                this.color = '#4a86e8';
                this.imageIndex = 1;
                break;
            case 'tough':
                this.speed = GameSettings.currentDifficulty.enemySpeed * 0.8;
                this.health = 2;
                this.score = 200;
                this.color = '#ff5252';
                this.imageIndex = 2;
                break;
        }
        
        // 射撃のタイマー
        this.shootTimer = Math.random() * 3000 + 2000;
    }
    
    update(deltaTime) {
        // 下に移動
        this.y += this.speed;
        
        // 射撃のタイマーを更新
        this.shootTimer -= deltaTime;
        if (this.shootTimer  0.1) {
            this.size -= 0.05;
        }
    }
    
    draw(ctx) {
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
        ctx.fill();
    }
}

Canvas描画のテクニック 🎨

HTML5のCanvas要素は、2Dグラフィックスを描画するための強力なAPIを提供します。以下に、ゲーム開発でよく使用されるCanvasの描画テクニックをいくつか紹介します。

1. 基本的な図形の描画

// 四角形の描画
ctx.fillStyle = '#ff0000';
ctx.fillRect(x, y, width, height);

// 円の描画
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();

// 線の描画
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();

2. 画像の描画

// 画像オブジェクトの作成
const img = new Image();
img.src = 'path/to/image.png';

// 画像の描画(画像が読み込まれた後)
img.onload = function() {
    ctx.drawImage(img, x, y, width, height);
};

// または、画像が既に読み込まれているか確認
if (img.complete) {
    ctx.drawImage(img, x, y, width, height);
} else {
    // 代替の描画処理
}

3. テキストの描画

ctx.font = '20px Arial';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText('Game Over', canvas.width / 2, canvas.height / 2);

4. 敵の画像生成

敵の画像をプログラムで生成する方法を紹介します。これにより、外部画像ファイルに依存せずにゲームを作成できます。

createEnemyImages() {
    const colors = ['#ff9900', '#4a86e8', '#ff5252'];
    GameSettings.enemyImages = [];
    
    for (let i = 0; i 

    
    発射
    

/* CSSに追加 */
.mobile-controls {
    display: none;
    width: 100%;
    height: 60px;
    background-color: #333;
    justify-content: space-between;
    padding: 10px;
}

.mobile-controls button {
    background-color: #ff9900;
    color: white;
    border: none;
    border-radius: 5px;
    padding: 10px;
    font-size: 1.2rem;
    width: 30%;
    height: 100%;
}

@media (max-width: 768px) {
    .mobile-controls {
        display: flex;
    }
    
    #game-canvas {
        height: calc(100% - 110px);
}
}
// JavaScriptに追加(setupEventListenersメソッド内)
// モバイルコントロール
document.getElementById('mobile-left').addEventListener('touchstart', () => {
    this.keys.ArrowLeft = true;
});

document.getElementById('mobile-left').addEventListener('touchend', () => {
    this.keys.ArrowLeft = false;
});

document.getElementById('mobile-right').addEventListener('touchstart', () => {
    this.keys.ArrowRight = true;
});

document.getElementById('mobile-right').addEventListener('touchend', () => {
    this.keys.ArrowRight = false;
});

document.getElementById('mobile-fire').addEventListener('click', () => {
    if (!GameSettings.gameState.isPaused && !GameSettings.gameState.isGameOver) {
        this.player.shoot();
    }
});

パフォーマンス最適化のポイント ⚡

ゲームのパフォーマンスを向上させるためのテクニックをいくつか紹介します。

1. オブジェクトプーリング

オブジェクトの生成と削除は、JavaScriptのガベージコレクションに負荷をかけます。頻繁に生成・削除されるオブジェクト(弾やパーティクルなど)には、オブジェクトプーリングを使用することで、パフォーマンスを向上させることができます。

// オブジェクトプールの例
class BulletPool {
    constructor(size) {
        this.pool = [];
        // プールを初期化
        for (let i = 0; i  GameSettings.canvas.width ||
    enemy.y  GameSettings.canvas.height) {
    continue; // 更新をスキップ
}

3. requestAnimationFrameの使用

setIntervalsetTimeoutではなく、requestAnimationFrameを使用することで、ブラウザのレンダリングサイクルに合わせた効率的なアニメーションを実現できます。

// 良い実装
function gameLoop() {
    update();
    render();
    requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

// 避けるべき実装
setInterval(function() {
    update();
    render();
}, 16); // 約60FPS

難易度調整とゲームバランス 🎯

良いゲームは、適切な難易度とバランスを持っています。以下に、難易度調整のためのテクニックをいくつか紹介します。

1. 段階的な難易度上昇

ゲームが進むにつれて徐々に難しくなるようにすることで、プレイヤーは常に適度な挑戦を感じることができます。

// レベルアップの処理
levelUp() {
    GameSettings.gameState.level++;
    this.levelElement.textContent = GameSettings.gameState.level;
    
    // 難易度の調整
    GameSettings.currentDifficulty.enemySpeed *= 1.1;
    GameSettings.currentDifficulty.enemySpawnRate *= 0.9;
    
    // 敵の出現確率の調整
    if (GameSettings.gameState.level % 3 === 0) {
        GameSettings.enemySpawnProbability.normal -= 0.05;
        GameSettings.enemySpawnProbability.fast += 0.03;
        GameSettings.enemySpawnProbability.tough += 0.02;
        
        // 確率の下限を設定
        if (GameSettings.enemySpawnProbability.normal  1500) {
        // プレイヤーが非常に上手い場合は難易度を上げる
        GameSettings.currentDifficulty.enemySpeed *= 1.05;
        GameSettings.currentDifficulty.enemySpawnRate *= 0.95;
    } else if (performance 




// JavaScriptに追加
// 効果音の取得
this.shootSound = document.getElementById('shoot-sound');
this.enemyHitSound = document.getElementById('enemy-hit-sound');
this.playerHitSound = document.getElementById('player-hit-sound');
this.gameOverSound = document.getElementById('game-over-sound');

// 効果音の再生
this.shootSound.currentTime = 0;
this.shootSound.play();

3. 背景の星

宇宙を表現するために、背景に星を描画しましょう。

// 背景の星を描画するメソッド
drawStars() {
    this.ctx.fillStyle = '#fff';
    
    // 固定の星の位置
    const starPositions = [
        { x: 50, y: 50 },
        { x: 150, y: 100 },
        { x: 250, y: 200 },
        // ... 他の星の位置
    ];
    
    // 星を描画
    for (const star of starPositions) {
        const x = star.x % this.canvas.width;
        const y = star.y % this.canvas.height;
        
        // 星の大きさをランダムに
        const size = Math.random() * 2 + 1;
        
        this.ctx.beginPath();
        this.ctx.arc(x, y, size, 0, Math.PI * 2);
        this.ctx.fill();
    }
}

ゲームの初期化と実行

最後に、ゲームを初期化して実行するコードを追加します。

// ゲームの初期化
window.addEventListener('DOMContentLoaded', () => {
    new Game();
});

まとめと発展的な内容 🚀

これで、基本的なスペースインベーダー風ゲームの実装が完了しました!以下に、さらに発展させるためのアイデアをいくつか紹介します。

1. ボスキャラクターの追加

一定のスコアに達したら、ボスキャラクターが出現するようにしましょう。ボスは通常の敵よりも体力が高く、複雑な攻撃パターンを持ちます。

2. パワーアップアイテムの実装

敵を倒した時に、ランダムでパワーアップアイテムがドロップするようにしましょう。例えば、以下のようなパワーアップが考えられます:

  • 連射:一度に複数の弾を発射できる
  • シールド:一定時間ダメージを無効化する
  • スピードアップ:プレイヤーの移動速度が上昇する

3. ステージの多様化

複数のステージを実装し、それぞれ異なる背景や敵の配置を持つようにしましょう。ステージごとに難易度や特徴を変えることで、ゲームの多様性が増します。

4. スコアボードの実装

オンラインのスコアボードを実装し、他のプレイヤーとスコアを競えるようにしましょう。これには、サーバーサイドの実装が必要になります。

おわりに

今回は、HTML5のCanvas要素とJavaScriptを使って、スペースインベーダー風ゲームを作成する方法を解説しました。ゲーム開発は、プログラミングの知識を実践的に学ぶための素晴らしい方法です。

このチュートリアルで学んだ技術は、他の2Dゲームの開発にも応用できます。ぜひ、自分だけのオリジナルゲームを作ってみてください!

質問やフィードバックがあれば、コメント欄でお待ちしています。楽しいゲーム開発ライフを!


参考リソース


最後に:業務委託のご相談を承ります

私は、業務委託エンジニアとしてWEB制作やシステム開発を請け負っています。最新技術を活用し、レスポンシブなWebサイトやインタラクティブなアプリケーション、API連携など、幅広いニーズに対応可能です。

「課題解決に向けた即戦力が欲しい」「高品質なWeb制作を依頼したい」という方は、ぜひお気軽にご相談ください。一緒にビジネスの成長を目指しましょう!

👉 ポートフォリオ

🌳 らくらくサイト

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?