🔬 AI発明ギャラリー o-yuto 筋斗雲ゲーム
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>筋斗雲 3Dレーシング</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #87CEEB; /* Sky blue */
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            touch-action: none;
        }

        #game-canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }

        #ui-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }

        .hud {
            display: flex;
            justify-content: space-between;
            padding: 20px;
            color: white;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
            font-size: 1.2rem;
            font-weight: bold;
        }

        #level-display {
            color: #ffeb3b;
        }

        #screens {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            pointer-events: auto;
            background: rgba(0, 0, 0, 0.4);
            backdrop-filter: blur(4px);
            transition: opacity 0.3s ease;
        }

        .screen-content {
            background: white;
            padding: 30px;
            border-radius: 20px;
            text-align: center;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            max-width: 500px;
            width: 90%;
        }

        h1 {
            color: #ff9800;
            margin-top: 0;
            font-size: 1.8rem;
            text-shadow: 1px 1px 0px #d87b00;
        }

        .char-selector {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin: 20px 0;
        }

        .char-option {
            flex: 1;
            padding: 10px;
            border: 3px solid #ddd;
            border-radius: 15px;
            cursor: pointer;
            transition: all 0.2s;
            background: #f9f9f9;
        }

        .char-option.selected {
            border-color: #ff9800;
            background: #fff3e0;
            transform: scale(1.05);
        }

        .char-preview {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            margin: 0 auto 10px;
            border: 2px solid rgba(0,0,0,0.1);
        }

        .btn {
            background: #ffeb3b;
            color: #e65100;
            border: 2px solid #ffc107;
            padding: 15px 30px;
            font-size: 1.2rem;
            font-weight: bold;
            border-radius: 30px;
            cursor: pointer;
            margin-top: 10px;
            transition: all 0.2s;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }

        .btn:hover {
            background: #ffc107;
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0,0,0,0.2);
        }

        .hidden {
            opacity: 0;
            pointer-events: none !important;
        }

        #lives {
            color: #ff5252;
        }

        #damage-flash {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background: red;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.1s;
        }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>

    <div id="damage-flash"></div>

    <div id="ui-layer">
        <div class="hud">
            <div id="level-display">LEVEL: <span id="level-val">1</span></div>
            <div>SCORE: <span id="score-val">0</span></div>
            <div id="lives">♥♥♥</div>
            <div>SPEED: <span id="speed-val">0.7</span>x</div>
        </div>
    </div>

    <div id="screens">
        <div class="screen-content" id="start-screen">
            <h1>筋斗雲レーシング</h1>
            <p>新しいキャラクターを選んでね!</p>
            
            <div class="char-selector">
                <div class="char-option selected" data-char="raijin">
                    <div class="char-preview" style="background: #333333; border-color: #ffff00;"></div>
                    <div>雷神</div>
                </div>
                <div class="char-option" data-char="peach">
                    <div class="char-preview" style="background: #ffb7c5;"></div>
                    <div>桃幻</div>
                </div>
                <div class="char-option" data-char="dark">
                    <div class="char-preview" style="background: #1a1a1a; border-color: #4b0082;"></div>
                    <div>漆黒</div>
                </div>
            </div>

            <button class="btn" id="start-btn">ゲームスタート</button>
            <p style="font-size: 0.9rem; margin-top: 15px;">操作: 矢印キー / スワイプ</p>
        </div>

        <div class="screen-content hidden" id="game-over-screen">
            <h1 style="color: #f44336;">GAME OVER</h1>
            <p>最終スコア: <strong id="final-score" style="font-size:1.5rem;">0</strong></p>
            <button class="btn" id="restart-btn">もう一度乗る</button>
        </div>
    </div>

    <script>
        // --- State Variables ---
        let scene, camera, renderer;
        let playerGroup;
        let activeObjects = []; 
        let bgClouds = []; 
        let gameStarted = false;
        let isGameOver = false;
        
        let score = 0;
        let lives = 3;
        let baseSpeed = 0.7; 
        let currentSpeed = 0;
        let distanceTraveled = 0;
        let invulnerableTime = 0; 
        let currentLevel = 1;
        
        let selectedChar = 'raijin';
        const charConfigs = {
            raijin: { cloud: 0x333333, body: 0xffff00, head: 0xffe0bd },
            peach: { cloud: 0xffb7c5, body: 0xff69b4, head: 0xffe0bd },
            dark: { cloud: 0x1a1a1a, body: 0x4b0082, head: 0xffe0bd }
        };

        let targetBgColor = new THREE.Color(0x87CEEB);
        let targetFogColor = new THREE.Color(0x87CEEB);

        const keys = { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false, w: false, a: false, s: false, d: false };
        const touchInput = { active: false, x: 0, y: 0, startX: 0, startY: 0, playerStartX: 0, playerStartY: 0 };

        const BOUNDARY_X = 12; 
        const BOUNDARY_Y_MAX = 8; 
        const BOUNDARY_Y_MIN = -2; 
        const SPAWN_Z = -150; 
        const DESPAWN_Z = 20; 

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

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 250);
            camera.position.set(0, 3, 10);

            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            document.body.appendChild(renderer.domElement);

            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
            scene.add(ambientLight);

            const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
            dirLight.position.set(20, 50, 20);
            dirLight.castShadow = true;
            scene.add(dirLight);

            createPlayer();

            for(let i=0; i<10; i++) {
                spawnBackgroundCloud(true);
            }

            // キャラクター選択のリスナー
            document.querySelectorAll('.char-option').forEach(opt => {
                opt.addEventListener('click', () => {
                    document.querySelectorAll('.char-option').forEach(o => o.classList.remove('selected'));
                    opt.classList.add('selected');
                    selectedChar = opt.dataset.char;
                    updatePlayerSkin();
                });
            });

            window.addEventListener('resize', onWindowResize);
            document.addEventListener('keydown', e => handleKeyDown(e));
            document.addEventListener('keyup', e => handleKeyUp(e));
            
            renderer.domElement.addEventListener('touchstart', handleTouchStart, {passive: false});
            renderer.domElement.addEventListener('touchmove', handleTouchMove, {passive: false});
            renderer.domElement.addEventListener('touchend', handleTouchEnd);

            document.getElementById('start-btn').addEventListener('click', startGame);
            document.getElementById('restart-btn').addEventListener('click', resetGame);

            animate();
        }

        function createPlayer() {
            if(playerGroup) scene.remove(playerGroup);
            playerGroup = new THREE.Group();
            
            const config = charConfigs[selectedChar];
            const cloudMaterial = new THREE.MeshStandardMaterial({ 
                color: config.cloud, 
                roughness: 0.8,
                flatShading: true
            });

            const cloudGeometries = [
                { r: 1.0, x: 0, y: 0, z: 0 },
                { r: 0.8, x: -0.9, y: 0.1, z: 0 },
                { r: 0.8, x: 0.9, y: 0.1, z: 0 },
                { r: 0.7, x: -0.5, y: -0.2, z: 0.8 },
                { r: 0.7, x: 0.5, y: -0.2, z: 0.8 },
                { r: 0.9, x: 0, y: 0.2, z: -0.8 },
                { r: 0.6, x: -1.2, y: 0, z: -0.4 },
                { r: 0.6, x: 1.2, y: 0, z: -0.4 }
            ];

            cloudGeometries.forEach(data => {
                const geo = new THREE.SphereGeometry(data.r, 16, 16);
                const mesh = new THREE.Mesh(geo, cloudMaterial);
                mesh.position.set(data.x, data.y, data.z);
                mesh.castShadow = true;
                mesh.name = "cloud_part";
                playerGroup.add(mesh);
            });

            const riderBodyGeo = new THREE.CylinderGeometry(0.3, 0.4, 1.2, 8);
            const riderMat = new THREE.MeshStandardMaterial({ color: config.body });
            const riderBody = new THREE.Mesh(riderBodyGeo, riderMat);
            riderBody.position.set(0, 1.2, -0.2);
            riderBody.rotation.x = 0.2;
            riderBody.castShadow = true;
            riderBody.name = "rider_body";
            playerGroup.add(riderBody);

            const riderHeadGeo = new THREE.SphereGeometry(0.35, 16, 16);
            const headMat = new THREE.MeshStandardMaterial({ color: config.head });
            const riderHead = new THREE.Mesh(riderHeadGeo, headMat);
            riderHead.position.set(0, 2.0, -0.4);
            riderHead.castShadow = true;
            playerGroup.add(riderHead);

            scene.add(playerGroup);
        }

        function updatePlayerSkin() {
            const config = charConfigs[selectedChar];
            playerGroup.children.forEach(child => {
                if(child.name === "cloud_part") {
                    child.material.color.setHex(config.cloud);
                } else if(child.name === "rider_body") {
                    child.material.color.setHex(config.body);
                }
            });
        }

        function spawnBackgroundCloud(randomZ = false) {
            const size = Math.random() * 3 + 2;
            const geo = new THREE.SphereGeometry(size, 8, 8);
            const mat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 });
            const cloud = new THREE.Mesh(geo, mat);
            const x = (Math.random() - 0.5) * 100;
            const y = (Math.random() - 0.5) * 40 - 10;
            const z = randomZ ? (Math.random() * -150 - 20) : SPAWN_Z;
            if (Math.abs(x) < 20) cloud.position.x += (x > 0 ? 20 : -20);
            cloud.position.set(x, y, z);
            cloud.scale.set(1, 0.6, 1.2);
            scene.add(cloud);
            bgClouds.push(cloud);
        }

        function spawnRing(x, y, z) {
            const geo = new THREE.TorusGeometry(2.5, 0.3, 8, 24);
            const mat = new THREE.MeshStandardMaterial({ color: 0xffff00, emissive: 0xaa8800, metalness: 0.8, roughness: 0.2 });
            const ring = new THREE.Mesh(geo, mat);
            ring.position.set(x, y, z);
            ring.userData = { type: 'ring', passed: false, rotationSpeed: Math.random() * 0.05 + 0.02 };
            scene.add(ring);
            activeObjects.push(ring);
        }

        function spawnObstacle(x, y, z) {
            const group = new THREE.Group();
            group.position.set(x, y, z);
            const mat = new THREE.MeshStandardMaterial({ color: 0x444455, roughness: 0.9, flatShading: true });
            for(let i=0; i<5; i++) {
                const r = Math.random() * 1.0 + 1.0;
                const geo = new THREE.SphereGeometry(r, 8, 8);
                const mesh = new THREE.Mesh(geo, mat);
                mesh.position.set((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2);
                group.add(mesh);
            }
            const boltGeo = new THREE.ConeGeometry(0.2, 1.5, 4);
            const boltMat = new THREE.MeshBasicMaterial({ color: 0xffff00 });
            for(let i=0; i<3; i++) {
                const bolt = new THREE.Mesh(boltGeo, boltMat);
                bolt.position.set((Math.random()-0.5)*2, -1, (Math.random()-0.5)*2);
                bolt.rotation.x = Math.PI;
                group.add(bolt);
            }
            group.userData = { type: 'obstacle', hit: false, radius: 2.0 };
            scene.add(group);
            activeObjects.push(group);
        }

        let spawnTimer = 0;
        function updateSpawning(delta) {
            spawnTimer += delta * baseSpeed;
            const baseInterval = 45;
            const spawnInterval = Math.max(18, baseInterval - currentLevel * 7); 
            
            if (spawnTimer > spawnInterval) {
                spawnTimer = 0;
                const pattern = Math.floor(Math.random() * 3);
                const startX = (Math.random() - 0.5) * BOUNDARY_X * 1.5;
                const startY = Math.random() * (BOUNDARY_Y_MAX - BOUNDARY_Y_MIN) + BOUNDARY_Y_MIN;
                
                if (pattern === 0) {
                    for(let i=0; i<3; i++) spawnRing(startX, startY, SPAWN_Z - i * 15);
                    spawnObstacle(startX - 6, startY, SPAWN_Z - 15);
                    spawnObstacle(startX + 6, startY, SPAWN_Z - 15);
                } else if (pattern === 1) {
                    for(let i=0; i<5; i++) spawnRing(startX + Math.sin(i)*4, startY + Math.cos(i)*2, SPAWN_Z - i * 10);
                } else {
                    spawnObstacle(startX - 5, startY, SPAWN_Z);
                    spawnRing(startX, startY, SPAWN_Z);
                    spawnObstacle(startX + 5, startY, SPAWN_Z);
                }

                const additionalObstacles = currentLevel - 1; 
                for(let i=0; i < (1 + additionalObstacles); i++) {
                    const ox = (Math.random() - 0.5) * BOUNDARY_X * 2;
                    const oy = Math.random() * (BOUNDARY_Y_MAX - BOUNDARY_Y_MIN) + BOUNDARY_Y_MIN;
                    const oz = SPAWN_Z - Math.random() * 50;
                    if (Math.abs(ox - startX) > 4 || Math.abs(oy - startY) > 4) spawnObstacle(ox, oy, oz);
                }
                
                if (Math.random() < 0.5 + (currentLevel * 0.1)) spawnBackgroundCloud(false);
            }
        }

        function startGame() {
            document.getElementById('screens').classList.add('hidden');
            score = 0;
            lives = 3;
            baseSpeed = 0.7; 
            distanceTraveled = 0;
            invulnerableTime = 0;
            currentLevel = 1;
            targetBgColor.setHex(0x87CEEB);
            targetFogColor.setHex(0x87CEEB);
            scene.background.setHex(0x87CEEB);
            scene.fog.color.setHex(0x87CEEB);
            playerGroup.position.set(0, 2, 0);
            playerGroup.rotation.set(0, 0, 0);
            activeObjects.forEach(obj => scene.remove(obj));
            activeObjects = [];
            updateHUD();
            gameStarted = true;
            isGameOver = false;
            currentSpeed = 0;
        }

        function resetGame() {
            document.getElementById('game-over-screen').classList.add('hidden');
            document.getElementById('start-screen').classList.remove('hidden');
        }

        function gameOver() {
            isGameOver = true;
            document.getElementById('screens').classList.remove('hidden');
            document.getElementById('game-over-screen').classList.remove('hidden');
            document.getElementById('final-score').innerText = Math.floor(score);
        }

        function takeDamage() {
            if (invulnerableTime > 0) return;
            lives--;
            updateHUD();
            const flash = document.getElementById('damage-flash');
            flash.style.opacity = 0.5;
            setTimeout(() => { flash.style.opacity = 0; }, 100);
            baseSpeed = Math.max(0.6, baseSpeed - 0.2); 
            if (lives <= 0) gameOver();
            else invulnerableTime = 90; 
        }

        function updateHUD() {
            document.getElementById('level-val').innerText = currentLevel;
            document.getElementById('score-val').innerText = Math.floor(score);
            document.getElementById('speed-val').innerText = baseSpeed.toFixed(1);
            let hearts = "";
            for(let i=0; i<lives; i++) hearts += "♥";
            document.getElementById('lives').innerText = hearts;
        }

        function handleKeyDown(e) { if (keys.hasOwnProperty(e.key)) keys[e.key] = true; }
        function handleKeyUp(e) { if (keys.hasOwnProperty(e.key)) keys[e.key] = false; }
        
        function handleTouchStart(e) {
            e.preventDefault();
            if (!gameStarted || isGameOver) return;
            const touch = e.touches[0];
            touchInput.active = true;
            touchInput.startX = touch.clientX;
            touchInput.startY = touch.clientY;
            touchInput.playerStartX = playerGroup.position.x;
            touchInput.playerStartY = playerGroup.position.y;
        }
        function handleTouchMove(e) {
            e.preventDefault();
            if (!touchInput.active || isGameOver) return;
            const touch = e.touches[0];
            const sensitivity = 0.05;
            touchInput.targetX = touchInput.playerStartX + (touch.clientX - touchInput.startX) * sensitivity;
            touchInput.targetY = touchInput.playerStartY - (touch.clientY - touchInput.startY) * sensitivity;
        }
        function handleTouchEnd() { touchInput.active = false; }

        const clock = new THREE.Clock();
        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();
            if (gameStarted && !isGameOver) {
                scene.background.lerp(targetBgColor, 0.01);
                scene.fog.color.lerp(targetFogColor, 0.01);
                checkLevelUp();
                
                currentSpeed += (baseSpeed - currentSpeed) * 0.05;
                baseSpeed += (0.0001 * (1 + currentLevel * 0.2)); 
                distanceTraveled += currentSpeed;
                score += currentSpeed * 0.1;
                updatePlayerMovement();
                
                const time = Date.now() * 0.005;
                playerGroup.children.forEach((child, index) => {
                    if (child.name === "cloud_part") {
                        if (!child.userData.baseY) child.userData.baseY = child.position.y;
                        child.position.y = child.userData.baseY + Math.sin(time + index) * 0.1;
                    }
                });
                
                if (invulnerableTime > 0) {
                    invulnerableTime--;
                    playerGroup.visible = (Math.floor(invulnerableTime / 5) % 2 === 0);
                } else playerGroup.visible = true;
                
                updateEnvironment(currentSpeed);
                updateSpawning(currentSpeed);
                if (Math.floor(distanceTraveled) % 5 === 0) updateHUD();
                
                camera.position.x += (playerGroup.position.x * 0.5 - camera.position.x) * 0.1;
                camera.position.y += ((playerGroup.position.y + 3) - camera.position.y) * 0.1;
            }
            renderer.render(scene, camera);
        }

        function checkLevelUp() {
            let newLevel = 1;
            if (score >= 3000) newLevel = 3;
            else if (score >= 1000) newLevel = 2;
            
            if (newLevel !== currentLevel) {
                const levelDiff = newLevel - currentLevel;
                currentLevel = newLevel;
                baseSpeed += 0.15 * levelDiff; 
                updateHUD();
                if (currentLevel === 2) { targetBgColor.setHex(0xFF8C00); targetFogColor.setHex(0xFF8C00); }
                else if (currentLevel === 3) { targetBgColor.setHex(0x191970); targetFogColor.setHex(0x191970); }
            }
        }

        function updatePlayerMovement() {
            let targetX = playerGroup.position.x;
            let targetY = playerGroup.position.y;
            const moveSpeed = 0.35; 
            if (keys.ArrowLeft || keys.a) targetX -= moveSpeed;
            if (keys.ArrowRight || keys.d) targetX += moveSpeed;
            if (keys.ArrowUp || keys.w) targetY += moveSpeed;
            if (keys.ArrowDown || keys.s) targetY -= moveSpeed;
            if (touchInput.active && touchInput.targetX !== undefined) {
                targetX = touchInput.targetX;
                targetY = touchInput.targetY;
            }
            targetX = Math.max(-BOUNDARY_X, Math.min(BOUNDARY_X, targetX));
            targetY = Math.max(BOUNDARY_Y_MIN, Math.min(BOUNDARY_Y_MAX, targetY));
            playerGroup.position.x += (targetX - playerGroup.position.x) * 0.15;
            playerGroup.position.y += (targetY - playerGroup.position.y) * 0.15;
            playerGroup.rotation.z = Math.max(-0.4, Math.min(0.4, (playerGroup.position.x - targetX) * 0.1));
            playerGroup.rotation.x = Math.max(-0.25, Math.min(0.25, (targetY - playerGroup.position.y) * 0.1));
        }

        function updateEnvironment(speed) {
            const moveSpeed = speed * 1.5;
            for (let i = activeObjects.length - 1; i >= 0; i--) {
                const obj = activeObjects[i];
                obj.position.z += moveSpeed;
                if (obj.userData.type === 'ring') obj.rotation.y += obj.userData.rotationSpeed;
                if (obj.position.z > -2 && obj.position.z < 2) {
                    const dx = obj.position.x - playerGroup.position.x;
                    const dy = obj.position.y - playerGroup.position.y;
                    const distanceSq = dx * dx + dy * dy;
                    if (obj.userData.type === 'ring' && !obj.userData.passed) {
                        if (distanceSq < 3.0 * 3.0) {
                            obj.userData.passed = true;
                            score += 100;
                            obj.material.color.setHex(0xffffff);
                            obj.material.emissive.setHex(0xaaaaaa);
                            baseSpeed += 0.02; 
                            updateHUD();
                        }
                    } else if (obj.userData.type === 'obstacle' && !obj.userData.hit) {
                        if (distanceSq < 3.0 * 3.0) {
                            obj.userData.hit = true;
                            takeDamage();
                        }
                    }
                }
                if (obj.position.z > DESPAWN_Z) { scene.remove(obj); activeObjects.splice(i, 1); }
            }
            for (let i = bgClouds.length - 1; i >= 0; i--) {
                const cloud = bgClouds[i];
                cloud.position.z += moveSpeed * 0.5;
                if (cloud.position.z > DESPAWN_Z) { scene.remove(cloud); bgClouds.splice(i, 1); spawnBackgroundCloud(false); }
            }
        }

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

        window.onload = init;
    </script>
</body>
</html>
✨ ピックアップ

筋斗雲ゲーム

by o-yuto

金のわっかをくぐると得点GET!高得点を目指そう!

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