
【ゲーム】JavaScript:19 ブロック崩し(Breakout)
「ブロック崩し(Breakout)」は、プレイヤーが画面下部のパドルを左右に操作し、跳ね返ったボールで上部のブロックをすべて破壊してステージクリアを目指すクラシックアーケードゲームです。全3ステージで、ボールを3回まで落とせる制限付きのスリルあるプレイが楽しめます。
遊び方・操作方法
- ←→キーでパドルを左右に移動
- ボールを跳ね返して、画面上部のブロックをすべて壊す
- ボールが画面下へ落ちるとボール残数が1減少
- ボール残数が0になるとゲームオーバー
ルール
- ステージごとに異なるブロック配置(定番/ピラミッド/交互配置)
- ボールを3つまで落とせる(
lives = 3
) - 全3ステージをクリアするとエンディング
- ブロックを1つ壊すごとに得点+1
- ボールが落ちると一時停止後に自動再開
🎮ゲームプレイ
以下のリンク先から実際にプレイできます。
素材のダウンロード
以下のリンクから使用する素材をダウンロードできます。
breakout_title.png | breakout_bg.png |
---|---|
![]() | ![]() |
ゲーム画面イメージ

プログラム全文(breakout.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>🎉 ブロック崩し 🎉</title>
<style>
/* ====== 全体設定 ====== */
body {
margin: 0; /* ページ余白をなくす */
overflow: hidden; /* スクロールバーを隠す */
background: url('breakout_bg.png') no-repeat center center fixed;
background-size: cover; /* 背景画像を画面いっぱいに表示 */
font-family: Arial, sans-serif;
}
/* ====== キャンバス ====== */
#canvas {
display: none; /* 初期時は非表示 */
position: absolute;
top: 0; left: 0;
}
/* ====== HUD(ボール残数&得点表示) ====== */
#hud {
position: absolute;
top: 20px; right: 20px;
color: #fff;
background: rgba(0, 0, 0, 0.6); /* 暗背景で視認性アップ */
padding: 5px 10px;
border-radius: 5px;
display: none; /* 初期時は非表示 */
z-index: 1;
line-height: 1.4;
font-size: 18px;
}
/* ====== タイトル画面 ====== */
#titleScreen {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.7); /* 半透明黒背景 */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
text-align: center;
z-index: 2;
}
#titleScreen img {
max-width: 50%;
margin-bottom: 20px;
}
#titleScreen .message {
background: rgba(0, 0, 0, 0.6); /* 暗背景で見やすく */
padding: 10px;
border-radius: 5px;
margin: 5px 0;
line-height: 1.4;
}
#titleScreen button {
font-size: 1.2em;
padding: 10px 20px;
margin-top: 20px;
cursor: pointer;
background: #0a74da; /* ブルーボタン */
border: none;
color: #fff;
border-radius: 5px;
}
/* ====== オーバーレイ ====== */
#overlay {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.7);
display: none; /* 非表示 */
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
text-align: center;
z-index: 3;
}
#overlay h1 {
background: rgba(0, 0, 0, 0.6);
padding: 15px;
border-radius: 5px;
margin: 0;
font-size: 2em;
}
#overlay button {
font-size: 1.2em;
padding: 10px 20px;
margin-top: 20px;
cursor: pointer;
background: #0a74da;
border: none;
color: #fff;
border-radius: 5px;
display: none; /* 初期は隠す */
}
</style>
</head>
<body>
<!-- ====== タイトル画面 ====== -->
<div id="titleScreen">
<img src="breakout_title.png" alt="🎉 ブロック崩し 🎉">
<h1 class="message">▶️ 遊び方・ルール ◀️</h1>
<p class="message">
• ←→キーでパドルを左右に操作<br>
• ボールを跳ね返してブロックをすべて壊そう!<br>
• 全3ステージ、ボールは3個まで落とせる<br>
• ボールがなくなるとゲームオーバー!
</p>
<button id="startBtn">▶️ スタート</button>
</div>
<!-- ====== ゲームキャンバス ====== -->
<canvas id="canvas"></canvas>
<!-- ====== HUD(ボール残数&得点) ====== -->
<div id="hud"></div>
<!-- ====== オーバーレイ ====== -->
<div id="overlay">
<h1 id="overlayMessage">メッセージ</h1>
<button id="overlayBtn">🔄 タイトル画面に戻る</button>
</div>
<script>
// ====== 要素取得 ======
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
const titleScreen = document.getElementById('titleScreen');
const startBtn = document.getElementById('startBtn');
const overlay = document.getElementById('overlay');
const overlayMessage = document.getElementById('overlayMessage');
const overlayBtn = document.getElementById('overlayBtn');
// ====== 画面サイズ調整 ======
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// ====== グローバル変数 ======
let currentStage = 1; // 現在のステージ
let blocks = []; // ブロック配列
let paddle, ball; // パドル・ボールオブジェクト
let gameRunning = false; // ゲーム進行フラグ
let lives = 3; // ボール残数
let score = 0; // 得点
let animationId; // アニメーションID
// ====== 行ごとの色設定 ======
const rowColors = [
'#ff4d4d', // 赤
'#ff944d', // オレンジ
'#ffff4d', // 黄
'#4dff4d', // 緑
'#4d4dff' // 青
];
// ====== パドルクラス ======
class Paddle {
constructor(y) {
this.width = 100; // パドル幅
this.height = 20; // パドル高さ
this.x = canvas.width/2 - this.width/2; // 初期X位置
this.y = y; // Y位置
this.speed = 8; // 移動速度
this.vx = 0; // X方向速度
}
draw() {
ctx.fillStyle = '#fff';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
update() {
this.x += this.vx;
// 画面外へ出ないよう制限
if (this.x < 0) this.x = 0;
if (this.x + this.width > canvas.width) {
this.x = canvas.width - this.width;
}
}
}
// ====== ボールクラス ======
class Ball {
constructor() {
this.radius = 10; // ボール半径
this.reset();
}
reset() {
// 中央に戻し、ランダム角度で発射
this.x = canvas.width/2;
this.y = canvas.height/2;
const angle = (Math.random() * Math.PI/2) + Math.PI/4; // 45°〜135°
this.speed = 5;
this.vx = this.speed * Math.cos(angle);
this.vy = this.speed * Math.sin(angle);
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
ctx.fillStyle = '#ff0'; // 黄色
ctx.fill();
ctx.closePath();
}
update(paddle, blocks) {
// 壁反射(左右)
if (this.x - this.radius < 0 || this.x + this.radius > canvas.width) {
this.vx = -this.vx;
}
// 上壁反射
if (this.y - this.radius < 0) {
this.vy = -this.vy;
}
// パドル反射
if (this.y + this.radius > paddle.y &&
this.x > paddle.x && this.x < paddle.x + paddle.width) {
this.vy = -Math.abs(this.vy);
// 当たった位置で反射角を調整
const hitPos = (this.x - (paddle.x + paddle.width/2)) / (paddle.width/2);
this.vx = this.speed * hitPos;
}
// ブロック衝突判定
for (let b of blocks) {
if (!b.destroyed &&
this.x > b.x && this.x < b.x + b.width &&
this.y - this.radius < b.y + b.height &&
this.y + this.radius > b.y) {
this.vy = -this.vy; // Y方向反転
b.destroyed = true; // ブロック消去
score++; // 得点加算
updateHUD(); // HUD更新
break;
}
}
// 座標更新
this.x += this.vx;
this.y += this.vy;
}
}
// ====== ブロッククラス ======
class Block {
constructor(x, y, w, h, color) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.color = color; // 行ごとの色
this.destroyed = false; // 消去フラグ
}
draw() {
if (this.destroyed) return;
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.strokeStyle = '#000';
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
// ====== ステージ初期化 ======
function initStage(stage) {
paddle = new Paddle(canvas.height - 40);
ball = new Ball();
blocks = [];
const cols = 10;
const rows = 5;
const blockW = canvas.width / cols - 10;
const blockH = 20;
const offsetY = 60;
if (stage === 1) {
// 定番5行×10列
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = c * (blockW + 10) + 5;
const y = offsetY + r * (blockH + 5);
blocks.push(new Block(x, y, blockW, blockH, rowColors[r]));
}
}
} else if (stage === 2) {
// 中央ピラミッド型
for (let r = 0; r < rows; r++) {
for (let c = r; c < cols - r; c++) {
const x = c * (blockW + 10) + 5;
const y = offsetY + r * (blockH + 5);
blocks.push(new Block(x, y, blockW, blockH, rowColors[r]));
}
}
} else if (stage === 3) {
// 交互配置
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if ((r + c) % 2 === 0) {
const x = c * (blockW + 10) + 5;
const y = offsetY + r * (blockH + 5);
blocks.push(new Block(x, y, blockW, blockH, rowColors[r]));
}
}
}
}
}
// ====== HUD更新 ======
function updateHUD() {
// ボール残数と得点を1行で表示
let bullets = '';
for (let i = 0; i < lives; i++) {
bullets += '<span style="color:yellow">●</span>';
}
hud.innerHTML = `ボール残数: ${bullets} 得点: ${score}`;
}
// ====== キー操作 ======
window.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft') paddle.vx = -paddle.speed;
if (e.key === 'ArrowRight') paddle.vx = paddle.speed;
});
window.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') paddle.vx = 0;
});
// ====== メインゲームループ ======
function gameLoop() {
if (!gameRunning) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 描画・更新
paddle.update(); paddle.draw();
for (let b of blocks) b.draw();
ball.update(paddle, blocks);
ball.draw();
// ボール落下判定
if (ball.y - ball.radius > canvas.height) {
lives--; // ボール数-1
updateHUD(); // HUD更新
if (lives <= 0) {
gameOver(); // ゲームオーバー
return;
}
// ボールロスト演出
gameRunning = false;
overlayMessage.textContent = '⚠️ ボールを失いました ⚠️';
overlayBtn.style.display = 'none';
overlay.style.display = 'flex';
cancelAnimationFrame(animationId);
// 2秒後に自動再開
setTimeout(() => {
overlay.style.display = 'none';
ball.reset();
paddle.x = canvas.width/2 - paddle.width/2;
gameRunning = true;
gameLoop();
}, 2000);
return;
}
// ブロック全消しチェック
const remaining = blocks.filter(b => !b.destroyed).length;
if (remaining === 0) {
handleStageClear();
return;
}
animationId = requestAnimationFrame(gameLoop);
}
// ====== ステージクリア処理 ======
function handleStageClear() {
gameRunning = false;
overlayMessage.textContent = `🎊 ステージ${currentStage} クリア! 🎊`;
overlayBtn.style.display = 'none';
overlay.style.display = 'flex';
// 5秒後に次ステージ or 全クリア
setTimeout(() => {
overlay.style.display = 'none';
currentStage++;
if (currentStage > 3) {
showGameComplete();
} else {
initStage(currentStage);
updateHUD();
gameRunning = true;
gameLoop();
}
}, 5000);
}
// ====== ゲームオーバー表示 ======
function gameOver() {
gameRunning = false;
overlayMessage.textContent = `💥 ゲームオーバー 💥`;
overlayBtn.style.display = 'block';
overlay.style.display = 'flex';
}
// ====== 全クリア表示 ======
function showGameComplete() {
overlayMessage.textContent = `🎉 全ステージクリア! 🎉`;
overlayBtn.style.display = 'block';
overlay.style.display = 'flex';
}
// ====== スタートボタン処理 ======
startBtn.addEventListener('click', () => {
currentStage = 1;
lives = 3;
score = 0;
updateHUD();
titleScreen.style.display = 'none';
canvas.style.display = 'block';
hud.style.display = 'block';
overlay.style.display = 'none';
initStage(currentStage);
gameRunning = true;
gameLoop();
});
// ====== オーバーレイ「タイトルへ戻る」 ======
overlayBtn.addEventListener('click', () => {
hud.style.display = 'none';
canvas.style.display = 'none';
overlay.style.display = 'none';
titleScreen.style.display = 'flex';
});
</script>
</body>
</html>
アルゴリズムの流れ
ステップ | 処理内容 | 主な関数/命令 |
---|---|---|
1. 初期化 | キャンバスサイズ調整、ステージ・スコア・ライフ設定 | resizeCanvas() , startBtn イベント |
2. ステージ生成 | パドル・ボール・ブロック配置 | initStage(stage) |
3. HUD更新 | ボール残数と得点を表示 | updateHUD() |
4. 入力受付 | ←→キーでパドル速度を設定 | keydown /keyup イベント |
5. メインループ | 画面クリア→オブジェクト更新・描画→得点・衝突判定 | gameLoop() |
6. ボール落下判定 | ボールが下端を越えたらライフ減少orゲームオーバー | gameLoop() →gameOver() |
7. ブロック消去判定 | 全ブロック破壊でステージクリア | handleStageClear() |
8. ステージ遷移 | 次ステージ or 全クリア表示 | handleStageClear() , showGameComplete() |
9. 終了画面 | ゲームオーバー or 全クリア時にオーバーレイ表示 | gameOver() , showGameComplete() |
関数・クラスの詳しい解説
名称 | 説明 |
---|---|
class Paddle | パドルの位置・サイズ・速度・描画(draw() )・移動(update() )管理 |
class Ball | ボールの位置・速度・描画(draw() )・跳ね返り&衝突判定(update() )・初期化(reset() ) |
class Block | ブロックの位置・サイズ・色・消去フラグ管理、描画(draw() ) |
resizeCanvas() | ウィンドウリサイズ時に <canvas> を画面全体に設定 |
initStage(stage) | 指定ステージのブロック配置ルールに従って blocks[] を生成 |
updateHUD() | <div id="hud"> にボール残数●と得点を反映 |
gameLoop() | 毎フレームの描画・更新、落下判定、クリア判定、再帰ループ呼出し管理 |
handleStageClear() | ステージクリア時のオーバーレイ表示、次ステージ or 全クリア遷移 |
gameOver() | ライフ切れ時のオーバーレイ表示と「タイトルへ戻る」ボタン表示 |
showGameComplete() | 全ステージクリア時のオーバーレイ表示 |
改造のポイント
- ブロック配置カスタマイズ:
initStage()
内に新しい配置パターンを追加して、ステージ数を増やす - ボール挙動の強化:ボール速度の増加、ランダムバウンド、回転(スピン)効果を導入
- パワーアップ要素:壊したブロックからパワーアップアイテムをドロップし、パドル拡大やマルチボールなどを実装
- サウンド/エフェクト追加:
Audio
API で効果音や BGM を再生し、演出を強化 - スコアランキング:
localStorage
へスコアを保存し、ハイスコア表示画面を追加 - レスポンシブ調整:モバイル/タブレットでも遊べるよう、タッチ操作を追加
アドバイス:ゲーム性を深めるには、パワーアップ/パワーダウンアイテムを導入してリスクとリワードを設計し、ユーザーが自分の戦略を考えられるようにすると良いでしょう。また、ステージクリア時に動的な演出(パーティクルやアニメーション)を入れると達成感が高まります!