0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[AI Coding Challenge] 3Dテトリスゲームアプリを作ってみた!

0
Last updated at Posted at 2025-12-06

はじめに

毎日ひとつ、AIアプリをつくる。
日替わりでテーマに沿って小さなアプリを開発します。Claudeで。

この記事は :calendar_spiral: AI Code Challenge Advent Calender 2025 の 7 日目の記事です!

:arrow_left: 6日目の記事:3Dブロック崩しゲームアプリを作ってみた!
  8日目の記事:AIプロファイリングアプリを作ってみた! :arrow_right:

AI アプリ

概要

:speech_balloon:
ゲームを作りたい。一番やりこめるのはテトリス。
でも単純すぎるからもっと複雑にしたい。

3Dのテトリスはどうだろう!? :grinning:

AI 実装

Claude Desktopでプロンプトを与えて実装してもらいます。

:boy_tone1: プロンプト:

3Dテトリスゲームアプリをつくって

:robot:

3Dテトリスゲームを作成しました!

:boy_tone1: プロンプト:

奥行きも再現して

:robot:

奥行きのある本格的な3Dテトリスに改良しました!

:boy_tone1: プロンプト:

見た目をモダナイズして

:robot:

ゲームの見た目を大幅にモダナイズしました!

完成したアプリ

できました!

  :large_blue_diamond: 3Dテトリス

:point_down: のリンクからアプリを確認できます:sparkles:

使い方:

  • ← → : 左右移動(X軸)
  • A D : 奥行き移動(Z軸)
  • ↓ : 下に移動(Y軸)
  • W : X軸回転
  • Q E : Y軸回転
  • R : Z軸回転
  • スペース : 一気に落下

こんなイメージです。

image.png

あとは実際にゲームしてみてください!
PCでお楽しみください!(矢印キーを使うため)

プログラム解説

