javascriptを使ったミニゲーム製作体験

目次

どんなゲームを作るか

今回作成するのは、簡単な迷路を使ったタイムトライアルゲームです。下記の画像のようなゲームを作成します。必要に応じてソースコード・デモページも参考にしてください。

ゲームの実装画面


このゲームを作成すると身につく知識

JavaScriptの基礎知識

JavaScriptはウェブ開発で広く使われるプログラミング言語です。ここでは、JavaScriptの基本的な構文と概念について説明します。

変数宣言

JavaScriptでは、変数を宣言する方法としてvarletconstがあります。letconstvarよりも新しい方法であり、ブロックスコープを持つため、より安全に使えます。ブロックスコープとは、中括弧 {}で囲まれたブロック内でのみ有効な変数です。

javascript
// varの例
var x = 10;
console.log(x); // 10

// letの例
let y = 20;
console.log(y); // 20

// constの例
const z = 30;
console.log(z); // 30
                

データ型

JavaScriptのデータ型とは、簡単に言えばデータの「種類」を示しています。データ型を理解すると、プログラムでどのようにデータが保存され、操作されるかがわかりやすくなります。
その中でも基本となる以下のデータ型について説明します。

文字列型(String)

テキストデータを表すためのデータ型です。文字列はダブルクォート " またはシングルクォート ' で囲まれます。

javascript
let text = "Hello, World!";

数値型(Number)

整数および浮動小数点数を表すためのデータ型です。JavaScriptでは整数と浮動小数点数は同じ数値型として扱われます。

javascript
let number = 42;
let decimal = 3.14;

ブール型(Boolean)

真(true)または偽(false)の2つの値を持つデータ型です。

javascript
let isTrue = true;
let isFalse = false;
                

特殊型(Undefined, Null)

Undefined は変数が定義されていないことを示し、Null は変数に何も値が設定されていないことを示します。

2つ使い分け例

undefinedを使用する場面

nullを使用する場面


javascript
let undefinedValue;
let nullValue = null;

オブジェクト型(Object, Array, Function)

複雑なデータ構造を表すためのデータ型です。オブジェクト、配列、関数が含まれます。

javascript
let obj = { name: "Alice", age: 25 };
let array = [1, 2, 3, 4, 5];
let func = function() { console.log("Hello!"); };
                

演算子

javascriptの演算子について紹介します。

javascript
// 算術演算子
let sum = 10 + 5; // 15
let difference = 10 - 5; // 5
let product = 10 * 5; // 50
let quotient = 10 / 5; // 2
let remainder = 10 % 3; // 1 (10を3で割った余り)

// 比較演算子
let isEqual = (10 == 10); // true
let isStrictEqual = (10 === "10"); // false (型が異なるため)
let isNotEqual = (10 != 5); // true
let isGreaterThan = (10 > 5); // true
let isLessThanEqual = (10 <= 10); // true

// 論理演算子
let logicalAnd = (true && false); // false
let logicalOr = (true || false); // true
let logicalNot = !true; // false
                

制御構文

JavaScriptでは、コードの実行を制御するために、条件分岐やループが使用されます。ここからは制御構文について紹介していきます。

条件分岐(if文)

条件分岐(if文)は、特定の条件が真(true)であるかどうかを確認し、条件に応じて異なるコードブロックを実行します。

javascript
if (条件式) {
    // 条件が真の場合に実行されるコード
} else if (別の条件式) {
    // 別の条件が真の場合に実行されるコード
} else {
    // どの条件も満たさない場合に実行されるデフォルトのコード
}

ループ(forループ)

forループは特定の条件の間、コードブロックを繰り返し実行します。主に配列やオブジェクトの要素やプロパティに対して使用されます。

javascript
// 標準的なforループ
for (初期化式; 条件式; 更新式) {
    // 条件式が真の間、繰り返し実行されるコード
}

// for...ofループ(配列の各要素に対して使用)
for (要素 of 配列) {
    // 配列の各要素に対して繰り返し実行されるコード
}

// for...inループ(オブジェクトのプロパティに対して使用)
for (プロパティ in オブジェクト) {
    // オブジェクトのプロパティごとに繰り返し実行されるコード
}
            

関数宣言

JavaScriptでは、以下のように異なる方法で関数を定義することができます。それぞれの方法には特有の特徴があります。

宣言関数(Function Declaration)

