「プログラミングでゲームを作ってみたい!」 そう思ったとき、最初に挑戦するジャンルとしておすすめなのがシューティングゲームです。
本記事では、これまで数回に分けて解説していたシューティングゲームの作成手順を1つの記事に統合・完全リニューアルしました。 特別なゲームエンジン(Unityなど)を使わず、Web標準の技術(HTML / CSS / JavaScript)だけで動作するため、ブラウザさえあれば誰でもすぐに開発を始められます。
現役エンジニアの視点から、単なるコードのコピペではなく「なぜこの処理が必要なのか?」という仕組みの部分まで丁寧に解説します。
1. 開発環境と全体の構成
今回作成するゲームの構成は非常にシンプルです。
- HTML: ゲーム画面の土台を作ります。
- CSS: 自機や敵、弾のデザイン(見た目)を決めます。
- JavaScript: 「動く」「当たる」「スコアが増える」といったゲームのロジックを担当します。
フォルダ構成
PCの適当な場所にフォルダを作り、以下のファイルを作成してください。
index.htmlstyle.cssscript.js
完成映像
2. HTML:ゲーム画面の構築
まずはゲームの表示領域(キャンバス)を作成します。
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="game-container">
<h1>Shooting Game</h1>
<p>スコア: <span id="score">0</span></p>
<canvas id="gameCanvas" width="400" height="600"></canvas>
<p>操作方法: ←→キーで移動、スペースキーで発射</p>
</div>
<script src="script.js"></script>
</body>
</html>
解説:
<canvas>タグは、JavaScriptを使って図形や画像を描画するための専用エリアです。今回はここにゲームの世界を描き出します。
3. CSS:デザインの適用
ゲーム画面が見やすくなるよう、スタイルを調整します。
CSS style.css
body {
background-color: #222;
color: #fff;
text-align: center;
font-family: 'Arial', sans-serif;
}
.game-container {
margin-top: 20px;
}
canvas {
background-color: #000; /* 宇宙空間をイメージした黒背景 */
border: 2px solid #444;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.2);
display: block;
margin: 0 auto;
}
4. JavaScript:ゲームロジックの実装
ここが開発のメインパートです。以下の機能を実装します。
- 自機の描画と移動
- 弾の発射処理
- 敵の出現と移動
- 当たり判定(衝突検知)
少し長いですが、コピペして動作を確認してみてください。
JavaScript script.js
// script.js
// キャンバスの取得
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// ゲームの状態変数
let score = 0;
let isGameOver = false;
// 自機の設定
const player = {
x: canvas.width / 2 - 15,
y: canvas.height - 50,
width: 30,
height: 30,
color: '#00f0ff',
speed: 5
};
// 弾と敵の管理配列
let bullets = [];
let enemies = [];
// キー入力の監視
let keys = {};
document.addEventListener('keydown', (e) => {
// ゲームオーバー時にスペースキーでリスタート
if (isGameOver && e.code === 'Space') {
resetGame();
return;
}
keys[e.code] = true;
});
document.addEventListener('keyup', (e) => keys[e.code] = false);
// ■ ゲームのリセット(再スタート)処理
function resetGame() {
score = 0;
isGameOver = false;
bullets = [];
enemies = [];
player.x = canvas.width / 2 - 15;
player.y = canvas.height - 50;
// スコア表示もリセット
document.getElementById('score').innerText = score;
// ゲームループ再開
gameLoop();
}
// ■ 1. 自機の描画
function drawPlayer() {
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
}
// ■ 2. 自機の移動
function updatePlayer() {
if (isGameOver) return; // ゲームオーバー中は動かせない
if (keys['ArrowLeft'] && player.x > 0) {
player.x -= player.speed;
}
if (keys['ArrowRight'] && player.x < canvas.width - player.width) {
player.x += player.speed;
}
// スペースキーで弾発射
if (keys['Space']) {
shootBullet();
keys['Space'] = false; // 連射防止
}
}
// ■ 3. 弾の生成と移動
function shootBullet() {
bullets.push({
x: player.x + player.width / 2 - 2.5,
y: player.y,
width: 5,
height: 10,
color: '#ff0',
speed: 7
});
}
function updateBullets() {
bullets.forEach((bullet, index) => {
bullet.y -= bullet.speed;
ctx.fillStyle = bullet.color;
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
if (bullet.y < 0) bullets.splice(index, 1);
});
}
// ■ 4. 敵の生成と移動
function spawnEnemy() {
if (Math.random() < 0.02) {
enemies.push({
x: Math.random() * (canvas.width - 30),
y: 0,
width: 30,
height: 30,
color: '#ff0055',
speed: 2 + Math.random() * 2
});
}
}
function updateEnemies() {
enemies.forEach((enemy, eIndex) => {
enemy.y += enemy.speed;
ctx.fillStyle = enemy.color;
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
if (enemy.y > canvas.height) {
isGameOver = true;
}
// 当たり判定
bullets.forEach((bullet, bIndex) => {
if (
bullet.x < enemy.x + enemy.width &&
bullet.x + bullet.width > enemy.x &&
bullet.y < enemy.y + enemy.height &&
bullet.y + bullet.height > enemy.y
) {
enemies.splice(eIndex, 1);
bullets.splice(bIndex, 1);
score += 100;
document.getElementById('score').innerText = score;
}
});
});
}
// ■ メインループ
function gameLoop() {
if (isGameOver) {
// ゲームオーバー画面の描画
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '30px Arial';
ctx.textAlign = 'center'; // 文字を中央揃えに
ctx.fillText("GAME OVER", canvas.width / 2, canvas.height / 2);
ctx.font = '16px Arial';
ctx.fillText("Press Space to Restart", canvas.width / 2, canvas.height / 2 + 40);
return; // ここでループを止める
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
updatePlayer();
drawPlayer();
updateBullets();
spawnEnemy();
updateEnemies();
requestAnimationFrame(gameLoop);
}
// ゲーム開始
gameLoop();
5. 技術的なポイント解説
ただ動かすだけでなく、仕組みを理解することが重要です。
① なぜ requestAnimationFrame を使うのか?
ゲームは「パラパラ漫画」と同じ仕組みで動いています。 かつては setInterval というタイマー機能が使われていましたが、これはブラウザの描画タイミングとズレが生じやすく、動きがカクつく原因でした。
今回使用した requestAnimationFrame は、「ブラウザが画面を更新する準備ができたタイミング(通常は1秒間に60回)」に合わせて関数を呼び出してくれます。これにより、PCのスペックに合わせた滑らかなアニメーション(60FPS)を実現しています。
② 配列(Array)によるオブジェクト管理
弾や敵キャラクターは、画面上に「複数」存在します。これらを管理するために、bullets や enemies という配列を使用しています。
- 生成時:
push()メソッドで新しい弾や敵を配列に追加します。 - 削除時:
splice()メソッドで配列から取り除きます。
★エンジニアの視点: 特に重要なのが「削除処理」です。画面外に出た弾や敵を削除せず配列に残し続けると、メモリ使用量が無限に増え続け、最終的にブラウザがフリーズします。これを防ぐために、if (bullet.y < 0) のような条件でこまめにデータを消去(ガベージコレクションへの配慮)を行っています。
③ 入力遅延を防ぐキー操作ロジック
初心者向けのコードでは、keydown イベントの中で直接 player.x += 5 のように移動処理を書くことが多いです。しかし、これだと「キーを押しっぱなしにしたとき」にOSの設定によって一瞬動きが止まる現象が起きます。
これを防ぐため、本コードでは以下の2段階構想を採用しています。
- イベントリスナー: 「今、キーが押されているか?」という**状態(フラグ)**だけを
keysオブジェクトに記録する。 - ゲームループ: ループの中で
keysの状態を見て、押されていれば移動させる。
これにより、キーボード連打や長押しでも、常に滑らかな移動が可能になります。
まとめ
今回はHTML・CSS・JavaScriptを使って、基礎的なシューティングゲームを作成しました。 このコードをベースに、以下のような改造を加えてみるのも勉強になります。
- 画像の差し替え: 四角形ではなく、フリー素材の宇宙船画像を表示してみる
- 音の追加:
<audio>タグを使って、発射音や爆発音を鳴らす - スマホ対応: タッチ操作で動けるようにイベントリスナーを追加する
プログラミングは「作って動かす」のが一番の学習です。ぜひ自分だけのオリジナルゲームに進化させてみてください!


コメント