はじめに
こんにちは!Web技術を使って自分だけのゲームを作ってみたい、と思ったことはありませんか?
この記事では、HTML、CSS、そしてJavaScriptという基本的なWeb技術だけで、あの有名なパズルゲーム「テトリス」を作成する方法を、ゼロから丁寧に解説していきます。
プログラミング学習の素晴らしい点は、実際に「動くもの」を作りながら学べることです。テトリスは、ゲーム開発の基本的な要素(描画、ユーザー操作、ルール実装など)が詰まっており、初心者の方が楽しみながらスキルアップするのに最適な題材です。
「百聞は一見に如かず」 ということで、まずは完成品がどのようなものか、実際に遊んでみてください!
完成版テトリス - まずは遊んでみよう!
こちらが今回作成するテトリスゲームの完成版です。キーボードの矢印キーで操作できます。ぜひ一度プレイして、どのようなゲームを作るのかイメージを掴んでください。
完成版テトリス - プレイ可能なデモ
実行結果
ゲーム作りのステップバイステップ解説
いかがでしたか?ここからは、このテトリスゲームをどのように作っていくのか、その手順を一つずつ解説していきます。
Step 1: 準備 - 必要なもの
ゲーム開発というと難しそうですが、今回のテトリス作りに特別なツールは必要ありません。
- テキストエディタ: Visual Studio Code、Atomなど、お好みのエディタでOKです。
- Webブラウザ: Google ChromeやFirefoxなど、普段お使いのもので十分です。
前提知識について
HTML、CSS、JavaScriptの基本的な知識(変、配列、if文、for文など)があるとスムーズですが、分からなくても大丈夫!作りながら学んでいけるように、丁寧に解説します。
Step 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>HTML CSS JavaScript テトリス</title> <link rel="stylesheet" href="styles.css"></head><body> <div id="tetris-game"> <h2>テトリス</h2> <div class="game-container"> <!-- ゲーム画面 --> <canvas id="tetris-canvas" width="150" height="300"></canvas>
<!-- ゲーム情報パネル --> <div class="game-info"> <p>スコア: <span id="score-display">0</span></p> <p>ライン: <span id="lines-display">0</span></p> <p>レベル: <span id="level-display">1</span></p>
<!-- 操作説明 --> <div class="controls"> <p><strong>操作:</strong></p> <p>← → : 移動</p> <p>↓ : 高速落下</p> <p>↑ : 回転</p> <p>Space : 即座に落下</p> </div>
<!-- リスタートボタン --> <button id="restart-btn">リスタート</button> </div> </div> </div>
<script src="script.js"></script></body></html>
HTMLの重要なポイント
1. セマンティックな構造
<div id="tetris-game"> <!-- ゲーム全体のコンテナ --> <h2>テトリス</h2> <!-- タイトル --> <div class="game-container"> <!-- ゲーム画面とサイド情報の親要素 --> <canvas id="tetris-canvas"> <!-- ゲーム描画エリア --> <div class="game-info"> <!-- サイド情報パネル -->
2. Canvas要素の設定
<canvas id="tetris-canvas" width="150" height="300"></canvas>
width="150"
height="300"
: ゲーム画面のピクセルサイズ- この比率は、10×20のゲーム盤に15ピクセルブロックを使った計算
3. 動的表示用のspan要素
<p>スコア: <span id="score-display">0</span></p>
- JavaScriptから
textContent
を更新するためのspan要素
viewport meta tagの重要性
<meta name="viewport" content="width=device-width, initial-scale=1.0">
は、モバイルデバイスでの表示を最適化するために必須です。これがないと、スマートフォンでの表示が崩れる可能性があります。
Step 3: CSSで魅力的なデザインを作る
次に、CSSを使ってゲーム画面を美しく、そして使いやすくデザインします。完成版では、グラデーション背景、フレックスボックスレイアウト、レスポンシブデザインを採用しています。
1. ベーススタイルとレイアウト
まず、全体のレイアウトとベースとなるスタイルを定義します。
/* メインコンテナのスタイル */#tetris-game { text-align: center; font-family: Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 15px; color: white;}
/* ゲーム画面とサイド情報を並べるコンテナ */.game-container { display: flex; justify-content: center; align-items: flex-start; /* 上揃え */ gap: 20px; /* 要素間の間隔 */ margin: 20px 0;}
グラデーション背景の解説
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
135deg
: 右下方向への斜めグラデーション#667eea
: 青紫色(開始色)#764ba2
: 深い紫色(終了色)
2. Canvas(ゲーム画面)のスタイル
ゲーム画面となるCanvas要素に、枠線と影を付けて立体的に見せます。
#tetris-canvas { border: 3px solid #fff; /* 白い枠線 */ background: #000; /* 黒い背景 */ box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); /* ドロップシャドウ */}

