HTML5 Canvas + JavaScript だけで作る！ラク子さんの横スクロールアクションゲーム開発入門

# 「ラク子さんの冒険」を作ろう！HTML5 Canvasで横スクロールアクションゲーム開発 🎮

## はじめに：今回のチャレンジ 🚀

みなさん、こんにちは！@YushiYamamotoです。今回は[「らくらくサイト」](https://rakuraku-site.prodouga.com/)のイメージキャラクター「ラク子さん」を主人公にした横スクロールアクションゲームの作り方をご紹介します。
HTML5のCanvas要素とバニラJavaScriptだけで、スマホでも動作する本格的なゲームを作っていきましょう！

このブログを読めば、JavaScriptの基本から応用まで、実践的なゲーム開発の知識が身につきます。特にオブジェクト指向プログラミングの考え方や、ゲームループの実装方法など、他のプロジェクトにも応用できる技術が満載です。

## 完成イメージと機能概要 📋

まずは完成イメージを確認しましょう。

<p class="codepen" data-height="610" data-default-tab="result" data-slug-hash="vEYZRWy" data-pen-title="ラク子さんの冒険" data-editable="true" data-user="yamamotoyushi" style="height: 610px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/yamamotoyushi/pen/vEYZRWy">
  ラク子さんの冒険</a> by Yushi Yamamoto (<a href="https://codepen.io/yamamotoyushi">@yamamotoyushi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

「ラク子さんの冒険」は以下の機能を持つシンプルながらも奥深い横スクロールアクションゲームです：

- ラク子さんが主人公の横スクロールアクションゲーム
- キーボード操作（矢印キー、スペースキー）とタッチ操作に対応
- 障害物を避けながらコインを集める
- スコアシステムとハイスコア機能
- レスポンシブデザインでPC・スマホどちらでも遊べる

## 開発環境と使用技術 🛠️

今回使用する技術スタックはシンプルです：

- HTML5（Canvas API）
- CSS3
- バニラJavaScript（フレームワークなし）

特別なツールは必要ありません。お好みのテキストエディタ（VSCode推奨）とブラウザがあれば開発できます。

## ゲーム開発の基本フロー 🔄

横スクロールゲームの基本的な流れを図解してみましょう：

```
初期化 → ゲームループ開始
   ↓
更新処理（Update）← ユーザー入力
   ↓
描画処理（Draw）
   ↓
衝突判定
   ↓
スコア計算
   ↓
ゲームオーバー判定 → Yes → ゲーム終了
   ↓ No
ゲームループ継続
```

このフローを理解することが、ゲーム開発の基本です。

## 実装手順：ステップバイステップ 👣

### Step 1: HTML/CSSの基本構造を作る

まずはゲームの「器」となるHTML/CSSを作成します。

### HTML

```html
    <div id="game-container">
        <canvas id="bg-layer"></canvas>
        <canvas id="game-layer"></canvas>
        <canvas id="ui-layer"></canvas>
        
        <div id="score-display">スコア: 0</div>
        
        <div id="mobile-controls">
            <div id="left-btn" class="control-btn">←</div>
            <div id="jump-btn" class="control-btn">↑</div>
            <div id="right-btn" class="control-btn">→</div>
        </div>
        
        <div id="start-screen" class="screen">
            <h1>ラク子さんの冒険</h1>
                    <small>Powered by らくらくサイト</small>

          <br>
            <button id="start-btn" class="btn">ゲームスタート</button>
            <br><p>矢印キー（←→）で移動<br>スペースキーでジャンプ<br>コインを集めて高得点を目指そう！</p>
        </div>
        
        <div id="game-over-screen" class="screen" style="display: none;">
            <h1>ゲームオーバー</h1>
            <p id="final-score">スコア: 0</p>
            <p id="high-score">ハイスコア: 0</p>
            <button id="restart-btn" class="btn">もう一度プレイ</button>
        </div>
    </div>
```

### CSS

```CSS
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #000000;
            font-family: 'Hiragino Kaku Gothic Pro', 'メイリオ', sans-serif;
        }
        #game-container {
            position: relative;
            width: 100%;
            max-width: 800px;
            height: 450px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
        }
        canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        .screen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            z-index: 20;
            text-align: center;
        }
        .btn {
            padding: 0.8rem 1.5rem;
            font-size: 1.2rem;
            background-color: #ffcc00;
            color: #333;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        #score-display {
            position: absolute;
            top: 10px;
            left: 10px;
            font-size: 1.2rem;
            color: white;
            text-shadow: 1px 1px 2px black;
            z-index: 15;
        }
        #mobile-controls {
            position: absolute;
            bottom: 20px;
            width: 100%;
            display: none;
            justify-content: space-between;
            padding: 0 20px;
            z-index: 15;
        }
        @media (max-width: 768px) {
            #mobile-controls { display: flex; }
        }
```

ポイント：
- 3つのキャンバスレイヤーを使用（背景、ゲーム、UI）
- モバイル用のコントロールボタンを用意
- スタート画面とゲームオーバー画面を準備

### Step 2: ゲームの基本設定とクラス設計

次に、JavaScriptでゲームの基本設定とクラス設計を行います。

```javascript
// ゲームの設定
const GAME_WIDTH = 800;
const GAME_HEIGHT = 450;
const GRAVITY = 0.5;
const JUMP_FORCE = -12;
const PLAYER_SPEED = 5;
const SCROLL_SPEED = 3;
const OBSTACLE_FREQUENCY = 120; // フレーム数
const COIN_FREQUENCY = 100; // フレーム数

// キャンバスの設定
const bgLayer = document.getElementById('bg-layer');
const gameLayer = document.getElementById('game-layer');
const uiLayer = document.getElementById('ui-layer');
const bgCtx = bgLayer.getContext('2d');
const gameCtx = gameLayer.getContext('2d');
const uiCtx = uiLayer.getContext('2d');

// UI要素
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over-screen');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const scoreDisplay = document.getElementById('score-display');
const finalScore = document.getElementById('final-score');
const highScoreDisplay = document.getElementById('high-score');

// キャンバスのサイズ設定
function resizeCanvas() {
    const container = document.getElementById('game-container');
    const containerWidth = container.clientWidth;
    const containerHeight = container.clientHeight;
    
    bgLayer.width = gameLayer.width = uiLayer.width = GAME_WIDTH;
    bgLayer.height = gameLayer.height = uiLayer.height = GAME_HEIGHT;
    
    bgLayer.style.width = gameLayer.style.width = uiLayer.style.width = `${containerWidth}px`;
    bgLayer.style.height = gameLayer.style.height = uiLayer.style.height = `${containerHeight}px`;
}
```

### Step 3: プレイヤークラスの実装

ゲームの主人公「ラク子さん」を動かすためのクラスを実装します。

```javascript
// プレイヤークラス
class Player {
    constructor() {
        this.width = 50;
        this.height = 80;
        this.x = 100;
        this.y = GAME_HEIGHT - this.height - 20;
        this.velocityY = 0;
        this.isJumping = false;
        this.direction = 1; // 1: 右向き, -1: 左向き
    }
    
    update() {
        // 重力の適用
        this.velocityY += GRAVITY;
        this.y += this.velocityY;
        
        // 地面との衝突判定
        if (this.y > GAME_HEIGHT - this.height - 20) {
            this.y = GAME_HEIGHT - this.height - 20;
            this.velocityY = 0;
            this.isJumping = false;
        }
        
        // 左右移動
        if (keys.ArrowLeft) {
            this.x -= PLAYER_SPEED;
            this.direction = -1;
        }
        if (keys.ArrowRight) {
            this.x += PLAYER_SPEED;
            this.direction = 1;
        }
        
        // 画面端の制限
        if (this.x  GAME_WIDTH - this.width) this.x = GAME_WIDTH - this.width;
    }
    
    jump() {
        if (!this.isJumping) {
            this.velocityY = JUMP_FORCE;
            this.isJumping = true;
        }
    }
    
    draw() {
        gameCtx.save();
        if (this.direction === -1) {
            gameCtx.scale(-1, 1);
            gameCtx.drawImage(images.player, -this.x - this.width, this.y, this.width, this.height);
        } else {
            gameCtx.drawImage(images.player, this.x, this.y, this.width, this.height);
        }
        gameCtx.restore();
    }
    
    collidesWith(object) {
        return this.x  object.x &&
               this.y  object.y;
    }
}
```

ポイント：
- 重力の実装（velocityYに重力を加算）
- 左右移動とジャンプの実装
- 衝突判定メソッドの実装
- 向きに応じた描画の反転処理

### Step 4: 障害物とコインのクラス実装

次に、障害物とコインのクラスを実装します。

```javascript
// 障害物クラス
class Obstacle {
    constructor() {
        this.width = 50;
        this.height = 50;
        this.x = GAME_WIDTH;
        this.y = GAME_HEIGHT - this.height - 20;
    }
    
    update() {
        this.x -= SCROLL_SPEED;
    }
    
    draw() {
        gameCtx.drawImage(images.obstacle, this.x, this.y, this.width, this.height);
    }
    
    isOffScreen() {
        return this.x + this.width = 0; i--) {
        obstacles[i].update();
        obstacles[i].draw();
        
        // 画面外の障害物を削除
        if (obstacles[i].isOffScreen()) {
            obstacles.splice(i, 1);
        }
        
        // 衝突判定
        if (player.collidesWith(obstacles[i])) {
            gameOver();
            return;
        }
    }
    
    // コインの更新と描画
    for (let i = coins.length - 1; i >= 0; i--) {
        coins[i].update();
        coins[i].draw();
        
        // 画面外のコインを削除
        if (coins[i].isOffScreen()) {
            coins.splice(i, 1);
            continue;
        }
        
        // コイン獲得判定
        if (player.collidesWith(coins[i])) {
            score += 10;
            scoreDisplay.textContent = `スコア: ${score}`;
            coins.splice(i, 1);
        }
    }
    
    frameCount++;
    requestAnimationFrame(gameLoop);
}
```

ポイント：
- `requestAnimationFrame`を使用したスムーズなアニメーション
- 障害物とコインの生成タイミングの制御
- 衝突判定とスコア計算の実装
- 画面外のオブジェクトの削除処理

### Step 7: イベント処理の実装

キーボードやタッチ操作のイベント処理を実装します。

```javascript
// イベントリスナー
window.addEventListener('load', () => {
    resizeCanvas();
    highScore = localStorage.getItem('highScore') || 0;
});

window.addEventListener('resize', resizeCanvas);

window.addEventListener('keydown', (e) => {
    keys[e.code] = true;
    
    if (e.code === 'Space' && gameRunning) {
        player.jump();
        e.preventDefault();
    }
});

window.addEventListener('keyup', (e) => {
    keys[e.code] = false;
});

startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);

// モバイルコントロール
leftBtn.addEventListener('touchstart', () => { keys.ArrowLeft = true; });
leftBtn.addEventListener('touchend', () => { keys.ArrowLeft = false; });
rightBtn.addEventListener('touchstart', () => { keys.ArrowRight = true; });
rightBtn.addEventListener('touchend', () => { keys.ArrowRight = false; });
jumpBtn.addEventListener('touchstart', () => { player.jump(); });
```

### Step 8: ゲーム開始・終了処理の実装

ゲームの開始と終了の処理を実装します。

```javascript
// ゲームオーバー処理
function gameOver() {
    gameRunning = false;
    
    if (score > highScore) {
        highScore = score;
        localStorage.setItem('highScore', highScore);
    }
    
    finalScore.textContent = `スコア: ${score}`;
    highScoreDisplay.textContent = `ハイスコア: ${highScore}`;
    gameOverScreen.style.display = 'flex';
}

// ゲーム開始処理
function startGame() {
    startScreen.style.display = 'none';
    gameOverScreen.style.display = 'none';
    initGame();
    gameRunning = true;
    gameLoop();
}
```

## 技術的なポイント解説 💡

### Canvas APIの活用

今回のゲームでは、HTML5のCanvas APIを活用しています。Canvasは2D描画に特化したAPIで、ゲーム開発に最適です。

```javascript
// キャンバスコンテキストの取得
const gameCtx = gameLayer.getContext('2d');

// 描画例
gameCtx.drawImage(images.player, this.x, this.y, this.width, this.height);
```

### 複数レイヤーの活用

パフォーマンス向上のため、3つのキャンバスレイヤーを使用しています：

1. **背景レイヤー**：背景の描画専用
2. **ゲームレイヤー**：プレイヤー、障害物、コインなどのゲームオブジェクト用
3. **UIレイヤー**：スコア表示などのUI要素用

これにより、毎フレームで全体を再描画する必要がなくなり、パフォーマンスが向上します。

### オブジェクト指向設計

ゲームの各要素をクラスとして設計することで、コードの可読性と保守性が向上します：

```javascript
Player（プレイヤー）
 ├── プロパティ：位置、速度、状態など
 └── メソッド：update(), draw(), jump(), collidesWith()

Obstacle（障害物）
 ├── プロパティ：位置、サイズなど
 └── メソッド：update(), draw(), isOffScreen()

Coin（コイン）
 ├── プロパティ：位置、サイズなど
 └── メソッド：update(), draw(), isOffScreen()

Background（背景）
 ├── プロパティ：位置、サイズなど
 └── メソッド：update(), draw()
```

### 衝突判定アルゴリズム

シンプルな矩形同士の衝突判定（AABB：Axis-Aligned Bounding Box）を実装しています：

```javascript
collidesWith(object) {
    return this.x  object.x &&
           this.y  object.y;
}
```

より精密な衝突判定が必要な場合は、円形の当たり判定や、ピクセル単位の衝突判定などを検討できます。

### レスポンシブデザイン

様々な画面サイズに対応するため、レスポンシブデザインを実装しています：

```javascript
function resizeCanvas() {
    const container = document.getElementById('game-container');
    const containerWidth = container.clientWidth;
    const containerHeight = container.clientHeight;
    
    // 論理サイズは固定
    bgLayer.width = gameLayer.width = uiLayer.width = GAME_WIDTH;
    bgLayer.height = gameLayer.height = uiLayer.height = GAME_HEIGHT;
    
    // 表示サイズはコンテナに合わせる
    bgLayer.style.width = gameLayer.style.width = uiLayer.style.width = `${containerWidth}px`;
    bgLayer.style.height = gameLayer.style.height = uiLayer.style.height = `${containerHeight}px`;
}
```

これにより、キャンバスの論理サイズ（ゲーム内の座標系）は固定したまま、表示サイズだけを画面に合わせて変更できます。

### ローカルストレージの活用

ハイスコアをブラウザのローカルストレージに保存することで、ページを閉じても記録が残ります：

```javascript
// ハイスコアの保存
localStorage.setItem('highScore', highScore);

// ハイスコアの読み込み
highScore = localStorage.getItem('highScore') || 0;
```

## パフォーマンス最適化のコツ 🚀

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

頻繁に生成・破棄されるオブジェクト（コインや障害物など）は、オブジェクトプーリングを使うとパフォーマンスが向上します：

```javascript
// オブジェクトプールの実装例
class ObjectPool {
    constructor(objectType, initialSize) {
        this.pool = [];
        this.objectType = objectType;
        
        // プールの初期化
        for (let i = 0; i  -objects[i].width && objects[i].x  { player.isInvincible = false; }, 5000);
                break;
            case 'doubleJump':
                player.canDoubleJump = true;
                break;
        }
    }
}
```

### 3. 敵キャラクターの追加

単なる障害物ではなく、動きのある敵キャラクターを追加することで、ゲーム性が向上します：

```javascript
class Enemy {
    constructor() {
        this.width = 50;
        this.height = 50;
        this.x = GAME_WIDTH;
        this.y = GAME_HEIGHT - this.height - 20;
        this.velocityY = 0;
        this.jumpTimer = 0;
    }
    
    update() {
        this.x -= SCROLL_SPEED;
        
        // ランダムにジャンプする敵
        this.jumpTimer++;
        if (this.jumpTimer > 120 && Math.random()  GAME_HEIGHT - this.height - 20) {
            this.y = GAME_HEIGHT - this.height - 20;
            this.velocityY = 0;
        }
    }
}
```

## まとめ 📝

今回は「ラク子さんの冒険」という横スクロールアクションゲームの作り方を解説しました。HTML5 Canvasとバニラ JavaScript だけで、本格的なゲームが作れることがお分かりいただけたと思います。

ゲーム開発では以下のポイントが重要です：

1. **適切なクラス設計**：オブジェクト指向的な設計で、コードの可読性と保守性を高める
2. **効率的な描画処理**：複数のキャンバスレイヤーを活用し、パフォーマンスを最適化
3. **ゲームループの実装**：`requestAnimationFrame`を使用した滑らかなアニメーション
4. **衝突判定の実装**：シンプルかつ効率的な衝突判定アルゴリズム
5. **レスポンシブ対応**：様々なデバイスで快適にプレイできるよう最適化

このブログの内容を応用すれば、さらに複雑なゲームも開発できるようになります。ぜひチャレンジしてみてください！


次回もお楽しみに！🎮✨

## 参考リンク

- [MDN Canvas API リファレンス](https://developer.mozilla.org/ja/docs/Web/API/Canvas_API)
- [HTML5ゲーム開発の基礎](https://developer.mozilla.org/ja/docs/Games/Introduction)
- [JavaScript ゲーム開発のベストプラクティス](https://developer.mozilla.org/ja/docs/Games/Techniques/Efficient_animation_for_web_games)



---

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

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

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

👉 [ポートフォリオ](https://yushiyamamoto.vercel.app/)

🌳 [らくらくサイト](https://rakuraku-site.prodouga.com/)