今回作成するのは、簡単な迷路を使ったタイムトライアルゲームです。下記の画像のようなゲームを作成します。必要に応じてソースコード・デモページも参考にしてください。
JavaScriptはウェブ開発で広く使われるプログラミング言語です。ここでは、JavaScriptの基本的な構文と概念について説明します。
JavaScriptでは、変数を宣言する方法としてvar、let、constがあります。letとconstはvarよりも新しい方法であり、ブロックスコープを持つため、より安全に使えます。ブロックスコープとは、中括弧 {}で囲まれたブロック内でのみ有効な変数です。
// 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のデータ型とは、簡単に言えばデータの「種類」を示しています。データ型を理解すると、プログラムでどのようにデータが保存され、操作されるかがわかりやすくなります。
その中でも基本となる以下のデータ型について説明します。
テキストデータを表すためのデータ型です。文字列はダブルクォート " またはシングルクォート ' で囲まれます。
let text = "Hello, World!";
整数および浮動小数点数を表すためのデータ型です。JavaScriptでは整数と浮動小数点数は同じ数値型として扱われます。
let number = 42;
let decimal = 3.14;
真(true)または偽(false)の2つの値を持つデータ型です。
let isTrue = true;
let isFalse = false;
Undefined は変数が定義されていないことを示し、Null は変数に何も値が設定されていないことを示します。
undefinedを使用する場面
nullを使用する場面
let undefinedValue;
let nullValue = null;
複雑なデータ構造を表すためのデータ型です。オブジェクト、配列、関数が含まれます。
let obj = { name: "Alice", age: 25 };
let array = [1, 2, 3, 4, 5];
let func = function() { console.log("Hello!"); };
javascriptの演算子について紹介します。
+(加算)、-(減算)、*(乗算)、/(除算)、%(剰余)==(等価)、===(厳密等価)、!=(不等価)、!==(厳密不等価)、>(より大きい)、<(より小さい)、>=(以上)、<=(以下)&&(論理積、AND)、||(論理和、OR)、!(論理否定、NOT)// 算術演算子
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文)は、特定の条件が真(true)であるかどうかを確認し、条件に応じて異なるコードブロックを実行します。
if (条件式) {
// 条件が真の場合に実行されるコード
} else if (別の条件式) {
// 別の条件が真の場合に実行されるコード
} else {
// どの条件も満たさない場合に実行されるデフォルトのコード
}
forループは特定の条件の間、コードブロックを繰り返し実行します。主に配列やオブジェクトの要素やプロパティに対して使用されます。
// 標準的なforループ
for (初期化式; 条件式; 更新式) {
// 条件式が真の間、繰り返し実行されるコード
}
// for...ofループ(配列の各要素に対して使用)
for (要素 of 配列) {
// 配列の各要素に対して繰り返し実行されるコード
}
// for...inループ(オブジェクトのプロパティに対して使用)
for (プロパティ in オブジェクト) {
// オブジェクトのプロパティごとに繰り返し実行されるコード
}
JavaScriptでは、以下のように異なる方法で関数を定義することができます。それぞれの方法には特有の特徴があります。
宣言関数は、function キーワードを使って定義します。関数名を指定し、その関数が必要とする引数を丸括弧の中に書きます。関数が実行されたときに行う処理は波括弧の中に書きます。
function 関数名(引数1, 引数2, ...) { // 関数が実行されたときに行う処理 }
関数式は、関数を変数に代入する形式で定義します。関数名は省略することもできます。関数が実行されたときに行う処理は波括弧の中に書きます。
const 変数名 = function(引数1, 引数2, ...) { // 関数が実行されたときに行う処理 };
アロー関数は、関数をより簡潔に定義するための方法です。=> を使って関数を定義します。引数が1つの場合は丸括弧を省略できます。関数が実行されたときに行う処理は波括弧の中に書きます。
const 変数名 = (引数1, 引数2, ...) => { // 関数が実行されたときに行う処理 };
今回はHTML、CSS、javascriptを使用するので3つのファイルを作成します。ファイル名はバグ防止のため同じものにしてください。
次にゲームで使用する画像を保存してきます。このゲームで使用するのは4人分のキャラクターと3人分の荷物のイラストです。今回イラストはピクセルガロー様でお借りしました。
これらのファイルを用意出来たら、index.htmlとstyle.cssにそれぞれ下記のコードを貼り付けます。今回はjavascriptの勉強が目的ですので、ここではHTML、CSSについての解説はしていません。気になる人は各自調べてみてください。
<!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>
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を開いて下記の画像のような画面ができているかを確認してください。確認が取れたら次の作業に移ってください。
このゲームで使う変数は主に迷路に関するもの、キャラクターに関するもの、荷物に関するもの、ログに関するものの4つに分けられます。これらの変数を今から順に作成していきます。
2次元配列で、迷路の構造を定義しています。0は通路、1は壁を表します。
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]
];
HTMLの迷路を表示するための要素を保持する変数です。
const mazeContainer = document.getElementById('maze-container');
ゲームのログメッセージを表示するための要素を保持する変数です。
const logContainer = document.getElementById('log-container');
プレイヤーの現在位置を保持するオブジェクトです。初期位置は迷路の左上に設定されています。
playerはプレイヤーの位置を示すオブジェクトで、X座標とY座標を含んでいます。
let player = { x: 1, y: 1 };
配達先の顧客の位置と名前、画像を保持する配列です。
customersは顧客の位置、名前、画像を含むオブジェクトの配列です。各オブジェクトは、{ x: x座標, y: y座標, name: "役職", img: '画像の名前' }の形式です。
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' }
];
配達する荷物の位置と名前、画像を保持する配列です。
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' }
];
プレイヤーが現在運んでいる荷物を保持する変数です。荷物を拾ったときに設定されます。
let currentPackage = null;
ゲームの開始時間を保持する変数です。最初のキーが押されたときに設定されます。
let startTime = null;
プレイヤーが配達した荷物の数をカウントする変数です。
let deliveredPackages = 0;
プレイヤーの現在の移動方向を保持する変数です。初期値は'left'です。移動時にキャラクターの向きを変更するために使います。
let playerDirection = 'left';
ゲームのスタート地点を保持するオブジェクトです。startPositionオブジェクトには、迷路内の水平方向の位置を示すx と、垂直方向の位置を示すy の2つのプロパティがあります。初期値は(x = 0, y = 0)です。
const startPosition = { x: 0, y: 0 };
迷路を生成する関数です。迷路の各マスをHTML要素として作成し、mazeContainerに追加します。迷路のマスは、通路(path)または壁(wall)としてクラスが設定されます。このクラスにより、CSSでスタイルでスタイルを設定できるようになります。
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);
}
}
}
プレイヤーの位置に対応するマスに初期アイコンを表示させる関数です。プレイヤーの初期位置に対応するHTML要素にplayerクラスを設定し、CSSでプレイヤーの初期アイコンを表示します。
function placePlayer() {
const playerCell = getCell(player.x, player.y);
playerCell.classList.add('player');
}
顧客の位置に対応するマスに画像を表示させる関数です。各顧客の位置に対応するHTML要素にcustomerクラスを追加し、style.backgroundImageプロパティで顧客の画像を設定します。
function placeCustomers() {
customers.forEach(customer => {
const customerCell = getCell(customer.x, customer.y);
customerCell.classList.add('customer');
customerCell.style.backgroundImage = `url(${customer.img})`;
});
}
荷物の位置に対応するマスに画像を設定する関数です。各荷物の位置に対応するHTML要素にpackageクラスを追加し、style.backgroundImageプロパティで荷物の画像を設定します。
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})`;
}
});
}
この関数は、迷路の特定の位置にある座標(xとy)を使って、その位置に対応するマスを取得するものです。迷路はグリッド(格子)状になっていてHTMLの < div >タグを使って表現されています。mazeContainerの中にあるこれらの<div>タグを「子要素」と呼びます。これはプレイヤーが移動したときに、プレイヤーの表示を更新するときに使います。
mazeContainerが親要素で、その中にある個々のマス(<div>タグ)が子要素です。
function getCell(x, y) {
return mazeContainer.children[y * 20 + x];
}
ログメッセージを表示する関数です。div要素を作成してメッセージを設定し、logContainerに追加します。最後に、scrollTopプロパティでスクロール位置を更新します。
function logMessage(message) {
const logEntry = document.createElement('div');
logEntry.textContent = message;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
キー入力に応じてプレイヤーを移動させる関数です。移動先の座標が通路であれば、プレイヤーを移動させ、荷物や顧客とのインタラクションをチェックします。
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();
}
}
プレイヤーの位置と方向を更新する関数です。現在のプレイヤーの位置からplayerクラスと方向クラスを削除し、新しい位置にプレイヤーのクラスを追加します。そして、playerDirectionを更新してプレイヤーのアイコンを更新しアイコンの向きを変えます。
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();
}
プレイヤーが荷物を拾ったかどうかをチェックする関数です。荷物を拾った場合、その荷物をリストから削除し、対応するマスから荷物のクラスpackageを削除します。これにより迷路上からアイコンが消え、荷物を拾ったことがわかるようにログメッセージを更新します。
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('荷物を拾いました。');
}
});
}
プレイヤーが顧客に荷物を届けたかどうかをチェックする関数です。顧客に荷物を届けた場合、対応するマスから顧客のクラスを削除し、ログメッセージを更新します。
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}の荷物は渡せない!`);
}
}
}
ゲームの完了条件をチェックする関数です。全ての荷物が配達されたかどうかを確認し、完了していればログメッセージを表示します。
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('まだ荷物を運びきれていないよ!!');
}
}
}
プレイヤーのアイコンが向いている方向を管理する関数です。プレイヤーの方向に応じたクラスを追加し管理ます。
function updatePlayerIcon() {
const playerCell = getCell(player.x, player.y);
playerCell.classList.add(`player-${playerDirection}`);
}
ここまで変数や関数を定義してきましたが、定義するだけではプログラムは動きません。JavaScriptはコードを上から順に実行するため、あらかじめ必要なものを定義した後に実行処理を記述しなければなりません。多くの変数や関数を設定しましたが、関数の中で別の関数を実行している場合もありすべてを個別に実行する必要はないです。それも含めて実行コードはこのような形になります。
// キー押下時にプレイヤーを移動させる
document.addEventListener('keydown', handleKeyPress);
// 迷路を生成する
createMaze();
// プレイヤーを初期位置に配置する
placePlayer();
// 顧客を迷路内に配置する
placeCustomers();
// 荷物を迷路内に配置する
placePackages();
このチュートリアルを通じて、JavaScriptの基礎知識を学びながら簡単な迷路ゲームを作成しました。ここで、学んだ内容を振り返ってみましょう。
今回はJavaScriptを使ってゲームを作成しましたが、JavaScriptでは他にも様々なことができます。例えば、動的なウェブサイトの作成やリアルタイムのチャットアプリの開発などの作成ができます。是非チャレンジしてみてください。
本コンテンツの作成時間(HTML/CSS/JavaScriptの設計・実装を含む):約20時間