box-shadow
を使うことで、ゲーム画面に奥行きを与え、より魅力的な見た目にしています。
3. サイド情報パネルのデザイン
スコアや操作説明を表示するサイド情報パネルを、半透明の背景でエレガントにデザインします。
.game-info { background: rgba(255, 255, 255, 0.1); /* 半透明の白背景 */ padding: 20px; border-radius: 10px; min-width: 150px; text-align: left;}
.game-info p { margin: 10px 0; font-size: 16px;}
/* 操作説明部分 */.controls { margin-top: 20px; font-size: 14px;}
.controls p { margin: 5px 0;}
半透明背景の効果
background: rgba(255, 255, 255, 0.1);
rgba(255, 255, 255, 0.1)
: 白色の10%透明度- 背景のグラデーションが透けて見える美しい効果
4. ボタンのデザインとホバー効果
リスタートボタンに、ホバー時の色変化を加えてユーザビリティを向上させます。
#restart-btn { background: #4caf50; /* 緑色の背景 */ color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin-top: 10px; transition: background-color 0.3s ease; /* スムーズな色変化 */}
/* ホバー時のスタイル */#restart-btn:hover { background: #45a049; /* より深い緑色 */}
/* フォーカス時のスタイル(キーボード操作時) */#restart-btn:focus { outline: 2px solid #fff; outline-offset: 2px;}
transition プロパティ
transition: background-color 0.3s ease;
により、ホバー時の背景色変化が滑らかになり、より洗練された印象を与えます。
5. レスポンシブデザイン(モバイル対応)
スマートフォンやタブレットでも快適にプレイできるよう、メディアクエリを使用します。
/* モバイルデバイス向けの調整 */@media (max-width: 600px) { .game-container { flex-direction: column; /* 縦並びに変更 */ align-items: center; }
#tetris-canvas { width: 250px; /* Canvas を大きく表示 */ height: 500px; /* 注意: CSSでのサイズ変更は画質に影響する場合があります */ }
.game-info { margin-top: 20px; min-width: 250px; /* 情報パネルの幅を統一 */ }
/* フォントサイズの調整 */ .game-info p { font-size: 18px; }
.controls { font-size: 16px; }}
レスポンシブデザインの工夫点
- フレックス方向の変更:
flex-direction: column
で縦並びに - Canvas サイズの調整: モバイルでは大きめに表示
- フォントサイズの拡大: タッチデバイスでの可読性向上
6. 完全版CSSコード
全てを組み合わせた完全なCSSコードです:
#tetris-game { text-align: center; font-family: Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 15px; color: white;}
.game-container { display: flex; justify-content: center; align-items: flex-start; gap: 20px; margin: 20px 0;}
#tetris-canvas { border: 3px solid #fff; background: #000; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);}
.game-info { background: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 10px; min-width: 150px; text-align: left;}
.game-info p { margin: 10px 0; font-size: 16px;}
.controls { margin-top: 20px; font-size: 14px;}
.controls p { margin: 5px 0;}
#restart-btn { background: #4caf50; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin-top: 10px; transition: background-color 0.3s ease;}
#restart-btn:hover { background: #45a049;}
#restart-btn:focus { outline: 2px solid #fff; outline-offset: 2px;}
/* レスポンシブデザイン */@media (max-width: 600px) { .game-container { flex-direction: column; align-items: center; }
#tetris-canvas { width: 250px; height: 500px; }
.game-info { margin-top: 20px; min-width: 250px; }
.game-info p { font-size: 18px; }
.controls { font-size: 16px; }}
Step 4: JavaScriptでゲームの心臓部を作る
ここからが本番です。JavaScriptでゲームのロジックを実装していきます。完成版のコードは少し長くなりますが、機能ごとに分けて見ていきましょう。
1. 全体構造とセットアップ
まず、コード全体を即座実行関数(IIFE)で囲み、変数の名前空間を保護します。これにより、他のスクリプトとの競合を避けることができます。
(function() { 'use strict'; // strict modeで安全なコードを書く
// ゲーム設定 const BOARD_WIDTH = 10; const BOARD_HEIGHT = 20; const BLOCK_SIZE = 15;
// ゲーム状態 let board = []; let score = 0; let lines = 0; let level = 1; let gameSpeed = 1000; let gameRunning = false; let currentPiece = null; let gameLoop = null;
// Canvas要素の取得(エラーハンドリング付き) const canvas = document.getElementById('tetris-canvas'); if (!canvas) { console.error('Canvas element not found'); return; } const ctx = canvas.getContext('2d');})();
IIFE(即座実行関数)とは
(function() { ... })()
の形で書かれた関数は、定義と同時に実行されます。これにより、グローバル変数の汚染を防ぎ、他のスクリプトとの競合を避けることができます。
2. テトリミノ(ブロック)の定義
テトリスに登場する7種類のブロック(テトリミノ)の形と色を定義します。完成版では正確な形状と色を使用しています。
// テトリミノ定義(正しい形状)const PIECES = [ { shape: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], color: '#00f0f0' }, // I { shape: [[1,1],[1,1]], color: '#f0f000' }, // O { shape: [[0,1,0],[1,1,1],[0,0,0]], color: '#a000f0' }, // T { shape: [[0,1,1],[1,1,0],[0,0,0]], color: '#00f000' }, // S { shape: [[1,1,0],[0,1,1],[0,0,0]], color: '#f00000' }, // Z { shape: [[1,0,0],[1,1,1],[0,0,0]], color: '#0000f0' }, // J { shape: [[0,0,1],[1,1,1],[0,0,0]], color: '#f0a000' } // L];
3. ゲーム初期化処理
ゲーム開始時とリスタート時に呼び出される初期化関数を実装します。エラーハンドリングも含めた安全な実装です。
function initGame() { try { // ボード初期化(2次元配列で空の状態を作成) board = Array(BOARD_HEIGHT).fill().map(() => Array(BOARD_WIDTH).fill(0)); score = 0; lines = 0; level = 1; gameSpeed = 1000; gameRunning = true;
// 既存のゲームループをクリア if (gameLoop) { clearInterval(gameLoop); gameLoop = null; }
// 新しいピースを作成 currentPiece = createPiece();
// 表示更新 updateDisplay();
// ゲームループ開始 gameLoop = setInterval(() => { if (gameRunning) { dropPiece(); draw(); } }, gameSpeed);
// 初期描画 draw();
console.log('Game initialized successfully'); } catch (error) { console.error('Error initializing game:', error); }}
4. ピース作成と衝突判定
新しいピースを作成する関数と、ブロックが衝突するかを判定する関数を実装します。
function createPiece() { const pieceIndex = Math.floor(Math.random() * PIECES.length); const piece = PIECES[pieceIndex];
return { x: Math.floor(BOARD_WIDTH / 2) - Math.floor(piece.shape.length / 2), y: 0, shape: piece.shape.map(row => [...row]), // ディープコピーで配列を複製 color: piece.color };}
function isCollision(piece, dx = 0, dy = 0) { for (let y = 0; y < piece.shape.length; y++) { for (let x = 0; x < piece.shape[y].length; x++) { if (piece.shape[y][x]) { const newX = piece.x + x + dx; const newY = piece.y + y + dy;
// 境界チェックと既存ブロックとの衝突チェック if (newX < 0 || newX >= BOARD_WIDTH || newY >= BOARD_HEIGHT || (newY >= 0 && board[newY] && board[newY][newX])) { return true; } } } } return false;}
5. ゲームループとブロックの落下
ゲームの中核となる落下処理とライン消去、スコア計算を実装します。
function dropPiece() { if (!currentPiece || !gameRunning) return;
if (!isCollision(currentPiece, 0, 1)) { currentPiece.y++; } else { placePiece(); const linesCleared = clearLines();
// スコア計算とレベルアップ処理 if (linesCleared > 0) { lines += linesCleared; const scoreTable = [0, 40, 100, 300, 1200]; // テトリスの標準スコア表 score += scoreTable[linesCleared] * level; level = Math.floor(lines / 10) + 1; gameSpeed = Math.max(100, 1000 - (level - 1) * 100);
// ゲームスピード更新 if (gameLoop) { clearInterval(gameLoop); gameLoop = setInterval(() => { if (gameRunning) { dropPiece(); draw(); } }, gameSpeed); } }
currentPiece = createPiece(); if (isCollision(currentPiece)) { gameRunning = false; alert('ゲームオーバー!スコア: ' + score); if(gameLoop) clearInterval(gameLoop); } updateDisplay(); }}

完成版では、テトリスの標準的なスコア計算(1ライン40点、2ライン100点、3ライン300点、4ライン1200点)を実装し、レベルが上がるとゲームスピードも速くなります。
6. ブロック回転とライン消去
ブロックの回転処理と、揃ったラインを消去する処理を実装します。
function rotatePiece() { if (!currentPiece) return; const originalShape = currentPiece.shape; const n = originalShape.length; const rotated = Array(n).fill(0).map(() => Array(n).fill(0));
// 90度時計回りに回転 for (let y = 0; y < n; y++) { for (let x = 0; x < n; x++) { rotated[x][n - 1 - y] = originalShape[y][x]; } }
currentPiece.shape = rotated;
// 衝突する場合は元に戻す if (isCollision(currentPiece)) { currentPiece.shape = originalShape; }}
function clearLines() { let linesCleared = 0; for (let y = BOARD_HEIGHT - 1; y >= 0; y--) { if (board[y].every(cell => cell !== 0)) { board.splice(y, 1); // ラインを削除 board.unshift(Array(BOARD_WIDTH).fill(0)); // 上に空のラインを追加 linesCleared++; y++; // 削除したラインを再チェック } } return linesCleared;}
7. 描画処理と表示更新
ゲーム画面を描画し、スコアなどの情報を更新する処理を実装します。
function draw() { if (!ctx) return;
// 画面クリア ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height);
// ボードに固定されたブロックを描画 for (let y = 0; y < BOARD_HEIGHT; y++) { for (let x = 0; x < BOARD_WIDTH; x++) { if (board[y][x]) { ctx.fillStyle = board[y][x]; ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1); } } }
// 現在落下中のピースを描画 if (currentPiece && gameRunning) { ctx.fillStyle = currentPiece.color; for (let y = 0; y < currentPiece.shape.length; y++) { for (let x = 0; x < currentPiece.shape[y].length; x++) { if (currentPiece.shape[y][x]) { ctx.fillRect( (currentPiece.x + x) * BLOCK_SIZE, (currentPiece.y + y) * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1 ); } } } }}
function updateDisplay() { const scoreEl = document.getElementById('score-display'); const linesEl = document.getElementById('lines-display'); const levelEl = document.getElementById('level-display');
if (scoreEl) scoreEl.textContent = score; if (linesEl) linesEl.textContent = lines; if (levelEl) levelEl.textContent = level;}
8. プレイヤー操作とゲーム開始
キーボード操作とリスタート機能を実装し、最後にゲームを開始します。
// キーボード操作document.addEventListener('keydown', function(e) { if (!currentPiece || !gameRunning) return;
switch(e.key) { case 'ArrowLeft': if (!isCollision(currentPiece, -1, 0)) { currentPiece.x--; draw(); } break; case 'ArrowRight': if (!isCollision(currentPiece, 1, 0)) { currentPiece.x++; draw(); } break; case 'ArrowDown': dropPiece(); draw(); break; case 'ArrowUp': rotatePiece(); draw(); break; case ' ': // スペースキーでハードドロップ while (!isCollision(currentPiece, 0, 1)) { currentPiece.y++; } dropPiece(); draw(); break; } e.preventDefault(); // デフォルトの動作を防止});
// リスタートボタンの処理const restartBtn = document.getElementById('restart-btn');if (restartBtn) { restartBtn.addEventListener('click', function() { if (gameLoop) { clearInterval(gameLoop); gameLoop = null; } initGame(); });}
// ゲーム開始(少し遅延させてDOM要素が確実に読み込まれるようにする)setTimeout(() => { initGame();}, 100);
完成版の特徴
完成版では、スペースキーでのハードドロップ機能、レベル上昇によるスピードアップ、正確なスコア計算、エラーハンドリングなど、より本格的なテトリスゲームの機能を実装しています。
まとめ
この記事では、HTML、CSS、JavaScriptを使ってテトリスゲームを作成する基本的な流れを解説しました。完成品のデモから始めることで、目標を明確にしながら学習を進められたのではないでしょうか。
開発のステップをまとめると、以下のようになります。
graph TD A[完成形をイメージ] --> B(Step1: HTMLで骨格作成) B --> C(Step2: CSSでデザイン) C --> D(Step3: JavaScriptでロジック実装) D --> E(ロジックの詳細) E --> F(1.基本設定) E --> G(2.ブロック定義) E --> H(3.描画処理) E --> I(4.ゲームループ) E --> J(5.ユーザー操作) E --> K(6.衝突判定とライン消去) K --> L(完成!)
テトリス開発のステップ
ここからさらに、
- 次に出現するブロックの表示
- スコアやレベルの計算
- 効果音やBGMの追加
- タッチ操作への対応
など、様々な機能を追加して、あなただけのオリジナルゲームに発展させることも可能です。
この記事が、あなたのゲーム開発の第一歩となれば、とても嬉しいです。
完成版では、ゲーム画面とサイド情報を横並びにするため、
.game-container
でフレックスレイアウトを使用しています。また、操作説明も含めることで、初めてプレイする人にも分かりやすくしています。