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?

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

Posted at

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

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

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

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

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

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

See the Pen ラク子さんの冒険 by Yushi Yamamoto (@yamamotoyushi) on CodePen.

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

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

開発環境と使用技術 🛠️

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

  • HTML5(Canvas API)
  • CSS3
  • バニラJavaScript(フレームワークなし)

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

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

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

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

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

実装手順:ステップバイステップ 👣

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

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

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

        * { 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でゲームの基本設定とクラス設計を行います。

// ゲームの設定
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: プレイヤークラスの実装

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

// プレイヤークラス
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: 障害物とコインのクラス実装

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

// 障害物クラス
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: イベント処理の実装

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

// イベントリスナー
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: ゲーム開始・終了処理の実装

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

// ゲームオーバー処理
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で、ゲーム開発に最適です。

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

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

複数レイヤーの活用

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

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

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

オブジェクト指向設計

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

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

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

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

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

衝突判定アルゴリズム

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

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

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

レスポンシブデザイン

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

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`;
}

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

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

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

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

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

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

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

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

// オブジェクトプールの実装例
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. 敵キャラクターの追加

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

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. レスポンシブ対応:様々なデバイスで快適にプレイできるよう最適化

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

次回もお楽しみに!🎮✨

参考リンク


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

私は、業務委託エンジニアとして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?