🔬 AI発明ギャラリー kizu 街破壊ゲーム
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>City Smasher 3D - Easy Mode</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <style>
        body { margin: 0; overflow: hidden; background-color: #1a1a1a; touch-action: none; }
        #game-canvas { display: block; width: 100vw; height: 100vh; }
        .ui-panel { pointer-events: none; }
        .interactive { pointer-events: auto; }
    </style>
</head>
<body>

    <canvas id="game-canvas"></canvas>

    <!-- UI Layer -->
    <div class="absolute inset-0 ui-panel flex flex-col justify-between p-6">
        <div class="flex justify-between items-start">
            <div class="bg-black/60 text-white p-4 rounded-xl border-2 border-orange-500 shadow-lg">
                <div class="text-xs uppercase tracking-widest text-orange-400">破壊スコア</div>
                <div id="score" class="text-3xl font-black">0</div>
            </div>
            <div class="bg-black/60 text-white p-4 rounded-xl border-2 border-blue-500 shadow-lg">
                <div class="text-xs uppercase tracking-widest text-blue-400">残り時間</div>
                <div id="timer" class="text-3xl font-black text-center">60</div>
            </div>
        </div>

        <!-- Start Screen -->
        <div id="start-screen" class="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-md interactive z-50">
            <div class="text-center p-8 bg-gray-900 border-4 border-orange-600 rounded-3xl shadow-2xl max-w-sm">
                <h1 class="text-5xl font-black text-orange-500 mb-2 italic">CITY SMASH</h1>
                <p class="text-gray-400 mb-8 uppercase tracking-tighter">かんたん操作モード</p>
                <div class="space-y-4 mb-8 text-left text-sm text-gray-300 bg-black/40 p-4 rounded-lg">
                    <p>🕹️ <b>操作:</b> マウス・指を左右に動かすだけ!</p>
                    <p>🚀 <b>自動移動:</b> キャラクターは自動で進みます</p>
                </div>
                <button id="start-btn" class="w-full bg-orange-600 hover:bg-orange-500 text-white font-bold py-4 rounded-xl text-xl transition-all transform hover:scale-105 active:scale-95 shadow-lg">
                    破壊を開始する
                </button>
            </div>
        </div>

        <!-- Game Over Screen -->
        <div id="game-over-screen" class="absolute inset-0 flex items-center justify-center bg-red-900/90 backdrop-blur-lg hidden interactive z-50">
            <div class="text-center text-white">
                <h2 class="text-6xl font-black mb-2">TIME UP!</h2>
                <p class="text-2xl mb-8">最終破壊スコア: <span id="final-score" class="text-yellow-400">0</span></p>
                <button id="restart-btn" class="bg-white text-red-600 font-bold py-4 px-12 rounded-full text-xl hover:bg-gray-200 transition-all">
                    もう一度暴れる
                </button>
            </div>
        </div>
    </div>

    <script>
        let scene, camera, renderer, monster, buildings = [], debris = [];
        let score = 0, timeLeft = 60, isPlaying = false;
        let targetX = 0;
        let monsterAutoSpeed = 0.2; // 自動前進速度
        let clock = new THREE.Clock();

        const config = {
            citySize: 150,
            buildingCount: 100,
            monsterLerp: 0.1 // 左右移動のスムーズさ
        };

        function init() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0a0f);
            scene.fog = new THREE.Fog(0x0a0a0f, 30, 100);

            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            // カメラを少し高く、遠くに配置して見やすく
            camera.position.set(0, 20, 30);

            renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('game-canvas'), antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;

            // Lights
            const ambient = new THREE.AmbientLight(0xffffff, 0.5);
            scene.add(ambient);

            const sun = new THREE.DirectionalLight(0xffaa00, 1.2);
            sun.position.set(50, 100, 50);
            sun.castShadow = true;
            sun.shadow.mapSize.width = 1024;
            sun.shadow.mapSize.height = 1024;
            scene.add(sun);

            // Ground
            const groundGeo = new THREE.PlaneGeometry(1000, 1000);
            const groundMat = new THREE.MeshPhongMaterial({ color: 0x111111 });
            const ground = new THREE.Mesh(groundGeo, groundMat);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // Grid
            const grid = new THREE.GridHelper(1000, 100, 0x4444ff, 0x222222);
            scene.add(grid);

            createMonster();
            createCity();

            window.addEventListener('mousemove', onInputMove);
            window.addEventListener('touchmove', onInputMove);
            window.addEventListener('resize', onWindowResize);
            
            document.getElementById('start-btn').onclick = startGame;
            document.getElementById('restart-btn').onclick = startGame;

            animate();
        }

        function createMonster() {
            const group = new THREE.Group();
            
            // 本体
            const geo = new THREE.SphereGeometry(2.5, 32, 32);
            const mat = new THREE.MeshPhongMaterial({ 
                color: 0xff4400, 
                emissive: 0xff2200,
                emissiveIntensity: 0.6 
            });
            const body = new THREE.Mesh(geo, mat);
            body.castShadow = true;
            group.add(body);

            // 目の光
            const eyeGeo = new THREE.SphereGeometry(0.5, 16, 16);
            const eyeMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
            const eyeL = new THREE.Mesh(eyeGeo, eyeMat);
            eyeL.position.set(1, 1, -1.5);
            group.add(eyeL);
            const eyeR = eyeL.clone();
            eyeR.position.x = -1;
            group.add(eyeR);

            monster = group;
            monster.position.y = 2.5;
            scene.add(monster);
        }

        function createCity() {
            const colors = [0x555555, 0x777777, 0x444466, 0x333333];
            
            for (let i = 0; i < config.buildingCount; i++) {
                const w = 4 + Math.random() * 4;
                const h = 10 + Math.random() * 20;
                const d = 4 + Math.random() * 4;
                
                const group = new THREE.Group();
                // プレイヤーの進行方向(Zマイナス)に沿って配置
                group.position.x = (Math.random() - 0.5) * 80;
                group.position.z = -Math.random() * 500 - 20; 
                
                const floors = Math.max(2, Math.floor(h / 3));
                const floorHeight = h / floors;
                const buildingColor = colors[Math.floor(Math.random() * colors.length)];
                
                for(let j = 0; j < floors; j++) {
                    const floorGeo = new THREE.BoxGeometry(w, floorHeight - 0.2, d);
                    const floorMat = new THREE.MeshPhongMaterial({ color: buildingColor });
                    const floor = new THREE.Mesh(floorGeo, floorMat);
                    floor.position.y = (j * floorHeight) + floorHeight/2;
                    floor.castShadow = true;
                    floor.receiveShadow = true;
                    
                    // 窓
                    if (Math.random() > 0.3) {
                        const winGeo = new THREE.BoxGeometry(w + 0.1, 0.4, d + 0.1);
                        const winMat = new THREE.MeshBasicMaterial({ color: 0xffffaa });
                        const win = new THREE.Mesh(winGeo, winMat);
                        floor.add(win);
                    }
                    
                    group.add(floor);
                }
                
                scene.add(group);
                buildings.push({
                    mesh: group,
                    active: true,
                    radius: (w + d) / 4 + 1.5, // 判定用の半径
                    floors: group.children.length
                });
            }
        }

        function startGame() {
            score = 0;
            timeLeft = 60;
            isPlaying = true;
            monsterAutoSpeed = 0.2;
            document.getElementById('score').innerText = score;
            document.getElementById('start-screen').classList.add('hidden');
            document.getElementById('game-over-screen').classList.add('hidden');
            
            buildings.forEach(b => scene.remove(b.mesh));
            debris.forEach(d => scene.remove(d.mesh));
            buildings = [];
            debris = [];
            createCity();
            
            monster.position.set(0, 2.5, 0);
            targetX = 0;

            const timerInt = setInterval(() => {
                if (!isPlaying) {
                    clearInterval(timerInt);
                    return;
                }
                timeLeft--;
                document.getElementById('timer').innerText = timeLeft;
                // 徐々にスピードアップして爽快感を出す
                monsterAutoSpeed += 0.005;

                if (timeLeft <= 0) {
                    endGame();
                    clearInterval(timerInt);
                }
            }, 1000);
        }

        function endGame() {
            isPlaying = false;
            document.getElementById('game-over-screen').classList.remove('hidden');
            document.getElementById('final-score').innerText = score;
        }

        function onInputMove(e) {
            if (!isPlaying) return;
            const clientX = e.touches ? e.touches[0].clientX : e.clientX;
            const x = (clientX / window.innerWidth) * 2 - 1;
            targetX = x * 45; // 左右移動範囲
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function updatePhysics() {
            if (!isPlaying) return;

            // 自動前進 + 左右移動
            monster.position.z -= monsterAutoSpeed;
            monster.position.x = THREE.MathUtils.lerp(monster.position.x, targetX, config.monsterLerp);
            
            // 怪獣を進行方向に少し傾ける
            monster.rotation.z = (monster.position.x - targetX) * 0.05;
            monster.rotation.x = -0.1;

            // カメラの追従
            camera.position.x = monster.position.x * 0.5;
            camera.position.z = monster.position.z + 35;
            camera.lookAt(monster.position.x, 2, monster.position.z - 10);

            // 衝突判定
            for (let i = buildings.length - 1; i >= 0; i--) {
                const b = buildings[i];
                const dx = monster.position.x - b.mesh.position.x;
                const dz = monster.position.z - b.mesh.position.z;
                const distSq = dx*dx + dz*dz;

                // 半径に基づいた円形判定(処理が速い)
                if (distSq < b.radius * b.radius + 10) {
                    explodeBuilding(b);
                    buildings.splice(i, 1);
                    score += b.floors * 10;
                    document.getElementById('score').innerText = score;
                }
                
                // 通り過ぎたビルを再配置(無限ループ用)
                if (b.mesh.position.z > monster.position.z + 20) {
                    b.mesh.position.z -= 500;
                    b.mesh.position.x = (Math.random() - 0.5) * 80;
                }
            }

            // デブリ
            for (let i = debris.length - 1; i >= 0; i--) {
                const d = debris[i];
                d.mesh.position.add(d.velocity);
                d.velocity.y -= 0.02;
                d.mesh.rotation.x += d.rot.x;

                if (d.mesh.position.y < 0.5) {
                    d.mesh.position.y = 0.5;
                    d.velocity.set(0,0,0);
                    d.life--;
                    if(d.life < 0) {
                        scene.remove(d.mesh);
                        debris.splice(i, 1);
                    }
                }
            }
        }

        function explodeBuilding(building) {
            const children = [...building.mesh.children];
            children.forEach((child, idx) => {
                const worldPos = new THREE.Vector3();
                child.getWorldPosition(worldPos);
                
                scene.add(child);
                child.position.copy(worldPos);
                
                debris.push({
                    mesh: child,
                    velocity: new THREE.Vector3(
                        (Math.random() - 0.5) * 0.8,
                        Math.random() * 0.6 + 0.2,
                        -Math.random() * 0.5
                    ),
                    rot: new THREE.Vector3(Math.random()*0.2, Math.random()*0.2, 0),
                    life: 100
                });
            });
            scene.remove(building.mesh);
        }

        function animate() {
            requestAnimationFrame(animate);
            updatePhysics();
            renderer.render(scene, camera);
        }

        init();
    </script>
</body>
</html>
✨ ピックアップ

街破壊ゲーム

by kizu
🤖 使用したAI
Gemini
👁 12 回閲覧
📅 投稿:2026年3月21日 🔄 更新:2026年4月11日
応援スタンプを押してみよう!