MENU

JavaScriptでテトリスを作る方法:初心者向け完全ガイド

みなさん、こんにちは!JavaScriptでテトリスを作ってみたいと思ったことありませんか?実は、思ったほど難しくないんですよ。この記事では、JavaScriptを使ってテトリスを作る方法を、初心者の方にも分かりやすく解説していきます。ステップバイステップで進めていくので、プログラミング初心者の方も安心してついてきてくださいね。さあ、一緒にJavaScriptの世界でテトリスを作る冒険に出かけましょう!

目次

テトリスゲーム開発の基礎:JavaScriptコーディングの第一歩

テトリスの開発を始める前に、まずはJavaScriptの基本を押さえておきましょう。変数、関数、条件分岐などの基本的な概念を理解していれば十分です。心配しないでください。これからの説明で、テトリスを作るために必要な知識を少しずつ身につけていけますよ。初めてのゲーム開発、ワクワクしませんか?一緒に楽しみながら学んでいきましょう!

ゲームループとフレームワークの設計:テトリスの基本構造を理解する

テトリスを作る上で最初に理解しておきたいのが、ゲームループとフレームワークの概念です。ゲームループって聞くと難しそうに感じるかもしれませんが、実はとってもシンプルなんですよ。

ゲームループは、ゲームの状態を常に更新し、画面に描画し続ける仕組みのことです。テトリスで言えば、ブロックが落ちてくる、ラインが揃ったら消える、スコアが加算される…といった一連の流れを繰り返し実行することですね。

JavaScriptでこのゲームループを実現するには、setInterval()requestAnimationFrame()という関数を使います。例えば、こんな感じでコードを書いてみましょう:

function gameLoop() {
    update();
    draw();
    requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

このコードでは、gameLoop関数が繰り返し呼び出されることで、ゲームの状態更新(update)と描画(draw)が連続して行われるんです。requestAnimationFrameを使うと、ブラウザの描画タイミングに合わせて処理を行えるので、スムーズな動きが実現できるんですよ。

フレームワークの設計は、ゲーム全体の構造を決める重要な部分です。テトリスの場合、以下のような構造を考えるといいでしょう:

  1. ゲーム状態の管理(現在のブロック、次のブロック、スコアなど)
  2. ブロックの操作(移動、回転)
  3. 衝突判定(ブロックが地面や他のブロックに当たったかどうか)
  4. ライン消去の処理
  5. 画面描画

これらの要素をそれぞれ関数やクラスとして実装していくと、整理された分かりやすいコードになりますよ。

requestAnimationFrameを使用したスムーズなアニメーション実装テクニック

先ほど少し触れたrequestAnimationFrameについて、もう少し詳しく見ていきましょう。この関数、実はアニメーションをスムーズに実装するための強い味方なんです。

requestAnimationFrameは、ブラウザのレンダリングタイミングに合わせて関数を呼び出してくれます。つまり、画面の更新タイミングに最適化された形でアニメーションを実行できるんですよ。これを使うと、カクカクしたアニメーションではなく、滑らかな動きを実現できます。

具体的には、こんな風に使います:

let lastTime = 0;
const fps = 60;
const frameInterval = 1000 / fps;

function gameLoop(currentTime) {
    requestAnimationFrame(gameLoop);

    const deltaTime = currentTime - lastTime;
    if (deltaTime < frameInterval) return;

    lastTime = currentTime - (deltaTime % frameInterval);

    update();
    draw();
}

requestAnimationFrame(gameLoop);

このコードでは、前回の更新からの経過時間(deltaTime)を計算し、一定のインターバル(この場合は60FPS)で更新と描画を行っています。こうすることで、コンピュータの性能に関わらず、一定のスピードでゲームが進行するんです。

ちなみに、update()関数ではゲームの状態更新(ブロックの落下、ライン消去の判定など)を行い、draw()関数では実際の画面描画を行います。これらの関数の中身は、ゲームの進行に合わせて適宜実装していくことになりますよ。

テトリスの場合、update()関数の中では、例えばこんなことを行います:

  • 現在のブロックを一段下に移動
  • 移動後に衝突が起きていないかチェック
  • 衝突が起きていたら、ブロックを固定し、新しいブロックを生成
  • ラインが揃っていないかチェックし、揃っていたら消去してスコア加算

draw()関数では:

  • ゲーム盤面の描画
  • 現在落下中のブロックの描画
  • 次に出てくるブロックの表示
  • スコアの表示

といった具合です。

このように、requestAnimationFrameを使ったゲームループを実装することで、スムーズで快適なテトリスゲームを作ることができるんです。初めは少し難しく感じるかもしれませんが、実際に動かしてみると、その動きの滑らかさに感動すると思いますよ!

キャンバス操作の基本:テトリスのグリッドを描画する方法

さて、次はテトリスの見た目を作っていきましょう。ここでは、HTML5のCanvas要素を使って、テトリスのグリッドを描画する方法を解説します。Canvasって聞くと難しそうに感じるかもしれませんが、基本を押さえれば意外と簡単なんですよ。

まずは、HTMLファイルにCanvas要素を追加しましょう:

<canvas id="tetrisCanvas" width="300" height="600"></canvas>

これで、幅300ピクセル、高さ600ピクセルのキャンバスが用意できました。次に、JavaScriptでこのキャンバスを操作していきます:

const canvas = document.getElementById('tetrisCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 30; // 1マスの大きさ
const boardWidth = 10; // 盤面の幅(マス数)
const boardHeight = 20; // 盤面の高さ(マス数)

function drawGrid() {
    ctx.strokeStyle = '#ddd';

    // 縦線を引く
    for (let x = 0; x <= boardWidth; x++) {
        ctx.beginPath();
        ctx.moveTo(x * gridSize, 0);
        ctx.lineTo(x * gridSize, boardHeight * gridSize);
        ctx.stroke();
    }

    // 横線を引く
    for (let y = 0; y <= boardHeight; y++) {
        ctx.beginPath();
        ctx.moveTo(0, y * gridSize);
        ctx.lineTo(boardWidth * gridSize, y * gridSize);
        ctx.stroke();
    }
}

drawGrid();

このコードで何をしているのか、順番に見ていきましょう:

  1. まず、Canvas要素を取得し、2D描画コンテキストを取得します。
  2. グリッドのサイズや盤面の大きさを定義します。
  3. drawGrid関数を定義し、その中でグリッドを描画します。
  4. 縦線と横線をそれぞれ繰り返し描画することで、グリッドを作成します。

これを実行すると、テトリスの盤面らしきグリッドが表示されるはずです。どうですか?思ったより簡単だったでしょう?

HTML5 Canvasを活用したテトリスブロックの効率的な描画手法

グリッドが描けたら、次はいよいよテトリスブロックを描画していきましょう。ブロックの描画も、基本的な図形描画の組み合わせで実現できるんです。

まずは、ブロックを表現するための配列を用意します:

const blocks = [
    [[1, 1, 1, 1]],  // I
    [[1, 1], [1, 1]],  // O
    [[1, 1, 1], [0, 1, 0]],  // T
    [[1, 1, 1], [1, 0, 0]],  // L
    [[1, 1, 1], [0, 0, 1]],  // J
    [[1, 1, 0], [0, 1, 1]],  // S
    [[0, 1, 1], [1, 1, 0]]   // Z
];

この配列は、各テトリスブロックの形状を表しています。1が実際のブロック部分、0が空白部分です。

次に、ブロックを描画する関数を作ります:

function drawBlock(blockType, x, y, color) {
    const block = blocks[blockType];
    ctx.fillStyle = color;

    for (let row = 0; row < block.length; row++) {
        for (let col = 0; col < block[row].length; col++) {
            if (block[row][col]) {
                ctx.fillRect(
                    (x + col) * gridSize, 
                    (y + row) * gridSize, 
                    gridSize - 1, 
                    gridSize - 1
                );
            }
        }
    }
}

この関数は、ブロックの種類、位置、色を引数として受け取り、対応するブロックを描画します。fillRectメソッドを使って、各マスを個別に塗りつぶしていきます。

実際に使う時はこんな感じです:

// Iブロック(水色)を(3, 0)の位置に描画
drawBlock(0, 3, 0, 'cyan');

// Tブロック(紫)を(5, 5)の位置に描画
drawBlock(2, 5, 5, 'purple');

これで、テトリスブロックを簡単に描画できるようになりました!

さらに効率的な描画を行うには、ダブルバッファリングという技術を使うこともできます。これは、画面外のキャンバスに一旦描画してから、それを実際の画面に転送する方法です。こうすることで、描画のちらつきを防ぐことができるんです。

const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
const offscreenCtx = offscreenCanvas.getContext('2d');

function draw() {
    // オフスクリーンキャンバスにゲーム状態を描画
    offscreenCtx.clearRect(0, 0, canvas.width, canvas.height);
    drawGrid(offscreenCtx);
    drawBlocks(offscreenCtx);

    // オフスクリーンキャンバスの内容を実際のキャンバスに転送
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(offscreenCanvas, 0, 0);
}

このテクニックを使うと、画面のちらつきが減って、よりスムーズな描画が可能になります。

以上が、HTML5 Canvasを使ったテトリスブロックの描画方法です。最初は少し複雑に感じるかもしれませんが、一つ一つ理解していけば、きっと「あ、こうやるんだ!」という発見があると思います。ぜひ、実際にコードを書いて試してみてくださいね。自分で書いたコードでブロックが動き出す瞬間、きっとワクワクするはずです!

テトリスのゲームロジック実装:JavaScriptで動きを制御する

さて、ここからが本当の意味でのゲーム作りの始まりです。見た目は整ったけど、まだ何も動いていませんよね。これから、JavaScriptを使ってテトリスの核心部分、つまりゲームの動きや規則を実装していきます。難しそうに聞こえるかもしれませんが、一つずつ理解していけば、きっと「あ、こうなってるんだ!」と納得できると思いますよ。それじゃあ、一緒に挑戦してみましょう!

ブロックの生成と落下:重力シミュレーションの作り方

テトリスの醍醐味といえば、次々と落ちてくるブロックですよね。この「落ちる」という動きを、JavaScriptでどう表現するのか、一緒に見ていきましょう。

まずは、ブロックを生成する関数を作ります:

function generateBlock() {
    const blockType = Math.floor(Math.random() * blocks.length);
    const x = Math.floor(boardWidth / 2) - 1;
    const y = 0;
    return { type: blockType, x: x, y: y };
}

let currentBlock = generateBlock();

この関数では、ランダムなブロックタイプを選び、盤面の上部中央に配置しています。

次に、ブロックを落下させる関数を作ります:

function dropBlock() {
    currentBlock.y++;
    if (checkCollision()) {
        currentBlock.y--;
        placeBlock();
        currentBlock = generateBlock();
    }
}

このdropBlock関数は、現在のブロックを1マス下に移動させます。もし衝突が起きたら(つまり、他のブロックや地面にぶつかったら)、1マス上に戻して固定し、新しいブロックを生成します。

ここで重要なのがcheckCollision関数です。これは、ブロックが他のブロックや壁、地面と衝突していないかをチェックする関数です:

function checkCollision() {
    const block = blocks[currentBlock.type];
    for (let row = 0; row < block.length; row++) {
        for (let col = 0; col < block[row].length; col++) {
            if (block[row][col]) {
                const x = currentBlock.x + col;
                const y = currentBlock.y + row;
                if (x < 0 || x >= boardWidth || y >= boardHeight || gameBoard[y][x]) {
                    return true;
                }
            }
        }
    }
    return false;
}

この関数は、現在のブロックの各部分について、盤面の範囲外に出ていないか、既に配置されたブロックと重なっていないかをチェックします。

さて、これらの関数を使って、実際にブロックを落下させるにはどうすればいいでしょうか?ここで登場するのが、JavaScriptのsetInterval関数です。

setIntervalを用いたブロック落下速度の調整と難易度設定

setIntervalは、指定した時間間隔で繰り返し関数を実行する JavaScript の機能です。これを使って、一定間隔でブロックを落下させることができるんです。

let dropInterval = 1000; // 1秒ごとにブロックを落下
let gameLoop = setInterval(dropBlock, dropInterval);

このコードでは、1秒(1000ミリ秒)ごとにdropBlock関数を呼び出しています。つまり、1秒に1マスずつブロックが落ちていくわけです。

でも、これだけじゃちょっと物足りないですよね。テトリスって、進むにつれて難しくなっていくのが楽しいところです。そこで、レベルアップに応じて落下速度を速くする仕組みを追加してみましょう:

let level = 1;
let score = 0;

function updateLevel() {
    level = Math.floor(score / 1000) + 1;
    dropInterval = Math.max(100, 1000 - (level - 1) * 100); // 最小100ミリ秒まで速くなる
    clearInterval(gameLoop);
    gameLoop = setInterval(dropBlock, dropInterval);
}

function addScore(lines) {
    const points = [40, 100, 300, 1200]; // 1,2,3,4ライン消しのスコア
    score += points[lines - 1] * level;
    updateLevel();
}

このupdateLevel関数は、スコアに応じてレベルを計算し、それに合わせて落下速度を調整します。レベルが上がるごとに、ブロックの落下間隔が100ミリ秒ずつ短くなります(ただし、最小100ミリ秒まで)。

addScore関数は、ライン消去時にスコアを加算し、レベルの更新をチェックします。

これらの関数を使うことで、ゲームの進行に応じて難易度が上がっていく、本格的なテトリスゲームが作れるんです。最初は簡単だけど、どんどん難しくなっていく…そんなゲーム性がプレイヤーを夢中にさせるんですよね。

ここまでで、ブロックの生成と落下、そして難易度調整の基本的な仕組みが完成しました。次は、もう一つの重要な要素、ライン消去の処理に進みましょう。テトリスの醍醐味である「ライン消し」、どうやってプログラムで表現するのか、一緒に見ていきましょう!

ライン消去とスコア計算:ゲームルールをコードで表現する

テトリスの醍醐味と言えば、ライン消去ですよね。ブロックを上手く積み重ねて、横一列を埋めたときの爽快感…あれをプログラムで表現するのは、実はとってもワクワクする作業なんです。

まずは、ライン消去を判定する関数を作ってみましょう:

function checkLines() {
    let linesCleared = 0;
    for (let y = boardHeight - 1; y >= 0; y--) {
        if (gameBoard[y].every(cell => cell !== 0)) {
            // ラインが揃った場合
            gameBoard.splice(y, 1);
            gameBoard.unshift(new Array(boardWidth).fill(0));
            linesCleared++;
            y++; // 消去したラインの分、もう一度同じ行をチェック
        }
    }
    return linesCleared;
}

この関数では、盤面の下から上に向かって各行をチェックしています。everyメソッドを使って、行のすべてのセルが埋まっているかを確認しています。ラインが揃っていたら、その行を削除し(splice)、新しい空の行を盤面の一番上に追加します(unshift)。

そして、消去したライン数を返します。これを使って、スコアを計算しましょう:

function updateScore(linesCleared) {
    const points = [40, 100, 300, 1200]; // 1,2,3,4ライン消しのスコア
    if (linesCleared > 0) {
        score += points[linesCleared - 1] * level;
        updateLevel();
        // スコア表示を更新
        document.getElementById('score').textContent = score;
    }
}

この関数では、消去したライン数に応じてスコアを加算しています。1行消し、2行消し、3行消し、4行消し(テトリス)で、それぞれ異なるポイントが加算されるんです。さらに、現在のレベルを掛け合わせることで、レベルが上がるほど高得点が取れるようになっています。

これらの関数を、ブロックが固定されたタイミングで呼び出すようにしましょう:

function placeBlock() {
    const block = blocks[currentBlock.type];
    for (let row = 0; row < block.length; row++) {
        for (let col = 0; col < block[row].length; col++) {
            if (block[row][col]) {
                const x = currentBlock.x + col;
                const y = currentBlock.y + row;
                gameBoard[y][x] = currentBlock.type + 1; // 0は空きマスなので+1する
            }
        }
    }
    const linesCleared = checkLines();
    updateScore(linesCleared);
}

これで、ブロックが固定されるたびにライン消去のチェックが行われ、スコアが更新されるようになりました。

配列操作を駆使したテトリスのライン消去ロジックの実装方法

先ほどのライン消去のロジックをもう少し詳しく見てみましょう。JavaScriptの配列操作を巧みに使うことで、効率的にライン消去を実現できるんです。

function checkLines() {
    let linesCleared = 0;
    for (let y = boardHeight - 1; y >= 0; y--) {
        if (gameBoard[y].every(cell => cell !== 0)) {
            // ラインが揃った場合
            gameBoard.splice(y, 1);
            gameBoard.unshift(new Array(boardWidth).fill(0));
            linesCleared++;
            y++; // 消去したラインの分、もう一度同じ行をチェック
        }
    }
    return linesCleared;
}

このコードの面白いところは、spliceunshiftを使っている点です。

  • splice(y, 1)は、y番目の要素を1つ削除します。つまり、揃ったラインを取り除いているんです。
  • unshift(new Array(boardWidth).fill(0))は、配列の先頭(つまり盤面の一番上)に新しい空の行を追加しています。

この2つの操作を組み合わせることで、揃ったラインを消し、上のブロックを下に落とす…という複雑な動きを、シンプルなコードで表現できているんです。

さらに、y++を使って同じ行を再チェックしているのも賢い方法です。これにより、複数のラインが同時に消える場合(例えば2行や3行同時消し)にも対応できています。

このように、配列操作をうまく使いこなすことで、複雑なゲームのルールも効率的に実装できるんです。プログラミングって、こういうところがとても面白いんですよ。ちょっとしたコードの工夫で、大きな動きを実現できる…そんな魔法のような感覚を味わえるんです。

さて、ここまでで、テトリスの基本的なゲームロジックがほぼ完成しました。ブロックの生成と落下、ライン消去とスコア計算…テトリスらしさが出てきましたね。でも、まだ足りないものがありますよね。そう、プレイヤーの操作です!次は、キーボード入力を受け付けて、ブロックを動かす方法を見ていきましょう。プレイヤーが実際に操作できるようになったら、きっともっとゲームらしくなりますよ。楽しみですね!

ユーザーインタラクションの実装:キーボード操作でテトリスを操作する

さあ、いよいよプレイヤーが実際にテトリスを操作できるようにしていきます。キーボードの矢印キーでブロックを動かしたり、回転させたり…そんなおなじみの操作を、JavaScriptでどう実現するのか。ちょっとワクワクしませんか?難しそうに思えるかもしれませんが、意外とシンプルなんですよ。一緒に挑戦してみましょう!

イベントリスナーの設定:キーボード入力を検知してブロックを動かす

JavaScriptでキーボード入力を受け付けるには、「イベントリスナー」というものを使います。これは、特定のイベント(この場合はキーが押されること)が起きたときに、特定の関数を呼び出す仕組みです。

まずは、キーボード入力を受け付ける関数を作ってみましょう:

function handleKeyPress(event) {
    switch(event.keyCode) {
        case 37: // 左矢印キー
            moveBlock(-1, 0);
            break;
        case 39: // 右矢印キー
            moveBlock(1, 0);
            break;
        case 40: // 下矢印キー
            moveBlock(0, 1);
            break;
        case 38: // 上矢印キー
            rotateBlock();
            break;
    }
}

document.addEventListener('keydown', handleKeyPress);

このhandleKeyPress関数では、押されたキーに応じて適切な動作を行います。左右の矢印キーでブロックを左右に動かし、下矢印キーで早く落とし、上矢印キーで回転させます。

document.addEventListener('keydown', handleKeyPress);という行で、キーが押されるたびにhandleKeyPress関数が呼ばれるようになります。

次に、ブロックを動かすmoveBlock関数を実装しましょう:

function moveBlock(dx, dy) {
    currentBlock.x += dx;
    currentBlock.y += dy;
    if (checkCollision()) {
        currentBlock.x -= dx;
        currentBlock.y -= dy;
        if (dy > 0) {
            placeBlock();
            currentBlock = generateBlock();
        }
    }
    drawGame();
}

この関数では、ブロックを指定された方向(dx, dy)に動かします。もし衝突が起きたら元の位置に戻します。下に動かそうとして衝突した場合は、ブロックを固定して新しいブロックを生成します。

これで、左右下の矢印キーでブロックを動かせるようになりました。でも、まだ回転ができませんね。次は回転の実装に挑戦してみましょう。

addEventListenerを使用した効率的なキーボード操作の実装テクニック

先ほど使ったaddEventListenerは、実はとても強力で柔軟な機能なんです。これをうまく使いこなすことで、より洗練されたキーボード操作を実現できます。

例えば、キーを押し続けたときの挙動を調整してみましょう:

let keyState = {};

document.addEventListener('keydown', function(e) {
    keyState[e.keyCode] = true;
});

document.addEventListener('keyup', function(e) {
    keyState[e.keyCode] = false;
});

function gameLoop() {
    if (keyState[37]) moveBlock(-1, 0);  // 左
    if (keyState[39]) moveBlock(1, 0);   // 右
    if (keyState[40]) moveBlock(0, 1);   // 下

    // 他のゲームロジック...

    requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

この方法では、キーが押されている間はkeyStateオブジェクトの対応する値がtrueになります。gameLoop関数内でこの状態をチェックすることで、キーを押し続けている間、連続してブロックを動かすことができます。

さらに、キーの同時押しにも対応できます。例えば、左右のキーを同時に押したときは何も起こらないようにするなど、より細かい制御が可能になります。

また、モバイルデバイスでの操作を考慮するなら、タッチイベントも追加するといいでしょう:

let touchStartX, touchStartY;

document.addEventListener('touchstart', function(e) {
    touchStartX = e.touches[0].clientX;
    touchStartY = e.touches[0].clientY;
});

document.addEventListener('touchmove', function(e) {
    let touchEndX = e.touches[0].clientX;
    let touchEndY = e.touches[0].clientY;

    let dx = touchEndX - touchStartX;
    let dy = touchEndY - touchStartY;

    if (Math.abs(dx) > Math.abs(dy)) {
        // 横方向の移動
        if (dx > 0) moveBlock(1, 0);
        else moveBlock(-1, 0);
    } else {
        // 縦方向の移動(下方向のみ)
        if (dy > 0) moveBlock(0, 1);
    }

    touchStartX = touchEndX;
    touchStartY = touchEndY;
});

これで、スマートフォンやタブレットでも指でスワイプしてブロックを操作できるようになります。

このように、addEventListenerを使いこなすことで、様々な入力方法に対応した柔軟な操作システムを構築できるんです。ユーザーにとって使いやすいインターフェースを作ることは、ゲーム開発において非常に重要なポイントですよ。

さて、ブロックの移動はできるようになりました。でも、テトリスと言えばブロックの回転も大切ですよね。次は、ブロックを回転させる機能を実装してみましょう。回転の処理は少し複雑ですが、理解できれば大きな達成感が得られるはずです。一緒に挑戦してみましょう!

ブロック回転のアルゴリズム:数学的アプローチで形状変更を実現する

テトリスのブロック回転、見ていて気持ちいいですよね。でも、これをプログラムで表現するとなると、ちょっと頭を悩ませるかもしれません。でも大丈夫、数学的なアプローチを使えば、意外とスッキリ実装できるんです。

まずは、基本的な回転の仕組みを考えてみましょう。2次元の点(x, y)を90度回転させると、新しい座標(y, -x)になります。この原理を使って、ブロックの回転を実装していきます。

function rotateBlock() {
    const rotated = [];
    for (let y = 0; y < currentBlock.shape[0].length; y++) {
        rotated.push([]);
        for (let x = 0; x < currentBlock.shape.length; x++) {
            rotated[y][x] = currentBlock.shape[currentBlock.shape.length - 1 - x][y];
        }
    }

    const previousShape = currentBlock.shape;
    currentBlock.shape = rotated;

    if (checkCollision()) {
        currentBlock.shape = previousShape;  // 衝突したら元に戻す
    } else {
        drawGame();  // 衝突しなければ描画を更新
    }
}

このrotateBlock関数では、現在のブロックの形状を90度回転させた新しい配列を作成しています。そして、回転後の形状で衝突チェックを行い、問題なければその形状を採用します。衝突が起きた場合は元の形状に戻します。

しかし、この単純な回転だけでは、壁際でうまく回転できないことがあります。そこで、「壁蹴り」と呼ばれるテクニックを実装してみましょう。

行列変換を応用したテトリスブロックの滑らかな回転処理の実装

「壁蹴り」とは、回転後にブロックが壁や他のブロックと衝突する場合、ブロックを少し横にずらして回転を可能にする技術です。これを実装することで、よりスムーズな操作感を実現できます。

function rotateBlock() {
    const rotated = [];
    for (let y = 0; y < currentBlock.shape[0].length; y++) {
        rotated.push([]);
        for (let x = 0; x < currentBlock.shape.length; x++) {
            rotated[y][x] = currentBlock.shape[currentBlock.shape.length - 1 - x][y];
        }
    }

    const previousShape = currentBlock.shape;
    currentBlock.shape = rotated;

    const kicks = [
        [0, 0],
        [-1, 0],
        [1, 0],
        [0, 1],
        [-1, 1],
        [1, 1]
    ];

    for (let kick of kicks) {
        currentBlock.x += kick[0];
        currentBlock.y += kick[1];
        if (!checkCollision()) {
            drawGame();
            return;  // 回転成功
        }
        currentBlock.x -= kick[0];
        currentBlock.y -= kick[1];
    }

    currentBlock.shape = previousShape;  // どの位置でも回転できなかった場合は元に戻す
}

この改良版のrotateBlock関数では、回転後にいくつかの位置でブロックを配置してみて、衝突しない位置が見つかればその位置で回転を確定させます。この「壁蹴り」のおかげで、壁際でもスムーズに回転できるようになります。

さらに、より高度な回転システムを実装したい場合は、SuperRotationSystem(SRS)と呼ばれる方式を採用することもできます。SRSは現代的なテトリスゲームで広く使われている回転システムで、より複雑な回転パターンを可能にします。

const SRSTests = {
    '0>>1': [[0,0], [-1,0], [-1,1], [0,-2], [-1,-2]],
    '1>>2': [[0,0], [1,0], [1,-1], [0,2], [1,2]],
    '2>>3': [[0,0], [1,0], [1,1], [0,-2], [1,-2]],
    '3>>0': [[0,0], [-1,0], [-1,-1], [0,2], [-1,2]],
    '0>>3': [[0,0], [1,0], [1,1], [0,-2], [1,-2]],
    '1>>0': [[0,0], [1,0], [1,-1], [0,2], [1,2]],
    '2>>1': [[0,0], [-1,0], [-1,1], [0,-2], [-1,-2]],
    '3>>2': [[0,0], [-1,0], [-1,-1], [0,2], [-1,2]]
};

function rotateSRS(direction) {
    const previousRotation = currentBlock.rotation;
    const newRotation = (currentBlock.rotation + direction + 4) % 4;
    const test = direction > 0 ? `${previousRotation}>>${newRotation}` : `${newRotation}>>${previousRotation}`;

    rotateMatrix(direction > 0);

    for (let [dx, dy] of SRSTests[test]) {
        currentBlock.x += dx;
        currentBlock.y -= dy;
        if (!checkCollision()) {
            currentBlock.rotation = newRotation;
            drawGame();
            return true;
        }
        currentBlock.x -= dx;
        currentBlock.y += dy;
    }

    rotateMatrix(direction < 0);  // 回転失敗時は元に戻す
    return false;
}

このrotateSRS関数は、SRSに基づいた回転を試みます。複数の位置で回転を試し、成功すればその位置で回転を確定させます。

このように、数学的なアプローチと工夫を組み合わせることで、テトリスの核心とも言えるブロック回転を実現できるんです。最初は難しく感じるかもしれませんが、一つずつ理解していけば、きっと「なるほど!」と納得できると思います。

さて、これでテトリスの基本的な操作系が完成しました。ブロックを移動させたり、回転させたり…プレイヤーが思い通りに操作できるようになりましたね。次は、ゲームの状態管理やUI設計について考えていきましょう。スコア表示やレベルアップ、ゲームオーバー判定など、まだまだ実装すべき要素がたくさんありますよ。一緒に、もっと本格的なテトリスゲームを作り上げていきましょう!

ゲーム状態の管理とUI設計:プレイヤー体験を向上させる

ゲームの基本的な動きはできましたが、まだプレイヤーにとって「ゲーム」として楽しめる形になっていませんよね。ここからは、スコア表示やレベルアップ、ゲームオーバーの判定など、プレイヤーの体験を向上させる要素を追加していきます。これらの機能を実装することで、より本格的で楽しいテトリスゲームになりますよ。

スコア表示とレベルアップシステム:ゲームの進行状況を可視化する

まずは、スコアとレベルの表示から始めましょう。プレイヤーが自分の進捗を視覚的に確認できるようにすることで、ゲームの楽しさが倍増しますよ。

HTMLに以下のような要素を追加しましょう:

<div id="game-info">
    <div>Score: <span id="score">0</span></div>
    <div>Level: <span id="level">1</span></div>
    <div>Lines: <span id="lines">0</span></div>
</div>

そして、これらの値を更新する関数を JavaScript で実装します:

let score = 0;
let level = 1;
let lines = 0;

function updateGameInfo() {
    document.getElementById('score').textContent = score;
    document.getElementById('level').textContent = level;
    document.getElementById('lines').textContent = lines;
}

function addScore(clearedLines) {
    const lineScores = [40, 100, 300, 1200];
    score += lineScores[clearedLines - 1] * level;
    lines += clearedLines;

    if (lines >= level * 10) {
        level++;
        // レベルアップ時の処理(例:ブロックの落下速度を上げる)
        dropInterval = Math.max(100, 1000 - (level - 1) * 50);
    }

    updateGameInfo();
}

このaddScore関数は、ライン消去時に呼び出されます。消去したライン数に応じてスコアを加算し、レベルアップの判定も行います。レベルが上がると、ブロックの落下速度が速くなり、ゲームの難易度が上がっていきます。

localStorageを活用したハイスコア保存機能の実装方法

プレイヤーのモチベーションを高めるには、ハイスコアの保存機能があると効果的です。ブラウザのlocalStorageを使えば、簡単にハイスコアを保存できますよ。

function saveHighScore() {
    const highScore = localStorage.getItem('tetrisHighScore') || 0;
    if (score > highScore) {
        localStorage.setItem('tetrisHighScore', score);
        alert(`新しいハイスコア: ${score}点!`);
    }
}

function showHighScore() {
    const highScore = localStorage.getItem('tetrisHighScore') || 0;
    document.getElementById('highScore').textContent = highScore;
}

// ゲーム開始時にハイスコアを表示
showHighScore();

// ゲームオーバー時にハイスコアを保存
function gameOver() {
    // ゲームオーバーの処理
    saveHighScore();
}

この機能を使えば、プレイヤーは前回のプレイでのスコアを超えることを目指せるようになります。小さな工夫ですが、ゲームの再プレイ性を高める効果がありますよ。

さて、ここまでくれば、かなりテトリスらしくなってきましたね。でも、まだ一つ大事な要素が足りません。そう、ゲームオーバーの判定です。最後に、この部分を実装して、テトリスを完成させましょう。

ゲームオーバー判定とリスタート機能:プレイ継続性を高める実装

ゲームオーバーの判定は、新しいブロックを生成するときに行います。新しいブロックが既存のブロックと重なってしまえば、それはゲームオーバーです。

function isGameOver() {
    const newBlock = generateBlock();
    return checkCollision(newBlock);
}

function generateBlock() {
    const block = {
        shape: blocks[Math.floor(Math.random() * blocks.length)],
        x: Math.floor(boardWidth / 2) - 1,
        y: 0
    };

    if (isGameOver()) {
        gameOver();
    } else {
        return block;
    }
}

function gameOver() {
    clearInterval(gameLoop);
    saveHighScore();
    showGameOverScreen();
}

そして、ゲームオーバー画面を表示する関数を実装します:

モーダルウィンドウを使用したゲームオーバー画面の効果的な表示テクニック

ゲームオーバー画面は、モーダルウィンドウを使って表示すると効果的です。HTMLとCSSを使って、以下のようなモーダルを作成しましょう:

<div id="gameOverModal" class="modal">
    <div class="modal-content">
        <h2>ゲームオーバー</h2>
        <p>スコア: <span id="finalScore"></span></p>
        <button id="restartButton">もう一度プレイ</button>
    </div>
</div>
.modal {
    display: none;
    position: fixed;
    z-index: 1;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.4);
}

.modal-content {
    background-color: #fefefe;
    margin: 15% auto;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 500px;
    text-align: center;
}

そして、JavaScriptでモーダルの表示と再スタート機能を実装します:

function showGameOverScreen() {
    document.getElementById('finalScore').textContent = score;
    document.getElementById('gameOverModal').style.display = 'block';
}

document.getElementById('restartButton').addEventListener('click', function() {
    document.getElementById('gameOverModal').style.display = 'none';
    resetGame();
    startGame();
});

function resetGame() {
    // ゲーム状態をリセット
    score = 0;
    level = 1;
    lines = 0;
    gameBoard = Array(boardHeight).fill().map(() => Array(boardWidth).fill(0));
    updateGameInfo();
}

function startGame() {
    currentBlock = generateBlock();
    gameLoop = setInterval(dropBlock, dropInterval);
}

これで、ゲームオーバー時にスコアを表示し、再スタートボタンを押すと新しくゲームを始められるようになりました。

ここまでくれば、基本的なテトリスゲームの完成です!プレイヤーはブロックを操作し、ラインを消してスコアを稼ぎ、レベルアップしていく…そして最後にゲームオーバーになったら、自分のスコアを確認して、もう一度挑戦する。テトリスの醍醐味が詰まったゲームが完成しましたね。

最後に、ちょっとしたアドバイスを。ゲーム開発で大切なのは、プレイヤーの体験です。見た目や操作感、難易度のバランスなど、実際に遊んでみて調整を重ねていくことが大切です。友達や家族に遊んでもらって、フィードバックをもらうのも良いでしょう。

さあ、これであなただけのオリジナルテトリスの完成です!自分で作ったゲームで遊ぶ楽しさを、存分に味わってくださいね。そして、ここまで学んだ技術を活かして、さらに新しいゲームづくりにチャレンジしてみるのはどうでしょうか?プログラミングの世界は無限大。これからの冒険が、きっと素晴らしいものになると信じています。頑張ってください!

「#javascript」人気ブログランキング
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次