ポイントとなるプログラムを解説します。

        // シーン設定
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x0a0a1a);
        scene.fog = new THREE.Fog(0x0a0a1a, 30, 60);
        
        // 3Dテトリミノの形状 (x, y, z座標の配列)
        const SHAPES = [
            // I字型(縦長)
            [[0,0,0], [0,1,0], [0,2,0], [0,3,0]],
            // O字型(立方体)
            [[0,0,0], [1,0,0], [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1]],
            // T字型
            [[0,0,0], [1,0,0], [2,0,0], [1,1,0]],
            // L字型
            [[0,0,0], [0,1,0], [0,2,0], [1,2,0]],
            // J字型
            [[1,0,0], [1,1,0], [1,2,0], [0,2,0]],
            // S字型
            [[0,0,0], [1,0,0], [1,1,0], [2,1,0]],
            // Z字型
            [[1,0,0], [2,0,0], [0,1,0], [1,1,0]],
            // 3D L字型
            [[0,0,0], [0,1,0], [0,0,1], [0,1,1]],
            // 3Dコーナー
            [[0,0,0], [1,0,0], [0,1,0], [0,0,1]],
            // 3D十字
            [[0,0,0], [1,0,0], [2,0,0], [1,0,1], [1,0,-1]],
        ];
                
        createBoardFrame();

        // ブロックを作成(3D位置対応)- モダンデザイン
        function createBlock(x, y, z, color) {
            const geometry = new THREE.BoxGeometry(BLOCK_SIZE * 0.9, BLOCK_SIZE * 0.9, BLOCK_SIZE * 0.9);
            const material = new THREE.MeshPhongMaterial({ 
                color: color,
                shininess: 150,
                specular: 0xffffff,
                emissive: color,
                emissiveIntensity: 0.2
            });
            const block = new THREE.Mesh(geometry, material);
            block.position.set(x + BLOCK_SIZE / 2, y + BLOCK_SIZE / 2, z + BLOCK_SIZE / 2);
            block.castShadow = true;
            block.receiveShadow = true;
            
            // エッジを追加(より明るく)
            const edges = new THREE.EdgesGeometry(geometry);
            const line = new THREE.LineSegments(
                edges,
                new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2, opacity: 0.8, transparent: true })
            );
            block.add(line);
            
            return block;
        }
        
        // ピースデータからピースを作成
        function createPieceFromData(pieceData) {
            const piece = {
                shape: pieceData.shape,
                color: pieceData.color,
                blocks: []
            };
            
            pieceData.shape.forEach(([x, y, z]) => {
                const block = createBlock(0, 0, 0, pieceData.color);
                piece.blocks.push({ block, offsetX: x, offsetY: y, offsetZ: z });
                scene.add(block);
            });
            
            return piece;
        }
        
        // 次のピースのプレビューを更新
        function updateNextPiecePreview() {
            // 既存のプレビューをクリア
            while(previewScene.children.length > 2) { // ライト2つを残す
                previewScene.remove(previewScene.children[2]);
            }
            
            if (!nextPiece) return;
            
            // 中心点を計算
            let centerX = 0, centerY = 0, centerZ = 0;
            nextPiece.shape.forEach(([x, y, z]) => {
                centerX += x;
                centerY += y;
                centerZ += z;
            });
            centerX /= nextPiece.shape.length;
            centerY /= nextPiece.shape.length;
            centerZ /= nextPiece.shape.length;
            
            // プレビュー用ブロックを作成(モダンデザイン)
            nextPiece.shape.forEach(([x, y, z]) => {
                const geometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
                const material = new THREE.MeshPhongMaterial({ 
                    color: nextPiece.color,
                    shininess: 150,
                    emissive: nextPiece.color,
                    emissiveIntensity: 0.3
                });
                const block = new THREE.Mesh(geometry, material);
                
                // 中心を原点に
                block.position.set(
                    (x - centerX) * 0.9,
                    (y - centerY) * 0.9,
                    (z - centerZ) * 0.9
                );
                
                const edges = new THREE.EdgesGeometry(geometry);
                const line = new THREE.LineSegments(
                    edges,
                    new THREE.LineBasicMaterial({ color: 0xffffff, opacity: 0.6, transparent: true })
                );
                block.add(line);
                
                previewScene.add(block);
            });
            
            // プレビューをレンダリング
            previewRenderer.render(previewScene, previewCamera);
        }
        
        // 衝突判定(3D版)
        function checkCollision(piece, pos) {
            if (!piece) return true;
            
            for (let i = 0; i < piece.blocks.length; i++) {
                const { offsetX, offsetY, offsetZ } = piece.blocks[i];
                const newX = pos.x + offsetX;
                const newY = pos.y + offsetY;
                const newZ = pos.z + offsetZ;
                
                // 境界チェック
                if (newX < 0 || newX >= BOARD_WIDTH || 
                    newY >= BOARD_HEIGHT || 
                    newZ < 0 || newZ >= BOARD_DEPTH) {
                    return true;
                }
                
                // ボードとの衝突チェック
                if (newY >= 0 && board[newY][newZ][newX]) {
                    return true;
                }
            }
            return false;
        }
        
        // ラインをクリア(3D版 - 平面をクリア)
        function clearLines() {
            let linesCleared = 0;
            
            for (let y = BOARD_HEIGHT - 1; y >= 0; y--) {
                // 平面が完全に埋まっているかチェック
                let isFull = true;
                for (let z = 0; z < BOARD_DEPTH; z++) {
                    for (let x = 0; x < BOARD_WIDTH; x++) {
                        if (board[y][z][x] === null) {
                            isFull = false;
                            break;
                        }
                    }
                    if (!isFull) break;
                }
                
                if (isFull) {
                    linesCleared++;
                    
                    // ブロックを削除
                    for (let z = 0; z < BOARD_DEPTH; z++) {
                        for (let x = 0; x < BOARD_WIDTH; x++) {
                            if (board[y][z][x]) {
                                scene.remove(board[y][z][x]);
                            }
                        }
                    }
                    
                    // 上のブロックを下に移動
                    for (let moveY = y; moveY > 0; moveY--) {
                        for (let z = 0; z < BOARD_DEPTH; z++) {
                            for (let x = 0; x < BOARD_WIDTH; x++) {
                                board[moveY][z][x] = board[moveY - 1][z][x];
                                if (board[moveY][z][x]) {
                                    board[moveY][z][x].position.y -= BLOCK_SIZE;
                                }
                            }
                        }
                    }
                    
                    // 最上段をクリア
                    for (let z = 0; z < BOARD_DEPTH; z++) {
                        for (let x = 0; x < BOARD_WIDTH; x++) {
                            board[0][z][x] = null;
                        }
                    }
                    
                    y++; // 同じ行を再チェック
                }
            }
            
            if (linesCleared > 0) {
                lines += linesCleared;
                score += linesCleared * linesCleared * 200 * level; // 3Dなのでスコア倍増
                level = Math.floor(lines / 5) + 1; // レベルアップを早めに
                dropSpeed = Math.max(100, 1000 - (level - 1) * 100);
                
                updateUI();
            }
        }
        
        // 新しいピースを生成(3D版)
        function spawnNewPiece() {
            // nextPieceがあればそれを使用、なければ新規作成
            if (nextPiece) {
                currentPiece = createPieceFromData(nextPiece);
            } else {
                currentPiece = createNewPiece();
            }
            
            // 次のピースを準備
            nextPiece = createPieceData();
            updateNextPiecePreview();
            
            // 中央に配置
            currentPosition = { 
                x: Math.floor(BOARD_WIDTH / 2) - 1, 
                y: 0,
                z: Math.floor(BOARD_DEPTH / 2) - 1
            };
            
            if (checkCollision(currentPiece, currentPosition)) {
                endGame();
            } else {
                updatePiecePosition();
            }
        }
        
        // ゲームオーバー
        function endGame() {
            gameOver = true;
            document.getElementById('final-score').textContent = score;
            document.getElementById('final-lines').textContent = lines;
            document.getElementById('game-over').style.display = 'block';
        }
        
        // 3D回転関数
        function rotateX(piece) {
            piece.blocks.forEach(block => {
                const y = block.offsetY;
                const z = block.offsetZ;
                block.offsetY = -z;
                block.offsetZ = y;
            });
        }
        
        function rotateY(piece) {
            piece.blocks.forEach(block => {
                const x = block.offsetX;
                const z = block.offsetZ;
                block.offsetX = z;
                block.offsetZ = -x;
            });
        }
        
        function rotateZ(piece) {
            piece.blocks.forEach(block => {
                const x = block.offsetX;
                const y = block.offsetY;
                block.offsetX = -y;
                block.offsetY = x;
            });
        }
        
        // ピースを回転(3D版)
        function rotatePiece(axis) {
            if (!currentPiece || gameOver) return;
            
            // 回転前の状態を保存
            const oldOffsets = currentPiece.blocks.map(b => ({
                offsetX: b.offsetX,
                offsetY: b.offsetY,
                offsetZ: b.offsetZ
            }));
            
            // 指定軸で回転
            if (axis === 'x') rotateX(currentPiece);
            else if (axis === 'y') rotateY(currentPiece);
            else if (axis === 'z') rotateZ(currentPiece);
            
            // 衝突チェック
            if (checkCollision(currentPiece, currentPosition)) {
                // 衝突する場合は元に戻す
                currentPiece.blocks.forEach((block, i) => {
                    block.offsetX = oldOffsets[i].offsetX;
                    block.offsetY = oldOffsets[i].offsetY;
                    block.offsetZ = oldOffsets[i].offsetZ;
                });
            } else {
                updatePiecePosition();
            }
        }

        // ゲームループ
        function animate(currentTime) {
            requestAnimationFrame(animate);
            
            if (!gameOver && currentPiece) {
                if (currentTime - lastDropTime > dropSpeed) {
                    if (!checkCollision(currentPiece, { x: currentPosition.x, y: currentPosition.y + 1, z: currentPosition.z })) {
                        currentPosition.y++;
                        updatePiecePosition();
                    } else {
                        lockPiece();
                    }
                    lastDropTime = currentTime;
                }
            }
            
            // カメラを回転
            const radius = 25;
            camera.position.x = Math.sin(currentTime * 0.0002) * radius + BOARD_WIDTH / 2;
            camera.position.z = Math.cos(currentTime * 0.0002) * radius + BOARD_DEPTH / 2;
            camera.lookAt(BOARD_WIDTH / 2, BOARD_HEIGHT / 2, BOARD_DEPTH / 2);
            
            renderer.render(scene, camera);
        }
        
        // ゲーム開始
        spawnNewPiece();
        animate(0);
  • Three.js を使って3D空間を実装。ここにブロックやライトを配置。
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a);
scene.fog = new THREE.Fog(0x0a0a1a, 30, 60);
  • BoxGeometry で立方体の形状を表現し、MeshPhongMaterial で光沢のある材質を表現します。