宣言関数は、function キーワードを使って定義します。関数名を指定し、その関数が必要とする引数を丸括弧の中に書きます。関数が実行されたときに行う処理は波括弧の中に書きます。

javascript
function 関数名(引数1, 引数2, ...) { // 関数が実行されたときに行う処理 } 
            

関数式(Function Expression)

関数式は、関数を変数に代入する形式で定義します。関数名は省略することもできます。関数が実行されたときに行う処理は波括弧の中に書きます。

javascript
const 変数名 = function(引数1, 引数2, ...) { // 関数が実行されたときに行う処理 };

アロー関数(Arrow Function)

アロー関数は、関数をより簡潔に定義するための方法です。=> を使って関数を定義します。引数が1つの場合は丸括弧を省略できます。関数が実行されたときに行う処理は波括弧の中に書きます。

javascript
const 変数名 = (引数1, 引数2, ...) => { // 関数が実行されたときに行う処理 };

ゲームの作り方

ファイルの作成

今回はHTML、CSS、javascriptを使用するので3つのファイルを作成します。ファイル名はバグ防止のため同じものにしてください。

  • HTML用ファイル:index.html
  • javascript用ファイル:game.js
  • CSS用ファイル:style.css
  • 画像の保存

    次にゲームで使用する画像を保存してきます。このゲームで使用するのは4人分のキャラクターと3人分の荷物のイラストです。今回イラストはピクセルガロー様でお借りしました。

    これらの画像はこのページの冒頭のソースコードのページからダウンロード出来ます。

    HTML&CSSファイルの設定

    これらのファイルを用意出来たら、index.htmlとstyle.cssにそれぞれ下記のコードを貼り付けます。今回はjavascriptの勉強が目的ですので、ここではHTML、CSSについての解説はしていません。気になる人は各自調べてみてください。

    index.html
    
    <!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>迷路ゲーム</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>        
        <div class="explain">
            <h1>迷路ゲーム</h1>
            <h2>ルール説明</h2>
            <p>左上のキャラクターを使って、なるべく早く迷路内の落し物持ち主に届けて、1番左上のマスに戻ってくるタイムを競うゲームである。</p>
            <ul>
                <li>荷物は一度に一つしか持てません。</li>
                <li>黄色い場所が地面で緑の場所が壁(障害物)です。</li>
                <li>荷物を渡すときは、正しい持ち主の周りのマスを踏むと渡せるようになっています。</li>
            </ul>
        </div>
        <div id="container">
            <div id="maze-container"></div>
            <div id="log-container">ここにはゲームのログが表示されるよ!</div>
        </div>
    
        <script src="game.js"></script>
    </body>
    </html>                    
                        
                    

    style.css
    
    body {
        display: flex;
        flex-direction: column;
        padding-left: 5%;
    }
    
    .explain{
        padding-right: 45%;
    }
    
    #maze-container {
        flex: 1;
        display: grid;
        grid-template-columns: repeat(20, 30px);
        grid-template-rows: repeat(11, 30px);
        gap: 2px;
    }
    
    .cell {
        width: 30px;
        height: 30px;
        border: 1px solid #ddd;
        display: flex;
        align-items: center;
        justify-content: center;
        background-size: cover;
        background-position: center;
    }
    
    #container {
        display: flex;
    }
    
    .player {
        background-color: #0f0;
        background-image: url('player.gif');
    }
    
    .player-left {
        background-image: url('player-left.gif');
    }
    
    .player-right {
        background-image: url('player-right.gif');
    }
    
    .customer {
        background-color: #00f;
    }
    
    .package {
        background-color: #f00;
    }
    
    .wall {
        background-color: #006036;
    }
    
    .path {
        background-color: #D0A900;
    }
    
    @import url('https://fonts.googleapis.com/css2?family=DotGothic16&display=swap');
    
    #log-container {
        flex: 1;
        margin-top: 20px;
        margin-left: 20px;
        border: 5px solid rgb(0, 117, 226);
        padding: 10px;
        width: 300px;
        height: 250px;
        overflow-y: auto;
        background-color: #000;
        color: #ddd;
        font-family: "DotGothic16", sans-serif;
        font-weight: 400;
        font-style: normal;
    }
            
                    

    これらのコードを貼り付けられたら一度、index.htmlを開いて下記の画像のような画面ができているかを確認してください。確認が取れたら次の作業に移ってください。

    javascript抜きの画面

    変数の設定

    このゲームで使う変数は主に迷路に関するもの、キャラクターに関するもの、荷物に関するもの、ログに関するものの4つに分けられます。これらの変数を今から順に作成していきます。

    maze

    2次元配列で、迷路の構造を定義しています。0は通路、1は壁を表します。

    game.js-maze
    const maze = [
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1],
                [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1],
                [0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1],
                [0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                [0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
                [1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
                [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
                [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
                [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1],
                [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1]
                ];

    mazeContainer

    HTMLの迷路を表示するための要素を保持する変数です。

    「要素を保持する」 とは、簡単に説明するとウェブページ上での場所取りです。HTMLのコード内で表示する場所を確保しておくことで、JavaScriptからその部分を操作したり、内容を変更したりすることが可能になります。もう少し具体的に説明すると、HTMLの要素はウェブページ上の構造を定義するために使用されます。これにより、 JavaScriptは特定のHTML要素を操作して、内容を動的に変更したり、スタイルを変更したり、イベントを処理したりすることが可能 になります。

    game.js-mazeContainer
    const mazeContainer = document.getElementById('maze-container');

    logContainer

    ゲームのログメッセージを表示するための要素を保持する変数です。

    game.js-logoContainer
    const logContainer = document.getElementById('log-container');

    player

    プレイヤーの現在位置を保持するオブジェクトです。初期位置は迷路の左上に設定されています。

    オブジェクトとは、複数の関連するデータを一つにまとめたものです。playerはプレイヤーの位置を示すオブジェクトで、X座標とY座標を含んでいます。

    game.js-player
    let player = { x: 1, y: 1 };

    customers

    配達先の顧客の位置と名前、画像を保持する配列です。

    配列とは、複数のデータを順序付けて保持するためのデータ構造です。customersは顧客の位置、名前、画像を含むオブジェクトの配列です。各オブジェクトは、{ x: x座標, y: y座標, name: "役職", img: '画像の名前' }の形式です。

    game.js-customers
    const customers = [
                    { x: 19, y: 0, name: '社長', img: 'syatyou.gif' },
                    { x: 0, y: 10, name: '職人', img: 'syokunin.gif' },
                    { x: 19, y: 10, name: '料理人', img: 'ryourinin.gif' }
                ];

    packages

    配達する荷物の位置と名前、画像を保持する配列です。

    game.js-packages
    let packages = [
                    { x: 5, y: 5, name: '社長', img: 'syatyou1.png' },
                    { x: 10, y: 5, name: '職人', img: 'syokunin1.png' },
                    { x: 15, y: 5, name: '料理人', img: 'ryourinin1.png' }
                ];

    currentPackage

    プレイヤーが現在運んでいる荷物を保持する変数です。荷物を拾ったときに設定されます。

    game.js-currentPackage
    let currentPackage = null;

    startTime

    ゲームの開始時間を保持する変数です。最初のキーが押されたときに設定されます。

    game.js-startTime
    let startTime = null;

    deliveredPackages

    プレイヤーが配達した荷物の数をカウントする変数です。

    game.js-deliveredPackages
    let deliveredPackages = 0;

    playerDirection

    プレイヤーの現在の移動方向を保持する変数です。初期値は'left'です。移動時にキャラクターの向きを変更するために使います。

    game.js-playerDirection
    let playerDirection = 'left';

    startPosition

    ゲームのスタート地点を保持するオブジェクトです。startPositionオブジェクトには、迷路内の水平方向の位置を示すx と、垂直方向の位置を示すy の2つのプロパティがあります。初期値は(x = 0, y = 0)です。

    game.js-startPosition
    const startPosition = { x: 0, y: 0 };

    関数の設定

    createMaze

    迷路を生成する関数です。迷路の各マスをHTML要素として作成し、mazeContainerに追加します。迷路のマスは、通路(path)または壁(wall)としてクラスが設定されます。このクラスにより、CSSでスタイルでスタイルを設定できるようになります。

    game.js-createMaze
    function createMaze() {
                    for (let y = 0; y < maze.length; y++) {
                        for (let x = 0; x < maze[y].length; x++) {
                            const cell = document.createElement('div');
                            cell.classList.add('cell');
                            if (maze[y][x] === 1) {
                                cell.classList.add('wall');
                            } else {
                                cell.classList.add('path');
                            }
                            mazeContainer.appendChild(cell);
                        }
                    }
                }

    placePlayer

    プレイヤーの位置に対応するマスに初期アイコンを表示させる関数です。プレイヤーの初期位置に対応するHTML要素にplayerクラスを設定し、CSSでプレイヤーの初期アイコンを表示します。

    game.js-placePlayer
    function placePlayer() {
                    const playerCell = getCell(player.x, player.y);
                    playerCell.classList.add('player');
                }

    placeCustomers

    顧客の位置に対応するマスに画像を表示させる関数です。各顧客の位置に対応するHTML要素にcustomerクラスを追加し、style.backgroundImageプロパティで顧客の画像を設定します。

    game.js-placeCustomers
    function placeCustomers() {
                    customers.forEach(customer => {
                        const customerCell = getCell(customer.x, customer.y);
                        customerCell.classList.add('customer');
                        customerCell.style.backgroundImage = `url(${customer.img})`;
                    });
                }

    placePackages

    荷物の位置に対応するマスに画像を設定する関数です。各荷物の位置に対応するHTML要素にpackageクラスを追加し、style.backgroundImageプロパティで荷物の画像を設定します。

    game.js-placePackages
    function placePackages() {
                    packages.forEach(pkg => {
                        if (pkg) { // nullでない場合にのみ表示
                            const packageCell = getCell(pkg.x, pkg.y);
                            packageCell.classList.add('package');
                            packageCell.style.backgroundImage = `url(${pkg.img})`;
                        }
                    });
                }

    getCell

    この関数は、迷路の特定の位置にある座標(xy)を使って、その位置に対応するマスを取得するものです。迷路はグリッド(格子)状になっていてHTMLの < div >タグを使って表現されています。mazeContainerの中にあるこれらの<div>タグを「子要素」と呼びます。これはプレイヤーが移動したときに、プレイヤーの表示を更新するときに使います。

    子要素とは HTMLの中である要素(親要素)の中に含まれている別の要素のこと です。ここでは、mazeContainerが親要素で、その中にある個々のマス(<div>タグ)が子要素です。

    game.js-getCell
    function getCell(x, y) {
                    return mazeContainer.children[y * 20 + x];
                }

    logMessage

    ログメッセージを表示する関数です。div要素を作成してメッセージを設定し、logContainerに追加します。最後に、scrollTopプロパティでスクロール位置を更新します。

    game.js-logMessage
    function logMessage(message) {
                    const logEntry = document.createElement('div');
                    logEntry.textContent = message;
                    logContainer.appendChild(logEntry);
                    logContainer.scrollTop = logContainer.scrollHeight;
                }

    handleKeyPress

    キー入力に応じてプレイヤーを移動させる関数です。移動先の座標が通路であれば、プレイヤーを移動させ、荷物や顧客とのインタラクションをチェックします。

    インタラクションとは、プレイヤーが荷物を拾ったり顧客に渡したりすることです。

    game.js-handleKeyPress
    function handleKeyPress(event) {
                    if (!startTime) {
                        startTime = new Date();
                    }
                
                    const { key } = event;
                    let newX = player.x;
                    let newY = player.y;
                    let direction = playerDirection;
                
                    if (key === 'ArrowUp') {
                        newY--;
                    }
                    if (key === 'ArrowDown') {
                        newY++;
                    }
                    if (key === 'ArrowLeft') {
                        newX--;
                        direction = 'left';
                    }
                    if (key === 'ArrowRight') {
                        newX++;
                        direction = 'right';
                    }
                
                    if (newX >= 0 && newX < 20 && newY >= 0 && newY < 11 && maze[newY][newX] === 0) {
                        movePlayer(newX, newY, direction);
                        checkForPackage();
                        checkForCustomer();
                        checkForCompletion();
                    }
                }

    movePlayer

    プレイヤーの位置と方向を更新する関数です。現在のプレイヤーの位置からplayerクラスと方向クラスを削除し、新しい位置にプレイヤーのクラスを追加します。そして、playerDirectionを更新してプレイヤーのアイコンを更新しアイコンの向きを変えます。

    game.js-movePlayer
    function movePlayer(newX, newY, direction) {
                    getCell(player.x, player.y).classList.remove('player', `player-${playerDirection}`);
                    player.x = newX;
                    player.y = newY;
                    playerDirection = direction;
                    placePlayer();
                    updatePlayerIcon();
                }

    checkForPackage

    プレイヤーが荷物を拾ったかどうかをチェックする関数です。荷物を拾った場合、その荷物をリストから削除し、対応するマスから荷物のクラスpackageを削除します。これにより迷路上からアイコンが消え、荷物を拾ったことがわかるようにログメッセージを更新します。

    game.js-checkForPackage
    function checkForPackage() {
                    packages.forEach((pkg, index) => {
                        if (pkg && pkg.x === player.x && pkg.y === player.y) {
                            packages[index] = null;
                            getCell(pkg.x, pkg.y).classList.remove('package');
                            logMessage('荷物を拾いました。');
                        }
                    });
                }

    checkForCustomer

    プレイヤーが顧客に荷物を届けたかどうかをチェックする関数です。顧客に荷物を届けた場合、対応するマスから顧客のクラスを削除し、ログメッセージを更新します。

    game.js-checkForCustomer
    function checkForCustomer() {
        if (!currentPackage) return;
    
        const customer = customers.find(cust =>
            Math.abs(cust.x - player.x) <= 1 && Math.abs(cust.y - player.y) <= 1
        );
    
        if (customer) {
            if (customer.name === currentPackage.name) {
                logMessage(`${customer.name}に${currentPackage.name}の荷物を渡した!`);
                currentPackage = null;
                deliveredPackages++;
                
                if (deliveredPackages === customers.length) {
                    logMessage('全ての荷物を配達完了!スタート地点に戻ってください。');
                }
            } else {
                logMessage(`${customer.name}には${currentPackage.name}の荷物は渡せない!`);
            }
        }
    }

    checkForCompletion

    ゲームの完了条件をチェックする関数です。全ての荷物が配達されたかどうかを確認し、完了していればログメッセージを表示します。

    game.js-checkForCompletion
    function checkForCompletion() {
        if (player.x === startPosition.x && player.y === startPosition.y) {
            if (deliveredPackages === customers.length) {
                const endTime = new Date();
                const timeTaken = (endTime - startTime) / 1000;
                logMessage(`ゲームクリア!かかった時間: ${timeTaken}秒`);
            } else {
                logMessage('まだ荷物を運びきれていないよ!!');
            }
        }
    }

    updatePlayerIcon

    プレイヤーのアイコンが向いている方向を管理する関数です。プレイヤーの方向に応じたクラスを追加し管理ます。

    game.js-updatePlayerIcon
    function updatePlayerIcon() {
                    const playerCell = getCell(player.x, player.y);
                    playerCell.classList.add(`player-${playerDirection}`);
                }

    実行処理

    ここまで変数や関数を定義してきましたが、定義するだけではプログラムは動きません。JavaScriptはコードを上から順に実行するため、あらかじめ必要なものを定義した後に実行処理を記述しなければなりません。多くの変数や関数を設定しましたが、関数の中で別の関数を実行している場合もありすべてを個別に実行する必要はないです。それも含めて実行コードはこのような形になります。

    game.js
        // キー押下時にプレイヤーを移動させる
        document.addEventListener('keydown', handleKeyPress);
        
        // 迷路を生成する
        createMaze();
        
        // プレイヤーを初期位置に配置する
        placePlayer();
        
        // 顧客を迷路内に配置する
        placeCustomers();
        
        // 荷物を迷路内に配置する
        placePackages();

    まとめ

    このチュートリアルを通じて、JavaScriptの基礎知識を学びながら簡単な迷路ゲームを作成しました。ここで、学んだ内容を振り返ってみましょう。

    学んだこと

    • 変数宣言:データを保存し、操作するための方法を学びました。
    • データ型:文字列、数値、配列などの異なるデータ型について理解しました。
    • 演算子:算術演算子や論理演算子を用いて計算や条件判断を行いました。
    • 制御構文:if文やforループを使い、プログラムの流れを制御しました。
    • 関数宣言:関数を定義して、再利用可能なコードを作成しました。

    今回はJavaScriptを使ってゲームを作成しましたが、JavaScriptでは他にも様々なことができます。例えば、動的なウェブサイトの作成リアルタイムのチャットアプリの開発などの作成ができます。是非チャレンジしてみてください。

    本コンテンツの作成時間(HTML/CSS/JavaScriptの設計・実装を含む):約20時間