function createBlock(x, y, z, color) {
    const geometry = new THREE.BoxGeometry(BLOCK_SIZE * 0.9, ...);
    const material = new THREE.MeshPhongMaterial({ color, shininess: 150, ... });
    const block = new THREE.Mesh(geometry, material);
    block.position.set(...);
    block.castShadow = true;
    block.receiveShadow = true;

    // エッジを追加
    const edges = new THREE.EdgesGeometry(geometry);
    const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
    block.add(line);

    return block;
}
  • カメラをゆっくり回転させて立体的な見栄えを演出しています。
function animate(currentTime) {
    requestAnimationFrame(animate);
    ...
    // カメラを回転
    camera.position.x = Math.sin(currentTime * 0.0002) * radius + BOARD_WIDTH / 2;
    camera.position.z = Math.cos(currentTime * 0.0002) * radius + BOARD_DEPTH / 2;
    camera.lookAt(BOARD_WIDTH / 2, BOARD_HEIGHT / 2, BOARD_DEPTH / 2);
    renderer.render(scene, camera);
}

おわりに

  • テトリスを3Dにして難易度をかなり上げてみました。
    3Dの表示もAI Codingで簡単にできました。
  • 作っておいてまだレベル1もクリアしていないので💦 お楽しみください!

AI で楽しいアプリ開発を!!

この記事は :calendar_spiral: AI Code Challenge Advent Calender 2025 の 7 日目の記事です!

:arrow_left: 6日目の記事:3Dブロック崩しゲームアプリを作ってみた!
  8日目の記事:AIプロファイリングアプリを作ってみた! :arrow_right:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?