🔬 AI発明ギャラリー syunsuke アゲハ蝶の一生
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
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>アゲハ蝶の一生 - Real 3D Adventure</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- Three.jsの読み込み -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
            user-select: none;
            touch-action: none;
        }
        #canvas-container {
            position: absolute;
            top: 0; left: 0; width: 100vw; height: 100vh;
            z-index: 1;
        }
        #ui-layer {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            z-index: 10;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        .pointer-events-auto { pointer-events: auto; }
        
        @keyframes pop {
            0% { transform: scale(0.8); opacity: 0; }
            100% { transform: scale(1); opacity: 1; }
        }
        .animate-pop { animation: pop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; }
        .glass-panel { background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.4); }

        .dpad-btn { transition: all 0.1s; }
        .dpad-btn:active, .dpad-btn.active-key { background-color: rgba(255, 255, 255, 0.4); transform: scale(0.9); }
        
        #flash-screen {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background-color: white; z-index: 9999; pointer-events: none;
            opacity: 0; transition: opacity 1s; display: none;
        }
    </style>
</head>
<body>

    <div id="canvas-container"></div>
    <div id="flash-screen"></div>

    <div id="ui-layer" class="p-4 sm:p-8">
        
        <div id="stats-bar" class="w-full max-w-3xl flex justify-center mt-6 text-2xl font-bold text-white drop-shadow-lg hidden">
            <div id="score-display" class="glass-panel px-8 py-3 rounded-2xl text-yellow-300 tracking-widest">🌸 0 / 20</div>
        </div>
        
        <div id="progress-container" class="w-full max-w-md mt-8 hidden">
            <div class="h-4 w-full bg-black/50 rounded-full overflow-hidden border border-white/50 shadow-inner">
                <div id="progress-bar" class="h-full bg-gradient-to-r from-green-400 to-emerald-600 w-0 transition-all duration-300 relative">
                    <div class="absolute inset-0 bg-white/20 w-full animate-pulse"></div>
                </div>
            </div>
        </div>

        <!-- 文字をなくし、再生アイコンのみのスタートボタン -->
        <button id="start-btn" class="pointer-events-auto bg-gradient-to-b from-green-500 to-green-700 hover:from-green-400 hover:to-green-600 text-white py-6 px-10 rounded-full shadow-[0_10px_25px_rgba(0,0,0,0.5)] transform transition hover:scale-105 active:scale-95 text-4xl mt-auto mb-20 animate-pop border-2 border-green-300">
            ▶
        </button>

        <!-- 糸を吐く攻撃ボタン(アイコンのみ) -->
        <button id="attack-btn" class="pointer-events-auto absolute bottom-32 right-8 sm:bottom-40 sm:right-12 w-24 h-24 bg-white/30 backdrop-blur-md rounded-full shadow-[0_0_20px_rgba(255,255,255,0.5)] text-white hidden z-50 border-2 border-white/80 flex items-center justify-center transition-transform active:scale-90 animate-pop">
            <span class="text-5xl drop-shadow-lg">🕸️</span>
        </button>

        <!-- 蛹になるボタン(アイコンのみ) -->
        <button id="action-btn" class="pointer-events-auto absolute bottom-32 right-8 sm:bottom-40 sm:right-12 w-24 h-24 bg-gradient-to-br from-yellow-400 to-orange-500 rounded-full shadow-[0_0_20px_rgba(255,165,0,0.8)] text-white hidden z-50 border-4 border-white/80 flex items-center justify-center transition-transform active:scale-90">
            <span class="text-5xl drop-shadow-lg">🧶</span>
        </button>
        
        <!-- クリアボタン(アイコンのみ) -->
        <button id="finish-btn" class="pointer-events-auto absolute bottom-32 right-8 sm:bottom-40 sm:right-12 w-24 h-24 bg-gradient-to-br from-pink-400 to-purple-500 rounded-full shadow-[0_0_20px_rgba(255,105,180,0.8)] text-white hidden z-50 border-4 border-white/80 flex items-center justify-center transition-transform active:scale-90 animate-pulse">
            <span class="text-5xl drop-shadow-lg">✨</span>
        </button>

        <!-- 文字なしのシンプルな十字キー -->
        <div id="dpad-container" class="absolute bottom-8 left-8 sm:bottom-12 sm:left-12 w-48 h-48 hidden z-50 pointer-events-auto">
            <div class="relative w-full h-full">
                <button id="btn-up" class="dpad-btn absolute top-0 left-16 w-16 h-16 bg-white/20 rounded-xl backdrop-blur-sm border border-white/40 shadow-lg"></button>
                <button id="btn-down" class="dpad-btn absolute bottom-0 left-16 w-16 h-16 bg-white/20 rounded-xl backdrop-blur-sm border border-white/40 shadow-lg"></button>
                <button id="btn-left" class="dpad-btn absolute top-16 left-0 w-16 h-16 bg-white/20 rounded-xl backdrop-blur-sm border border-white/40 shadow-lg"></button>
                <button id="btn-right" class="dpad-btn absolute top-16 right-0 w-16 h-16 bg-white/20 rounded-xl backdrop-blur-sm border border-white/40 shadow-lg"></button>
            </div>
        </div>
    </div>

    <script>
        const gameState = {
            phase: 0,
            score: 0,
            progress: 0,
            targetProgress: 5, 
            pointer: new THREE.Vector2(),
            flySpeed: 0,
            isReadyToPupate: false,
            hasStormed: false,
            isStorming: false,
            stormIntensity: 0, // 0.0 ~ 1.0 で嵐の激しさを管理
            waterWall: null, // 押し寄せる鉄砲水のオブジェクト
            startZ: 0,
            goalZ: -800,
            silks: [], 
            pupateTimer: 0,
            hasPupated: false
        };

        const inputState = { up: false, down: false, left: false, right: false };

        // --- 音声生成(Web Audio API) ---
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        let windOsc = null;
        let windGain = null;

        function playStormSound() {
            if (audioCtx.state === 'suspended') audioCtx.resume();
            // 風の音(ノイズ)
            const bufferSize = audioCtx.sampleRate * 2; 
            const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
            const data = buffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) {
                data[i] = Math.random() * 2 - 1;
            }
            windOsc = audioCtx.createBufferSource();
            windOsc.buffer = buffer;
            windOsc.loop = true;

            // フィルターで「ゴー」という低い音にする
            const filter = audioCtx.createBiquadFilter();
            filter.type = 'lowpass';
            filter.frequency.value = 100; // 初期はかなり低い

            windGain = audioCtx.createGain();
            windGain.gain.value = 0; // 初期は無音

            windOsc.connect(filter);
            filter.connect(windGain);
            windGain.connect(audioCtx.destination);
            windOsc.start();

            // 嵐の激しさに合わせて音量と周波数を上げるアニメーションはメインループで行う
            gameState.windFilter = filter;
        }

        function stopStormSound() {
            if (windGain) {
                windGain.gain.setTargetAtTime(0, audioCtx.currentTime, 1.0);
                setTimeout(() => { if(windOsc) windOsc.stop(); }, 2000);
            }
        }


        const uiLayer = document.getElementById('ui-layer');
        const startBtn = document.getElementById('start-btn');
        const actionBtn = document.getElementById('action-btn');
        const attackBtn = document.getElementById('attack-btn');
        const finishBtn = document.getElementById('finish-btn');
        const statsBar = document.getElementById('stats-bar');
        const scoreDisplay = document.getElementById('score-display');
        const progressBar = document.getElementById('progress-bar');
        const progressContainer = document.getElementById('progress-container');
        const dpadContainer = document.getElementById('dpad-container');
        const flashScreen = document.getElementById('flash-screen');

        const container = document.getElementById('canvas-container');
        const scene = new THREE.Scene();
        scene.fog = new THREE.FogExp2(0x87CEFA, 0.003);

        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
        
        const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        renderer.outputEncoding = THREE.sRGBEncoding;
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        container.appendChild(renderer.domElement);

        const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.9);
        hemiLight.position.set(0, 200, 0);
        scene.add(hemiLight);

        const dirLight = new THREE.DirectionalLight(0xffffee, 1.5);
        dirLight.position.set(150, 200, 100);
        dirLight.castShadow = true;
        dirLight.shadow.camera.top = 150;
        dirLight.shadow.camera.bottom = -150;
        dirLight.shadow.camera.left = -150;
        dirLight.shadow.camera.right = 150;
        dirLight.shadow.camera.near = 0.1;
        dirLight.shadow.camera.far = 1000;
        dirLight.shadow.mapSize.width = 2048;
        dirLight.shadow.mapSize.height = 2048;
        scene.add(dirLight);

        let gameObjects = [];
        let playerObj = null;
        let environmentGroup = new THREE.Group();
        let particleSystem = null;
        let npcButterflies = []; 
        let enemies = []; 
        scene.add(environmentGroup);

        const raycaster = new THREE.Raycaster();
        const clock = new THREE.Clock();

        function setupInput() {
            const bindBtn = (id, key) => {
                const btn = document.getElementById(id);
                const press = (e) => { e.preventDefault(); e.stopPropagation(); inputState[key] = true; btn.classList.add('active-key'); };
                const release = (e) => { e.preventDefault(); e.stopPropagation(); inputState[key] = false; btn.classList.remove('active-key'); };
                btn.addEventListener('pointerdown', press);
                btn.addEventListener('pointerup', release);
                btn.addEventListener('pointerleave', release);
            };
            bindBtn('btn-up', 'up'); bindBtn('btn-down', 'down');
            bindBtn('btn-left', 'left'); bindBtn('btn-right', 'right');

            window.addEventListener('keydown', (e) => {
                if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') { inputState.up = true; }
                if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') { inputState.down = true; }
                if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { inputState.left = true; }
                if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { inputState.right = true; }
            });
            window.addEventListener('keyup', (e) => {
                if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') { inputState.up = false; }
                if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') { inputState.down = false; }
                if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { inputState.left = false; }
                if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { inputState.right = false; }
            });

            let isDragging = false;
            window.addEventListener('pointerdown', (e) => {
                if(e.target.tagName === 'BUTTON' || e.target.tagName === 'SPAN') return; 
                if(audioCtx.state === 'suspended') audioCtx.resume(); // オーディオコンテキストの初期化
                isDragging = true;
                gameState.pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
                gameState.pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
                handleTapInteraction();
            });
            window.addEventListener('pointermove', (e) => {
                if (!isDragging) return;
                gameState.pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
                gameState.pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
            });
            window.addEventListener('pointerup', () => { isDragging = false; gameState.pointer.y = 0; });

            actionBtn.addEventListener('click', () => {
                if (gameState.phase === 2.2 && gameState.isReadyToPupate) {
                    actionBtn.classList.add('hidden');
                    dpadContainer.classList.add('hidden');
                    attackBtn.classList.add('hidden');
                    startPupating();
                }
            });

            finishBtn.addEventListener('click', () => {
                finishBtn.classList.add('hidden');
                showResult();
            });

            attackBtn.addEventListener('click', () => {
                if ((gameState.phase === 2 || gameState.phase === 2.1 || gameState.phase === 2.2) && !gameState.isStorming && playerObj) {
                    const netGroup = new THREE.Group();
                    const netMat = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8, wireframe: true});
                    const netGeo = new THREE.SphereGeometry(0.6, 8, 8); 
                    const net = new THREE.Mesh(netGeo, netMat);
                    
                    for(let i=0; i<6; i++){
                        const lGeo = new THREE.CylinderGeometry(0.01, 0.01, 1.2);
                        const l = new THREE.Mesh(lGeo, netMat);
                        l.rotation.x = Math.random() * Math.PI;
                        l.rotation.y = Math.random() * Math.PI;
                        netGroup.add(l);
                    }
                    netGroup.add(net);
                    
                    let fw = new THREE.Vector3(0, 0, -1).applyQuaternion(playerObj.quaternion).normalize();
                    netGroup.position.copy(playerObj.position).add(new THREE.Vector3(0, 0.5, 0)).add(fw.clone().multiplyScalar(1.5));
                    netGroup.quaternion.copy(playerObj.quaternion);
                    
                    scene.add(netGroup);
                    gameState.silks.push({mesh: netGroup, velocity: fw.multiplyScalar(30), life: 1.5, scaleSpeed: 4.0});
                }
            });
        }
        setupInput();

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        function createSkyTexture(type = 'forest') {
            const canvas = document.createElement('canvas');
            canvas.width = 2; canvas.height = 512;
            const ctx = canvas.getContext('2d');
            const grad = ctx.createLinearGradient(0, 0, 0, 512);
            if (type === 'forest') {
                grad.addColorStop(0, '#4aa8ff'); grad.addColorStop(0.5, '#87CEFA'); grad.addColorStop(1, '#e0f7fa'); 
            } else if (type === 'storm') {
                grad.addColorStop(0, '#222233'); grad.addColorStop(0.5, '#333344'); grad.addColorStop(1, '#444455'); 
            } else if (type === 'wetland') {
                grad.addColorStop(0, '#445544'); grad.addColorStop(0.5, '#667766'); grad.addColorStop(1, '#99aa99'); 
            }
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, 2, 512);
            return new THREE.CanvasTexture(canvas);
        }
        scene.background = createSkyTexture();

        function createWingTexture(isNPC = false) {
            const canvas = document.createElement('canvas');
            canvas.width = 1024; canvas.height = 1024;
            const ctx = canvas.getContext('2d');
            
            ctx.fillStyle = isNPC ? '#ffffff' : '#fceb3b';
            ctx.fillRect(0, 0, 1024, 1024);
            
            ctx.lineWidth = 100;
            ctx.strokeStyle = '#111111';
            ctx.strokeRect(0, 0, 1024, 1024);

            ctx.lineCap = 'round';
            for(let i = 0; i < 12; i++) {
                ctx.lineWidth = 12;
                ctx.beginPath();
                ctx.moveTo(0, 512); 
                let angle = -Math.PI/2.5 + (i / 11) * Math.PI*0.8; 
                let endX = 1024 * Math.cos(angle) * 1.5;
                let endY = 512 + 1024 * Math.sin(angle) * 1.5;
                ctx.lineTo(endX, endY);
                ctx.stroke();
            }
            
            ctx.fillStyle = '#111111';
            ctx.fillRect(0, 750, 1024, 274);

            ctx.fillStyle = '#e53935';
            ctx.beginPath(); ctx.arc(800, 120, 45, 0, Math.PI*2); ctx.fill();
            
            ctx.fillStyle = '#1e88e5';
            for(let i=0; i<5; i++) {
                ctx.beginPath(); ctx.arc(780 - i*70, 250 + i*30, 25, 0, Math.PI*2); ctx.fill();
            }

            const tex = new THREE.CanvasTexture(canvas);
            tex.encoding = THREE.sRGBEncoding;
            return tex;
        }

        function createButterflyModel(colorHex = 0xffffff, isNPC = false) {
            const group = new THREE.Group();
            
            const head = new THREE.Mesh(new THREE.SphereGeometry(0.18, 12, 12), new THREE.MeshStandardMaterial({ color: 0x111111 }));
            head.position.z = 0.8;
            group.add(head);
            const thorax = new THREE.Mesh(new THREE.SphereGeometry(0.25, 16, 16), new THREE.MeshStandardMaterial({ color: 0x222222 }));
            thorax.scale.set(1, 1, 1.5);
            group.add(thorax);
            const abdomen = new THREE.Mesh(new THREE.SphereGeometry(0.15, 12, 12), new THREE.MeshStandardMaterial({ color: 0x333333 }));
            abdomen.scale.set(1, 1, 5);
            abdomen.position.z = -0.7;
            group.add(abdomen);
            
            const antGeo = new THREE.CylinderGeometry(0.01, 0.01, 0.8);
            const lAnt = new THREE.Mesh(antGeo, head.material);
            lAnt.position.set(0.1, 0.1, 1.1); lAnt.rotation.x = Math.PI/2.5; lAnt.rotation.z = 0.2;
            group.add(lAnt);
            const rAnt = lAnt.clone();
            rAnt.position.x = -0.1; rAnt.rotation.z = -0.2;
            group.add(rAnt);

            const wingShape = new THREE.Shape();
            wingShape.moveTo(0, 0);
            wingShape.bezierCurveTo(0.5, 3, 3, 5, 6, 4); 
            wingShape.bezierCurveTo(5.5, 2, 4.5, 1, 3.5, 0.5); 
            wingShape.bezierCurveTo(4.5, -2, 4, -4, 4.2, -6);
            wingShape.lineTo(3.5, -6);
            wingShape.bezierCurveTo(3, -4, 1.5, -5, 0, -2);
            wingShape.lineTo(0, 0);
            
            const wingGeo = new THREE.ShapeGeometry(wingShape);
            wingGeo.rotateX(Math.PI / 2);
            wingGeo.translate(0.4, 0, 0);
            
            const wingMat = new THREE.MeshStandardMaterial({ 
                map: createWingTexture(isNPC), 
                side: THREE.DoubleSide, 
                roughness: 0.6, 
                alphaTest: 0.5, 
                color: colorHex 
            });
            
            const rWing = new THREE.Mesh(wingGeo, wingMat);
            group.add(rWing);

            const lWingGeo = wingGeo.clone();
            const posAttr = lWingGeo.attributes.position;
            for(let i=0; i<posAttr.count; i++) {
                posAttr.setX(i, -posAttr.getX(i));
            }
            lWingGeo.computeVertexNormals();
            const lWing = new THREE.Mesh(lWingGeo, wingMat);
            group.add(lWing);
            
            group.userData = { lWing, rWing, lAnt, rAnt, baseRotX: 0, baseRotY: 0, baseRotZ: 0 };
            return group;
        }

        // --- 敵キャラクター群 ---
        function createAnt() {
            const ant = new THREE.Group();
            const mat = new THREE.MeshStandardMaterial({color: 0x111111, roughness: 0.5, clearcoat: 0.8});
            const head = new THREE.Mesh(new THREE.SphereGeometry(0.6, 16, 16), mat); head.position.set(0, 0.6, 1.2);
            const thorax = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), mat); thorax.position.set(0, 0.5, 0);
            const abdomen = new THREE.Mesh(new THREE.SphereGeometry(0.8, 16, 16), mat); abdomen.position.set(0, 0.8, -1.2);
            ant.add(head, thorax, abdomen);
            
            ant.userData = { legs: [] };
            const legMat = new THREE.MeshStandardMaterial({color: 0x111111});
            for(let i=0; i<3; i++) {
                let zPos = 0.5 - i * 0.5;
                for(let side of [-1, 1]) {
                    const leg = new THREE.Group();
                    const upperLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.8), legMat);
                    upperLeg.position.set(side * 0.4, 0.4, 0); upperLeg.rotation.z = side * -Math.PI/4;
                    const lowerLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.01, 0.8), legMat);
                    lowerLeg.position.set(side * 0.8, 0, 0); lowerLeg.rotation.z = side * Math.PI/8;
                    leg.add(upperLeg, lowerLeg); leg.position.set(0, 0, zPos);
                    ant.add(leg);
                    ant.userData.legs.push({ mesh: leg, side: side, offset: i });
                }
            }
            return ant;
        }

        function createAntSwarm() {
            const swarm = new THREE.Group();
            swarm.userData = { type: 'antSwarm', seed: Math.random() * 100, isEnemy: true, childrenData: [], stunTimer: 0 };
            const numAnts = 5 + Math.floor(Math.random() * 5); 
            for(let i=0; i<numAnts; i++) {
                let ant = createAnt();
                let offsetX = (Math.random() - 0.5) * 8;
                let offsetZ = (Math.random() - 0.5) * 8;
                ant.position.set(offsetX, 0, offsetZ);
                ant.rotation.y = Math.random() * Math.PI * 2;
                swarm.add(ant);
                swarm.userData.childrenData.push({ speed: 0.8 + Math.random(), phase: Math.random() * Math.PI * 2 });
            }
            return swarm;
        }

        function createSpider() {
            const spider = new THREE.Group();
            const mat = new THREE.MeshStandardMaterial({color: 0x1a0a0a, roughness: 0.8});
            const body = new THREE.Mesh(new THREE.SphereGeometry(1.0, 16, 16), mat);
            const head = new THREE.Mesh(new THREE.SphereGeometry(0.6, 16, 16), mat); head.position.z = 1.0;
            spider.add(body, head);
            
            spider.userData = { type: 'spider', seed: Math.random() * 100, isEnemy: true, stunTimer: 0, legs: [] };
            for(let i=0; i<4; i++) {
                let zPos = 0.6 - i * 0.4;
                for(let side of [-1, 1]) {
                    const leg = new THREE.Group();
                    const upper = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1.5), mat);
                    upper.position.set(side * 0.7, 0.5, 0); upper.rotation.z = side * -Math.PI/3;
                    const lower = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.01, 2.0), mat);
                    lower.position.set(side * 1.8, -0.2, 0); lower.rotation.z = side * Math.PI/6;
                    leg.add(upper, lower); leg.position.set(0, 0, zPos);
                    spider.add(leg);
                    spider.userData.legs.push({mesh: leg, offset: i, side: side});
                }
            }
            const silk = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 40), new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.5}));
            silk.position.y = 20; spider.add(silk);
            return spider;
        }

        function createPitcherPlant() {
            const plant = new THREE.Group();
            const matOut = new THREE.MeshStandardMaterial({color: 0x228B22, side: THREE.DoubleSide});
            const matIn = new THREE.MeshStandardMaterial({color: 0xcc2222, side: THREE.DoubleSide});
            
            const jaw1 = new THREE.Mesh(new THREE.SphereGeometry(2, 16, 16, 0, Math.PI), matOut);
            jaw1.rotation.x = Math.PI / 4;
            const jawIn1 = new THREE.Mesh(new THREE.SphereGeometry(1.9, 16, 16, 0, Math.PI), matIn);
            jawIn1.rotation.x = Math.PI / 4;
            
            const jaw2 = new THREE.Mesh(new THREE.SphereGeometry(2, 16, 16, 0, Math.PI), matOut);
            jaw2.rotation.x = -Math.PI / 4; jaw2.rotation.z = Math.PI;
            const jawIn2 = new THREE.Mesh(new THREE.SphereGeometry(1.9, 16, 16, 0, Math.PI), matIn);
            jawIn2.rotation.x = -Math.PI / 4; jawIn2.rotation.z = Math.PI;

            plant.add(jaw1, jawIn1, jaw2, jawIn2);
            plant.position.y = 2;
            
            const wrapper = new THREE.Group();
            wrapper.add(plant);
            wrapper.userData = { type: 'plant', seed: Math.random() * 100, isEnemy: true, stunTimer: 0 };
            return wrapper;
        }

        function updateProgress() {
            if (gameState.phase === 2.1) {
                let totalDist = Math.abs(gameState.startZ - gameState.goalZ);
                let currentDist = gameState.startZ - playerObj.position.z;
                let p = Math.max(0, Math.min(100, (currentDist / totalDist) * 100));
                progressBar.style.width = p + '%';
            } else {
                const percentage = Math.min((gameState.progress / gameState.targetProgress) * 100, 100);
                progressBar.style.width = `${percentage}%`;
            }
        }

        function clearScene() {
            if (camera.parent) camera.parent.remove(camera);
            gameObjects.forEach(obj => { if (obj.mesh) scene.remove(obj.mesh); });
            gameObjects = [];
            npcButterflies.forEach(npc => scene.remove(npc));
            npcButterflies = [];
            enemies.forEach(e => scene.remove(e));
            enemies = [];
            gameState.silks.forEach(s => scene.remove(s.mesh));
            gameState.silks = [];
            if(gameState.waterWall) { scene.remove(gameState.waterWall); gameState.waterWall = null; }
            while(environmentGroup.children.length > 0) environmentGroup.remove(environmentGroup.children[0]); 
            if(playerObj) { scene.remove(playerObj); playerObj = null; }
            if(particleSystem) { scene.remove(particleSystem); particleSystem = null; }
        }

        function getTerrainHeight(x, z) {
            let y = Math.sin(x * 0.02) * Math.cos(z * 0.02) * 15;
            y += Math.sin(x * 0.05 + z * 0.03) * 5;
            y += Math.sin(x * 0.005) * Math.sin(z * 0.005) * 40;
            return y;
        }

        function handleTapInteraction() {
            if (!playerObj) return;
            if (gameState.phase === 1) {
                raycaster.setFromCamera(gameState.pointer, camera);
                const intersects = raycaster.intersectObject(playerObj, true);
                if (intersects.length > 0) {
                    gameState.progress++; updateProgress();
                    playerObj.userData.shakeTime = 0.2;
                    if (gameState.progress >= gameState.targetProgress) setTimeout(startPhase2, 500);
                }
            } else if (gameState.phase === 3) {
                gameState.progress++; updateProgress();
                playerObj.userData.shakeTime = 0.2;
                if (playerObj.children) {
                    playerObj.children.forEach(child => {
                        if(child.userData && child.userData.isSegment) {
                            child.children.forEach(mesh => {
                                if(mesh.material && mesh.material.color) {
                                    mesh.material.color.lerp(new THREE.Color(0xb8860b), 0.05);
                                }
                            });
                        }
                    });
                }
                if (gameState.progress >= gameState.targetProgress) setTimeout(startPhase4, 500);
            }
        }

        function createDustParticles() {
            if(particleSystem) scene.remove(particleSystem);
            const particleGeo = new THREE.BufferGeometry();
            const particleCount = 1500;
            const posArray = new Float32Array(particleCount * 3);
            for(let i=0; i<particleCount*3; i++) posArray[i] = (Math.random() - 0.5) * 300;
            particleGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
            const particleMat = new THREE.PointsMaterial({
                size: 0.6, color: 0xffffff, transparent: true, opacity: 0.6,
                blending: THREE.AdditiveBlending, depthWrite: false
            });
            particleSystem = new THREE.Points(particleGeo, particleMat);
            scene.add(particleSystem);
        }

        function buildEnvironment(type) {
            while(environmentGroup.children.length > 0) environmentGroup.remove(environmentGroup.children[0]);
            gameObjects.forEach(obj => { if (obj.mesh) scene.remove(obj.mesh); });
            gameObjects = [];
            enemies.forEach(e => scene.remove(e));
            enemies = [];

            const isWetland = type === 'wetland';
            const size = isWetland ? 2000 : 250;
            const segs = isWetland ? 256 : 128;

            const groundGeo = new THREE.PlaneGeometry(size, size, segs, segs);
            const pos = groundGeo.attributes.position;
            for(let i=0; i<pos.count; i++) {
                const px = pos.getX(i); const pz = -pos.getY(i);
                pos.setZ(i, getTerrainHeight(px, pz));
            }
            groundGeo.computeVertexNormals();

            let groundColor = isWetland ? 0x2a3322 : 0x3cb371; 
            const ground = new THREE.Mesh(groundGeo, new THREE.MeshStandardMaterial({ color: groundColor, roughness: 0.9, side: THREE.DoubleSide }));
            ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true;
            ground.userData.isGround = true; 
            environmentGroup.add(ground);

            if (isWetland) {
                const waterGeo = new THREE.PlaneGeometry(size, size);
                const waterMat = new THREE.MeshStandardMaterial({ color: 0x334455, transparent: true, opacity: 0.8, roughness: 0.1 });
                const water = new THREE.Mesh(waterGeo, waterMat);
                water.rotation.x = -Math.PI / 2;
                water.position.y = -5; 
                environmentGroup.add(water);

                const grassGeo = new THREE.CylinderGeometry(0.2, 2.5, 60, 8);
                const grassMat = new THREE.MeshStandardMaterial({color: 0x2a7a2a, roughness: 0.8});
                for(let i=0; i<800; i++) {
                    const grass = new THREE.Mesh(grassGeo, grassMat);
                    let px = (Math.random() - 0.5) * size; let pz = (Math.random() - 0.5) * size;
                    if(Math.abs(px) < 20 && Math.abs(pz - gameState.startZ) < 30) continue;
                    grass.position.set(px, getTerrainHeight(px, pz) + 28, pz);
                    grass.rotation.x = (Math.random() - 0.5) * 0.3;
                    grass.rotation.z = (Math.random() - 0.5) * 0.3;
                    grass.castShadow = true;
                    grass.userData.isGrass = true; 
                    environmentGroup.add(grass);
                }

                for(let i=0; i<25; i++) {
                    let antSwarm = createAntSwarm();
                    antSwarm.position.set((Math.random()-0.5)*size, 0, (Math.random()-0.5)*size);
                    scene.add(antSwarm); enemies.push(antSwarm);

                    let spider = createSpider();
                    spider.position.set((Math.random()-0.5)*size, 20, (Math.random()-0.5)*size);
                    scene.add(spider); enemies.push(spider);

                    let plant = createPitcherPlant();
                    plant.position.set((Math.random()-0.5)*size, 0, (Math.random()-0.5)*size);
                    scene.add(plant); enemies.push(plant);
                }

            } else {
                const trunkMat = new THREE.MeshStandardMaterial({ color: 0x5c4033, roughness: 1.0 });
                const leavesMat = new THREE.MeshStandardMaterial({ color: 0x1e5622, roughness: 0.8 });
                
                for(let i=0; i<60; i++) {
                    const tree = new THREE.Group();
                    const trunk = new THREE.Mesh(new THREE.CylinderGeometry(2.5, 4.0, 80, 32), trunkMat); 
                    trunk.position.y = 30; trunk.castShadow = true; trunk.receiveShadow = true;
                    tree.add(trunk);

                    const leaves = new THREE.Mesh(new THREE.DodecahedronGeometry(18, 1), leavesMat);
                    leaves.position.y = 65; leaves.castShadow = true; leaves.userData.isLeaves = true; 
                    tree.add(leaves);

                    let px = (Math.random() - 0.5) * 200; let pz = (Math.random() - 0.5) * 200;
                    if(Math.abs(px) < 15 && Math.abs(pz) < 15) px += 20; 
                    let py = getTerrainHeight(px, pz);
                    tree.position.set(px, py - 5, pz);
                    environmentGroup.add(tree);
                }
                for(let i=0; i<15; i++) spawnFood();
            }
        }

        function startPhase1() {
            gameState.phase = 1;
            gameState.progress = 0;
            gameState.targetProgress = 5;
            clearScene();
            scene.fog.density = 0.05;

            dpadContainer.classList.add('hidden');
            actionBtn.classList.add('hidden');
            finishBtn.classList.add('hidden');
            attackBtn.classList.add('hidden');
            progressContainer.classList.remove('hidden');
            updateProgress();

            const leafGeo = new THREE.PlaneGeometry(30, 30, 10, 10);
            const leafMat = new THREE.MeshStandardMaterial({ color: 0x2e8b57, roughness: 0.6, metalness: 0.1 });
            const leaf = new THREE.Mesh(leafGeo, leafMat);
            leaf.rotation.x = -Math.PI / 2; leaf.receiveShadow = true;
            environmentGroup.add(leaf);

            const eggGeo = new THREE.SphereGeometry(1, 64, 64); eggGeo.scale(1, 1.15, 1);
            playerObj = new THREE.Mesh(eggGeo, new THREE.MeshStandardMaterial({ color: 0xfffae6, roughness: 0.3, metalness: 0.1, clearcoat: 0.5 }));
            playerObj.position.y = 1; playerObj.castShadow = true;
            playerObj.userData = { shakeTime: 0, baseY: 1 };
            scene.add(playerObj);

            camera.position.set(0, 4, 6); camera.lookAt(0, 0.5, 0);
            createDustParticles();
        }

        function createPlayerLarva() {
            const player = new THREE.Group();
            const segmentsCount = 12;
            const bodyColor = 0x55c533; 

            for(let i=0; i<segmentsCount; i++) {
                let scaleFactor = 1.0;
                if (i === 0) scaleFactor = 0.4; 
                else if (i === 1) scaleFactor = 0.6;
                else if (i === 2) scaleFactor = 0.9;
                else if (i === 3) scaleFactor = 1.2; 
                else scaleFactor = 1.2 - (i - 3) * 0.08; 

                const segGroup = new THREE.Group();
                const segGeo = new THREE.SphereGeometry(0.55 * scaleFactor, 32, 32);
                segGeo.scale(1.1, 0.9, 1.0); 
                const segMat = new THREE.MeshPhysicalMaterial({ color: bodyColor, roughness: 0.4, metalness: 0.05, clearcoat: 0.6, clearcoatRoughness: 0.2 });
                const seg = new THREE.Mesh(segGeo, segMat);
                seg.castShadow = true;
                segGroup.add(seg);

                const sideLineGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.45 * scaleFactor, 8);
                const sideLineMat = new THREE.MeshBasicMaterial({color: 0xffffff});
                const lLine = new THREE.Mesh(sideLineGeo, sideLineMat);
                lLine.rotation.z = Math.PI / 2; lLine.position.set(0.58 * scaleFactor, -0.15 * scaleFactor, 0);
                segGroup.add(lLine);
                const rLine = lLine.clone();
                rLine.position.set(-0.58 * scaleFactor, -0.15 * scaleFactor, 0);
                segGroup.add(rLine);

                if (i === 3) {
                    const addEye = (side) => {
                        const eyeGroup = new THREE.Group();
                        const eyeBase = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.18, 0.05, 32), new THREE.MeshBasicMaterial({color: 0x111111}));
                        eyeBase.rotation.x = Math.PI / 2;
                        const eyeInner = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.06, 32), new THREE.MeshBasicMaterial({color: 0xddddaa}));
                        eyeInner.rotation.x = Math.PI / 2;
                        const pupil = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.07, 32), new THREE.MeshBasicMaterial({color: 0x111111}));
                        pupil.rotation.x = Math.PI / 2; pupil.position.set(0.02 * side, 0.02, 0.01);
                        eyeGroup.add(eyeBase); eyeGroup.add(eyeInner); eyeGroup.add(pupil);
                        eyeGroup.position.set(0.35 * side, 0.25, 0.35);
                        eyeGroup.rotation.y = (Math.PI / 3) * side; eyeGroup.rotation.x = -0.2;
                        segGroup.add(eyeGroup);
                    };
                    addEye(1); addEye(-1);
                    const stripe = new THREE.Mesh(new THREE.TorusGeometry(0.56 * scaleFactor, 0.04, 16, 32, Math.PI), new THREE.MeshBasicMaterial({color: 0x111111}));
                    stripe.rotation.x = -Math.PI / 2; stripe.position.y = 0.05;
                    segGroup.add(stripe);
                }

                if (i === 4 || i === 6 || i === 8 || i === 10) {
                    const stripe = new THREE.Mesh(new THREE.TorusGeometry(0.54 * scaleFactor, 0.03, 16, 32, Math.PI), new THREE.MeshBasicMaterial({color: 0x111111}));
                    stripe.rotation.x = -Math.PI / 2; stripe.position.y = 0.05;
                    segGroup.add(stripe);
                    const dot = new THREE.Mesh(new THREE.SphereGeometry(0.06, 16, 16), new THREE.MeshBasicMaterial({color: 0xff6600}));
                    dot.position.set(0, 0.52 * scaleFactor, 0);
                    segGroup.add(dot);
                }

                if ((i >= 1 && i <= 3) || (i >= 6 && i <= 9) || i === 11) {
                    const isThoracic = i <= 3;
                    const legGeo = new THREE.CylinderGeometry(isThoracic ? 0.03 : 0.08, isThoracic ? 0.01 : 0.1, isThoracic ? 0.3 : 0.25);
                    const legMat = new THREE.MeshStandardMaterial({color: isThoracic ? 0x111111 : bodyColor});
                    
                    const lLeg = new THREE.Mesh(legGeo, legMat);
                    lLeg.position.set(0.2 * scaleFactor, -0.45 * scaleFactor, 0); lLeg.rotation.z = -0.2;
                    lLeg.userData = { isLeg: true, baseRotX: 0, baseRotZ: -0.2, side: 1 };
                    segGroup.add(lLeg);
                    
                    const rLeg = new THREE.Mesh(legGeo, legMat);
                    rLeg.position.set(-0.2 * scaleFactor, -0.45 * scaleFactor, 0); rLeg.rotation.z = 0.2;
                    rLeg.userData = { isLeg: true, baseRotX: 0, baseRotZ: 0.2, side: -1 };
                    segGroup.add(rLeg);
                }

                if (i === 0) {
                    const face = new THREE.Mesh(new THREE.SphereGeometry(0.25, 16, 16), new THREE.MeshStandardMaterial({color: 0x8b5a2b})); 
                    face.position.set(0, -0.1, 0.2);
                    segGroup.add(face);
                }

                segGroup.position.z = i * 0.45; 
                segGroup.userData.isSegment = true;
                segGroup.userData.baseY = 0.6 * scaleFactor;
                segGroup.position.y = segGroup.userData.baseY;
                player.add(segGroup);
            }
            player.userData.surfaceNormal = new THREE.Vector3(0, 1, 0);
            return player;
        }

        function startPhase2() {
            gameState.phase = 2;
            gameState.progress = 0;
            gameState.targetProgress = 5; 
            gameState.isReadyToPupate = false;
            gameState.hasStormed = false;
            gameState.isStorming = false;
            clearScene();
            scene.fog.density = 0.02;

            dpadContainer.classList.remove('hidden');
            attackBtn.classList.remove('hidden'); 
            updateProgress();

            buildEnvironment('forest');

            playerObj = createPlayerLarva();
            playerObj.position.set(0, getTerrainHeight(0,0) + 1, 0);
            scene.add(playerObj);

            camera.position.set(0, 10, 15); camera.lookAt(0, 0, 0);
            createDustParticles();
        }

        function spawnFood() {
            let placed = false;
            for(let attempt=0; attempt<20; attempt++) {
                let basePos = playerObj ? playerObj.position : new THREE.Vector3(0,0,0);
                let px = THREE.MathUtils.clamp(basePos.x + (Math.random()-0.5)*120, -120, 120);
                let pz = THREE.MathUtils.clamp(basePos.z + (Math.random()-0.5)*120, -120, 120);

                let targetY = getTerrainHeight(px, pz) + 0.5;
                
                let eps = 0.1;
                let hx = getTerrainHeight(px + eps, pz);
                let hz = getTerrainHeight(px, pz + eps);
                let dx = new THREE.Vector3(eps, hx - (targetY - 0.5), 0);
                let dz = new THREE.Vector3(0, hz - (targetY - 0.5), eps);
                let targetNormal = new THREE.Vector3().crossVectors(dz, dx).normalize();

                const foodGroup = new THREE.Group();

                const foodMesh = new THREE.Mesh(
                    new THREE.SphereGeometry(0.8, 16, 16), 
                    new THREE.MeshStandardMaterial({ color: 0xadff2f, roughness: 0.2, emissive: 0x33aa00 })
                );
                foodMesh.castShadow = true; foodGroup.add(foodMesh);

                const aura = new THREE.Mesh(
                    new THREE.SphereGeometry(1.5, 16, 16), 
                    new THREE.MeshBasicMaterial({ color: 0xffff00, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending })
                );
                foodGroup.add(aura);

                const pillar = new THREE.Mesh(
                    new THREE.CylinderGeometry(0.1, 0.8, 30, 8), 
                    new THREE.MeshBasicMaterial({ color: 0xadff2f, transparent: true, opacity: 0.3, blending: THREE.AdditiveBlending })
                );
                pillar.position.y = 15; foodGroup.add(pillar);

                foodGroup.position.set(px, targetY, pz);
                let q = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), targetNormal);
                foodGroup.quaternion.copy(q);
                
                scene.add(foodGroup);
                gameObjects.push({ mesh: foodGroup, type: 'food' });
                placed = true;
                break;
            }
            if(!placed) setTimeout(spawnFood, 100); 
        }

        // --- 嵐と鉄砲水の演出 ---
        function triggerStorm() {
            gameState.isStorming = true;
            gameState.hasStormed = true;
            gameState.stormIntensity = 0;
            playStormSound();
            
            // 鉄砲水(濁流の壁)を生成
            const waterGeo = new THREE.PlaneGeometry(300, 100);
            const waterMat = new THREE.MeshStandardMaterial({color: 0x8b7355, transparent: true, opacity: 0.9, roughness: 0.2});
            gameState.waterWall = new THREE.Mesh(waterGeo, waterMat);
            
            let fw = new THREE.Vector3(0, 0, -1).applyQuaternion(playerObj.quaternion);
            gameState.waterWall.position.copy(playerObj.position).add(fw.multiplyScalar(150));
            gameState.waterWall.position.y = getTerrainHeight(gameState.waterWall.position.x, gameState.waterWall.position.z) + 10;
            gameState.waterWall.rotation.x = -Math.PI / 10; 
            gameState.waterWall.lookAt(playerObj.position);
            scene.add(gameState.waterWall);

            setTimeout(startWetlandPhase, 5000);
        }

        function startWetlandPhase() {
            flashScreen.style.display = 'block';
            flashScreen.style.backgroundColor = '#8b7355'; // 泥水の色でフラッシュ
            flashScreen.style.opacity = '1';
            
            stopStormSound();

            setTimeout(() => {
                gameState.phase = 2.1;
                gameState.startZ = 800;
                gameState.goalZ = -800;
                
                buildEnvironment('wetland');
                
                playerObj.position.set(0, getTerrainHeight(0, gameState.startZ) + 1, gameState.startZ);
                playerObj.rotation.set(0, Math.PI, 0); 
                
                flashScreen.style.opacity = '0';
                setTimeout(() => { flashScreen.style.display = 'none'; flashScreen.style.backgroundColor = 'white'; }, 1000);
                
                gameState.isStorming = false;
                if(gameState.waterWall) { scene.remove(gameState.waterWall); gameState.waterWall = null; }
                
                scene.background = createSkyTexture('wetland');
                scene.fog.color.setHex(0x667777);
                scene.fog.density = 0.005;
                hemiLight.intensity = 0.6;
                dirLight.intensity = 0.8;
                updateProgress();
            }, 500);
        }

        function transitionToHomeForest() {
            flashScreen.style.display = 'block';
            flashScreen.style.backgroundColor = '#ffffff';
            flashScreen.style.opacity = '1';

            setTimeout(() => {
                gameState.phase = 2.2;
                buildEnvironment('forest');
                playerObj.position.set(0, getTerrainHeight(0,0) + 1, 0);
                playerObj.rotation.set(0, 0, 0);

                flashScreen.style.opacity = '0';
                setTimeout(() => { flashScreen.style.display = 'none'; }, 1000);

                scene.background = createSkyTexture('forest');
                scene.fog.color.setHex(0x87CEFA);
                scene.fog.density = 0.002;
                hemiLight.intensity = 0.9;
                dirLight.intensity = 1.5;

                progressBar.style.width = '100%';
            }, 500);
        }

        function startPupating() {
            gameState.phase = 2.5;
            gameState.pupateTimer = 0;
            gameState.hasPupated = false;

            let localUp = playerObj.userData.surfaceNormal || new THREE.Vector3(0, 1, 0);
            let forward = new THREE.Vector3(0, 1, 0); 
            if (Math.abs(localUp.dot(forward)) > 0.99) {
                forward.set(1, 0, 0);
            }
            let right = new THREE.Vector3().crossVectors(localUp, forward).normalize();
            forward.crossVectors(right, localUp).normalize();
            let m = new THREE.Matrix4().makeBasis(right, localUp, forward.multiplyScalar(-1));
            gameState.pupateQuat = new THREE.Quaternion().setFromRotationMatrix(m);
            
            gameState.silkGroup = new THREE.Group();
            playerObj.add(gameState.silkGroup);
        }

        function startPhase4() {
            gameState.phase = 4;
            gameState.score = 0;
            gameState.progress = 0;
            gameState.targetProgress = 20; 
            gameState.flySpeed = 0;
            clearScene();

            scene.background = createSkyTexture('forest');
            scene.fog.color.setHex(0x87CEFA);
            scene.fog.density = 0.002;
            hemiLight.intensity = 0.9;
            dirLight.intensity = 1.5;

            dpadContainer.classList.remove('hidden');
            attackBtn.classList.add('hidden'); 
            progressContainer.classList.add('hidden');
            statsBar.classList.remove('hidden');
            scoreDisplay.innerText = `🌸 0 / ${gameState.targetProgress}`;

            const groundGeo = new THREE.PlaneGeometry(2500, 2500, 100, 100);
            const pos = groundGeo.attributes.position;
            for(let i=0; i<pos.count; i++) {
                const px = pos.getX(i); const pz = -pos.getY(i);
                pos.setZ(i, getTerrainHeight(px, pz));
            }
            groundGeo.computeVertexNormals();
            const ground = new THREE.Mesh(groundGeo, new THREE.MeshStandardMaterial({ color: 0x2e8b57, roughness: 1.0, side: THREE.DoubleSide }));
            ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true;
            environmentGroup.add(ground);

            const trunkGeo = new THREE.CylinderGeometry(1, 1.5, 10, 8);
            const leavesGeo = new THREE.DodecahedronGeometry(6, 1);
            const trunkMat = new THREE.MeshStandardMaterial({ color: 0x5c4033, roughness: 1.0 });
            const leavesMat = new THREE.MeshStandardMaterial({ color: 0x1e5622, roughness: 0.8 });

            for(let i=0; i<400; i++) {
                const tree = new THREE.Group();
                const trunk = new THREE.Mesh(trunkGeo, trunkMat); trunk.position.y = 5; trunk.castShadow = true;
                const leaves1 = new THREE.Mesh(leavesGeo, leavesMat); leaves1.position.set(0, 12, 0); leaves1.castShadow = true;
                const leaves2 = new THREE.Mesh(leavesGeo, leavesMat); leaves2.position.set(3, 10, 2); leaves2.scale.set(0.8, 0.8, 0.8); leaves2.castShadow = true;
                tree.add(trunk); tree.add(leaves1); tree.add(leaves2);
                
                const px = (Math.random() - 0.5) * 1800; const pz = (Math.random() - 0.5) * 1800;
                const py = getTerrainHeight(px, pz);
                tree.position.set(px, py - 5, pz);
                if(px*px + pz*pz > 2500) environmentGroup.add(tree);
            }

            playerObj = createButterflyModel(0xffffff);
            playerObj.position.set(0, getTerrainHeight(0,0) + 10, 0);
            scene.add(playerObj);

            const npcColors = [0xffffff, 0xffff00, 0xffa500, 0x87cefa, 0xff69b4];
            for(let i=0; i<15; i++) {
                let npc = createButterflyModel(npcColors[Math.floor(Math.random() * npcColors.length)], true);
                let px = (Math.random() - 0.5) * 800; let pz = (Math.random() - 0.5) * 800;
                let py = getTerrainHeight(px, pz) + 15 + Math.random() * 30;
                npc.position.set(px, py, pz); npc.rotation.y = Math.random() * Math.PI * 2;
                scene.add(npc); npcButterflies.push(npc);
            }

            camera.position.set(0, 15, -15); camera.lookAt(0, 10, 0);

            for(let i=0; i<30; i++) spawnRealFlower();
            createDustParticles();
        }

        function spawnRealFlower() {
            const colors = [0xff1493, 0xffff00, 0xff4500, 0x00ffff, 0xffffff];
            const color = colors[Math.floor(Math.random() * colors.length)];
            const flGroup = new THREE.Group();
            
            const stem = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.2, 5), new THREE.MeshStandardMaterial({color: 0x228b22}));
            stem.position.y = 2.5; flGroup.add(stem);

            const flower = new THREE.Mesh(new THREE.DodecahedronGeometry(2.5, 1), new THREE.MeshStandardMaterial({ color: color, roughness: 0.3, emissive: color, emissiveIntensity: 0.3 }));
            flower.position.y = 5; flower.castShadow = true; flGroup.add(flower);
            
            let px = (playerObj ? playerObj.position.x : 0) + (Math.random() - 0.5) * 300;
            let pz = (playerObj ? playerObj.position.z : 0) + (Math.random() - 0.5) * 300;
            let py = getTerrainHeight(px, pz);
            
            flGroup.position.set(px, py, pz);
            scene.add(flGroup);
            gameObjects.push({ mesh: flGroup, type: 'flower' });
        }

        function showResult() {
            gameState.phase = 5;
            clearScene();
            scene.fog = new THREE.FogExp2(0x87CEEB, 0.005);
            scene.background = createSkyTexture('forest');

            dpadContainer.classList.add('hidden');
            statsBar.classList.add('hidden');
            progressContainer.classList.add('hidden');
            
            startBtn.innerText = "▶";
            startBtn.classList.remove('hidden');

            camera.position.set(0, 0, 10); camera.lookAt(0, 0, 0);
            createDustParticles();
        }

        // --- メインループ ---
        function animate() {
            requestAnimationFrame(animate);
            const delta = Math.min(clock.getDelta(), 0.1); 
            const time = clock.getElapsedTime();

            if (gameState.isStorming) {
                // 嵐が徐々に激しくなる演出
                gameState.stormIntensity = Math.min(1.0, gameState.stormIntensity + delta * 0.2);
                
                // 空が暗くなり、霧が濃くなる
                scene.fog.density = 0.015 + gameState.stormIntensity * 0.05;
                hemiLight.intensity = Math.max(0.1, 0.9 - gameState.stormIntensity);
                dirLight.intensity = Math.max(0.1, 1.5 - gameState.stormIntensity * 1.5);
                
                // 音の激しさ(周波数と音量)を上げる
                if (windGain && gameState.windFilter) {
                    windGain.gain.value = gameState.stormIntensity;
                    gameState.windFilter.frequency.value = 100 + gameState.stormIntensity * 2000;
                }

                if (particleSystem) {
                    const pos = particleSystem.geometry.attributes.position.array;
                    let speed = 15 + gameState.stormIntensity * 30;
                    for(let i=0; i<pos.length; i+=3) { 
                        pos[i] += speed * delta; pos[i+1] -= (speed * 3) * delta; pos[i+2] += speed * delta; 
                        if (pos[i+1] < -50) { pos[i] = (Math.random() - 0.5) * 300; pos[i+1] = 150; pos[i+2] = (Math.random() - 0.5) * 300; }
                    }
                    particleSystem.geometry.attributes.position.needsUpdate = true;
                }
                
                // 鉄砲水がものすごい勢いで迫ってくる
                if (gameState.waterWall && playerObj) {
                    let dir = new THREE.Vector3().subVectors(playerObj.position, gameState.waterWall.position).normalize();
                    gameState.waterWall.position.add(dir.multiplyScalar(60 * delta));
                    
                    // 水に飲み込まれたらカメラを揺らす
                    if (gameState.waterWall.position.distanceTo(playerObj.position) < 10) {
                        camera.position.x += (Math.random() - 0.5) * 2;
                        camera.position.y += (Math.random() - 0.5) * 2;
                    }
                }
                
            } else {
                if (particleSystem) {
                    particleSystem.rotation.y += 0.01 * delta;
                    const pos = particleSystem.geometry.attributes.position.array;
                    for(let i=1; i<pos.length; i+=3) { pos[i] -= 2 * delta; if (pos[i] < -50) pos[i] = 150; }
                    particleSystem.geometry.attributes.position.needsUpdate = true;
                }
            }

            enemies.forEach(e => {
                if (e.userData.stunTimer > 0) {
                    e.userData.stunTimer -= delta;
                    return; 
                }
                
                let th = getTerrainHeight(e.position.x, e.position.z);
                
                if (e.userData.type === 'antSwarm') {
                    e.position.x += Math.sin(time * 0.3 + e.userData.seed) * 6 * delta;
                    e.position.z += Math.cos(time * 0.2 + e.userData.seed) * 6 * delta;
                    e.position.y = th; 
                    
                    e.children.forEach((ant, idx) => {
                        let data = e.userData.childrenData[idx];
                        ant.rotation.y += Math.sin(time * data.speed + data.phase) * 0.1;
                        let fw = new THREE.Vector3(0, 0, 1).applyQuaternion(ant.quaternion);
                        ant.position.add(fw.multiplyScalar(4 * delta));

                        if (ant.position.lengthSq() > 40) {
                            let targetRot = Math.atan2(ant.position.x, ant.position.z) + Math.PI;
                            ant.quaternion.slerp(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), targetRot), 0.1);
                        }
                        
                        let antTh = getTerrainHeight(e.position.x + ant.position.x, e.position.z + ant.position.z);
                        antTh = Math.max(antTh, -5); 
                        ant.position.y = antTh - th;

                        if (ant.userData.legs) {
                            ant.userData.legs.forEach(leg => {
                                leg.mesh.rotation.x = Math.sin(time * 20 * data.speed + leg.offset * Math.PI + leg.side * Math.PI/2) * 0.5;
                            });
                        }
                    });
                } 
                else if (e.userData.type === 'spider') {
                    let moveY = Math.sin(time * 1.5 + e.userData.seed);
                    e.position.y = th + 10 + moveY * 8;
                    e.rotation.y += delta * 0.5;
                    e.userData.legs.forEach(leg => {
                        leg.mesh.rotation.z = Math.sin(time * 10 + leg.offset) * 0.2 * moveY;
                    });
                } 
                else if (e.userData.type === 'plant') {
                    e.position.y = th;
                    let chomp = Math.max(0, Math.sin(time*3 + e.userData.seed));
                    e.children[0].children[0].rotation.x = Math.PI/4 - chomp * 0.6;
                    e.children[0].children[1].rotation.x = Math.PI/4 - chomp * 0.6;
                    e.children[0].children[2].rotation.x = -Math.PI/4 + chomp * 0.6;
                    e.children[0].children[3].rotation.x = -Math.PI/4 + chomp * 0.6;
                }
            });

            for (let i = gameState.silks.length - 1; i >= 0; i--) {
                let s = gameState.silks[i];
                s.life -= delta;
                s.mesh.position.add(s.velocity.clone().multiplyScalar(delta));
                s.mesh.scale.addScalar(s.scaleSpeed * delta); 
                s.mesh.rotation.z += delta * 5;
                
                let hitEnemy = null;
                for(let e of enemies) {
                    if(e.userData.stunTimer && e.userData.stunTimer > 0) continue;
                    let threshold = (e.userData.type === 'antSwarm' ? 8.0 : 4.0) * s.mesh.scale.x;
                    if(e.position.distanceTo(s.mesh.position) < threshold) {
                        hitEnemy = e;
                        break;
                    }
                }
                
                if (hitEnemy) {
                    hitEnemy.userData.stunTimer = 5.0; 
                    
                    const bindMesh = s.mesh.clone();
                    bindMesh.position.set(0, 0, 0);
                    bindMesh.rotation.set(0, 0, 0);
                    hitEnemy.add(bindMesh);
                    setTimeout(() => { if(hitEnemy) hitEnemy.remove(bindMesh); }, 5000); 
                    
                    scene.remove(s.mesh);
                    gameState.silks.splice(i, 1);
                    continue;
                }
                
                if(s.life <= 0) {
                    scene.remove(s.mesh);
                    gameState.silks.splice(i, 1);
                }
            }

            if ((gameState.phase === 2 || gameState.phase === 2.1 || gameState.phase === 2.2) && playerObj && !gameState.isStorming) {
                
                let hitEnemy = null;
                for(let e of enemies) {
                    if (e.userData.stunTimer && e.userData.stunTimer > 0) continue;
                    let dist = playerObj.position.distanceTo(e.position);
                    let threshold = e.userData.type === 'antSwarm' ? 6.0 : 3.5;
                    if (dist < threshold) { hitEnemy = e; break; }
                }
                
                if (hitEnemy) {
                    let backward = new THREE.Vector3(0, 0, 1).applyQuaternion(playerObj.quaternion);
                    playerObj.position.add(backward.multiplyScalar(5.0));
                    
                    flashScreen.style.display = 'block';
                    flashScreen.style.backgroundColor = 'red';
                    flashScreen.style.opacity = '0.4';
                    setTimeout(() => { flashScreen.style.opacity = '0'; }, 200);
                    setTimeout(() => { flashScreen.style.display = 'none'; flashScreen.style.backgroundColor = 'white'; }, 1000);
                }

                let oldPos = playerObj.position.clone();
                let oldQuat = playerObj.quaternion.clone();

                let isMoving = false;
                const moveSpeed = 6.0 * delta; 
                const rotSpeed = 2.0 * delta;

                if (inputState.up) { playerObj.translateZ(-moveSpeed); isMoving = true; }
                if (inputState.down) { playerObj.translateZ(moveSpeed); isMoving = true; }
                if (inputState.left) { playerObj.rotateY(rotSpeed); if(!inputState.up && !inputState.down) playerObj.translateZ(-moveSpeed); isMoving = true; }
                if (inputState.right) { playerObj.rotateY(-rotSpeed); if(!inputState.up && !inputState.down) playerObj.translateZ(-moveSpeed); isMoving = true; }

                const isSolid = (o) => !o.object.userData.isLeaves && !o.object.userData.isEnemy;

                let localUp = new THREE.Vector3(0, 1, 0).applyQuaternion(playerObj.quaternion).normalize();
                let localForward = new THREE.Vector3(0, 0, -1).applyQuaternion(playerObj.quaternion).normalize();
                let hitObj = null; let hitNormal = new THREE.Vector3(0, 1, 0); let hitPoint = playerObj.position.clone();

                let footOrigin = playerObj.position.clone().add(localUp.clone().multiplyScalar(2.0));
                raycaster.set(footOrigin, localUp.clone().multiplyScalar(-1));
                let footIntersects = raycaster.intersectObjects(environmentGroup.children, true).filter(isSolid);

                if (footIntersects.length > 0 && footIntersects[0].distance < 4.0) {
                    hitObj = footIntersects[0];
                    if (hitObj.face) hitNormal.copy(hitObj.face.normal).transformDirection(hitObj.object.matrixWorld).normalize();
                    hitPoint.copy(hitObj.point);
                }

                if (isMoving) {
                    let fwdOrigin = playerObj.position.clone().add(localUp.clone().multiplyScalar(0.8));
                    raycaster.set(fwdOrigin, localForward);
                    let fwdIntersects = raycaster.intersectObjects(environmentGroup.children, true).filter(isSolid);
                    
                    if (fwdIntersects.length > 0 && fwdIntersects[0].distance < 1.5) {
                        hitObj = fwdIntersects[0];
                        if (hitObj.face) hitNormal.copy(hitObj.face.normal).transformDirection(hitObj.object.matrixWorld).normalize();
                        hitPoint.copy(hitObj.point);
                    }
                }

                if (!hitObj) {
                    let fallOrigin = playerObj.position.clone(); fallOrigin.y += 20.0; 
                    raycaster.set(fallOrigin, new THREE.Vector3(0, -1, 0));
                    let fallbacks = raycaster.intersectObjects(environmentGroup.children, true).filter(isSolid);
                    if (fallbacks.length > 0) {
                        hitObj = fallbacks[0];
                        if (hitObj.face) hitNormal.copy(hitObj.face.normal).transformDirection(hitObj.object.matrixWorld).normalize();
                        hitPoint.copy(hitObj.point);
                    }
                }

                if (hitObj) {
                    let targetUp = hitNormal; playerObj.userData.surfaceNormal = targetUp.clone();
                    let targetPos = hitPoint.clone().add(targetUp.clone().multiplyScalar(0.8));
                    playerObj.position.lerp(targetPos, isMoving ? 0.4 : 0.8); 
                    
                    let alignQuat = new THREE.Quaternion().setFromUnitVectors(localUp, targetUp);
                    let targetQuat = playerObj.quaternion.clone().premultiply(alignQuat);
                    playerObj.quaternion.slerp(targetQuat, isMoving ? 0.3 : 0.6);
                } else {
                    playerObj.position.copy(oldPos); playerObj.quaternion.copy(oldQuat);
                }

                const mapLimit = gameState.phase === 2.1 ? 1000 : 120;
                if(playerObj.position.x > mapLimit) playerObj.position.x = mapLimit;
                if(playerObj.position.x < -mapLimit) playerObj.position.x = -mapLimit;
                if(gameState.phase !== 2.1) {
                    if(playerObj.position.z > mapLimit) playerObj.position.z = mapLimit;
                    if(playerObj.position.z < -mapLimit) playerObj.position.z = -mapLimit;
                }

                const terrainY = getTerrainHeight(playerObj.position.x, playerObj.position.z);
                if (playerObj.position.y < terrainY + 0.8) {
                    playerObj.position.y = terrainY + 0.8;
                    if (!hitObj) playerObj.userData.surfaceNormal = new THREE.Vector3(0, 1, 0);
                }

                playerObj.children.forEach((seg, i) => {
                    if (seg.userData && seg.userData.baseY !== undefined) {
                        if (isMoving) {
                            seg.position.y = seg.userData.baseY + Math.max(0, Math.sin(time * 15 - i * 0.8)) * 0.15;
                        } else {
                            seg.position.y = seg.userData.baseY + Math.sin(time * 2 - i * 0.3) * 0.02;
                        }

                        seg.children.forEach(child => {
                            if (child.userData && child.userData.isLeg) {
                                child.rotation.x = child.userData.baseRotX + (isMoving ? Math.sin(time * 20 - i) * 0.4 : 0);
                                
                                let grabAngle = playerObj.userData.surfaceNormal.y < 0.8 ? 0.5 : 0;
                                child.rotation.z = child.userData.baseRotZ - (child.userData.side * grabAngle);
                            }
                        });
                    }
                });

                let flatForward = localForward.clone(); flatForward.y = 0; 
                if (flatForward.lengthSq() < 0.001) {
                    flatForward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
                    flatForward.y = 0;
                    if (flatForward.lengthSq() < 0.001) flatForward.set(0, 0, -1);
                }
                flatForward.normalize();

                const targetCamPos = playerObj.position.clone().add(flatForward.multiplyScalar(-10)).add(new THREE.Vector3(0, 7, 0));
                let terrainHeightAtCam = getTerrainHeight(targetCamPos.x, targetCamPos.z);
                if (targetCamPos.y < terrainHeightAtCam + 3) targetCamPos.y = terrainHeightAtCam + 3;

                camera.position.lerp(targetCamPos, 0.08);
                camera.up.set(0, 1, 0); 
                camera.lookAt(playerObj.position);

                if (gameState.phase === 2) {
                    for (let i = gameObjects.length - 1; i >= 0; i--) {
                        if (!gameObjects[i]) continue;
                        const foodGroup = gameObjects[i].mesh;
                        if (foodGroup.children.length >= 3) {
                            foodGroup.children[0].rotation.y += delta * 2; 
                            foodGroup.children[1].scale.setScalar(1 + Math.sin(time * 5 + i) * 0.2); 
                            foodGroup.children[2].rotation.y -= delta; 
                        }
                        
                        const dx = playerObj.position.x - foodGroup.position.x;
                        const dy = playerObj.position.y - foodGroup.position.y;
                        const dz = playerObj.position.z - foodGroup.position.z;
                        const distSqXZ = dx * dx + dz * dz;

                        if (distSqXZ < 25.0 && Math.abs(dy) < 10.0) {
                            scene.remove(foodGroup); gameObjects.splice(i, 1);
                            if (gameState.progress < gameState.targetProgress) {
                                gameState.progress++; updateProgress();
                                playerObj.scale.addScalar(0.06);
                                if (gameState.progress >= gameState.targetProgress && !gameState.hasStormed) {
                                    triggerStorm();
                                } else {
                                    spawnFood('forest'); 
                                }
                            }
                        }
                    }
                } 
                else if (gameState.phase === 2.1) {
                    updateProgress();
                    if (playerObj.position.z <= gameState.goalZ) {
                        transitionToHomeForest();
                    }
                }
                else if (gameState.phase === 2.2) {
                    if (playerObj.position.y > terrainY + 8) { 
                        if (!gameState.isReadyToPupate) {
                            gameState.isReadyToPupate = true;
                            actionBtn.classList.remove('hidden'); 
                            actionBtn.classList.add('animate-pulse');
                            attackBtn.classList.add('hidden');
                        }
                    } else {
                        if (gameState.isReadyToPupate) {
                            gameState.isReadyToPupate = false;
                            actionBtn.classList.add('hidden'); 
                            actionBtn.classList.remove('animate-pulse');
                            attackBtn.classList.remove('hidden');
                        }
                    }
                }
            }
            else if (gameState.phase === 2.5 && playerObj) {
                gameState.pupateTimer += delta;

                if (gameState.pupateQuat) {
                    playerObj.quaternion.slerp(gameState.pupateQuat, 0.05);
                }

                if (playerObj.children[0]) {
                    playerObj.children[0].rotation.x = Math.max(-0.6, -gameState.pupateTimer * 0.3);
                }

                if (gameState.pupateTimer < 3.5) {
                    if (Math.random() < 0.4) {
                        const sGeo = new THREE.CylinderGeometry(0.01, 0.01, 1.5 + Math.random(), 4);
                        const sMat = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.6});
                        const s = new THREE.Mesh(sGeo, sMat);
                        s.position.set((Math.random()-0.5)*1.5, (Math.random()-0.5)*2.0, -0.5);
                        s.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, Math.random()*Math.PI);
                        gameState.silkGroup.add(s);
                    }
                } 
                else if (gameState.pupateTimer > 4.5 && !gameState.hasPupated) {
                    gameState.hasPupated = true;
                    
                    let savedPos = playerObj.position.clone();
                    let savedQuat = playerObj.quaternion.clone();
                    let silkGroupClone = gameState.silkGroup.clone(); 
                    
                    scene.remove(playerObj);
                    
                    playerObj = new THREE.Group();
                    const chrys = new THREE.Mesh(new THREE.CylinderGeometry(0.9, 0.1, 3.5, 32), new THREE.MeshStandardMaterial({ color: 0x556b2f, roughness: 0.6, metalness: 0.2, bumpScale: 0.05 }));
                    chrys.position.y = -1.5; chrys.rotation.x = 0.2; chrys.castShadow = true;
                    
                    const mainSilk = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 2.0, 8), new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 }));
                    mainSilk.position.set(0, 0.5, 0);
                    
                    playerObj.add(mainSilk); 
                    playerObj.add(chrys); 
                    playerObj.add(silkGroupClone); 
                    playerObj.position.copy(savedPos);
                    playerObj.quaternion.copy(savedQuat);
                    playerObj.userData = { shakeTime: 0 };
                    scene.add(playerObj);
                    
                    setTimeout(() => {
                        gameState.phase = 3;
                        gameState.progress = 0;
                        gameState.targetProgress = 20;
                    }, 1000);
                }

                const offset = new THREE.Vector3(0, 4, 12);
                const rotationMatrix = new THREE.Matrix4().extractRotation(playerObj.matrix);
                offset.applyMatrix4(rotationMatrix);
                const targetCamPos = playerObj.position.clone().add(offset);
                camera.position.lerp(targetCamPos, 0.02);
                camera.up.set(0, 1, 0); camera.lookAt(playerObj.position);
            }
            else if (gameState.phase === 3 && playerObj) {
                if (playerObj.userData.shakeTime > 0) {
                    playerObj.userData.shakeTime -= delta;
                    playerObj.rotation.z = Math.sin(time * 60) * 0.1;
                } else {
                    playerObj.rotation.z = 0;
                }
                const offset = new THREE.Vector3(Math.sin(time*0.5)*10, 2, Math.cos(time*0.5)*10);
                camera.position.lerp(playerObj.position.clone().add(offset), 0.05);
                camera.up.set(0, 1, 0); camera.lookAt(playerObj.position);
            }
            else if (gameState.phase === 4 && playerObj) {
                let targetSpeed = 0;
                if (inputState.up) targetSpeed = 50;
                if (inputState.down) targetSpeed = -20; 
                gameState.flySpeed = THREE.MathUtils.lerp(gameState.flySpeed, targetSpeed, 0.05);

                const turnSpeed = 2.0 * delta;
                if (inputState.left) playerObj.userData.baseRotY += turnSpeed;
                if (inputState.right) playerObj.userData.baseRotY -= turnSpeed;

                let targetPitch = (gameState.pointer.y || 0) * (Math.PI / 2.5);
                playerObj.userData.baseRotX = THREE.MathUtils.lerp(playerObj.userData.baseRotX, targetPitch, 0.08);

                let targetRoll = 0;
                if (inputState.left) targetRoll = Math.PI / 4;
                if (inputState.right) targetRoll = -Math.PI / 4;
                playerObj.userData.baseRotZ = THREE.MathUtils.lerp(playerObj.userData.baseRotZ, targetRoll, 0.08);

                playerObj.rotation.set(playerObj.userData.baseRotX, playerObj.userData.baseRotY, playerObj.userData.baseRotZ, 'YXZ');

                let localForward = new THREE.Vector3(0, 0, 1).applyQuaternion(playerObj.quaternion).normalize();
                let rayOrigin = playerObj.position.clone();
                raycaster.set(rayOrigin, localForward);
                let intersects = raycaster.intersectObjects(environmentGroup.children, true);

                let isLanded = false;
                if (intersects.length > 0 && intersects[0].distance < 3.0) {
                    let hitPoint = intersects[0].point.clone();
                    let hitNorm = new THREE.Vector3(0,1,0);
                    if(intersects[0].face) hitNorm.copy(intersects[0].face.normal).transformDirection(intersects[0].object.matrixWorld).normalize();
                    playerObj.position.copy(hitPoint.add(hitNorm.multiplyScalar(2.0)));
                    gameState.flySpeed = 0; isLanded = true;
                }

                let currentHeight = getTerrainHeight(playerObj.position.x, playerObj.position.z);
                if (playerObj.position.y < currentHeight + 3.0) {
                    playerObj.position.y = currentHeight + 3.0;
                    if (gameState.flySpeed < 10 && !inputState.up) isLanded = true; 
                }
                if (playerObj.position.y > 200) playerObj.position.y = 200; 

                if (!isLanded) {
                    playerObj.translateZ(gameState.flySpeed * delta);
                    playerObj.position.y += Math.sin(time * 10) * 0.05;
                }

                let flapSpeed = isLanded ? 2 : (15 + Math.abs(gameState.flySpeed) * 0.2);
                let flapAmp = isLanded ? 0.1 : 0.8;
                const flapTime = time * flapSpeed;
                const flap = Math.sin(flapTime) * flapAmp - (isLanded ? 0 : 0.2);
                
                if (playerObj.userData.lWing) {
                    playerObj.userData.lWing.rotation.z = flap;
                    playerObj.userData.rWing.rotation.z = -flap;
                }
                if (playerObj.userData.lAnt) {
                    playerObj.userData.lAnt.rotation.x = Math.PI/2.5 + Math.sin(time * 5) * 0.1;
                    playerObj.userData.rAnt.rotation.x = Math.PI/2.5 + Math.sin(time * 5.2) * 0.1;
                }

                for (let i = gameObjects.length - 1; i >= 0; i--) {
                    if (!gameObjects[i]) continue;
                    const flGroup = gameObjects[i].mesh;
                    if(flGroup.children.length > 1) flGroup.children[1].rotation.y += delta; 

                    if (playerObj.position.distanceTo(flGroup.position) < 8) {
                        scene.remove(flGroup); gameObjects.splice(i, 1);
                        gameState.score += 100;
                        if(gameState.progress < gameState.targetProgress) {
                            gameState.progress++; updateProgress();
                            scoreDisplay.innerText = `🌸 ${gameState.progress} / ${gameState.targetProgress}`;
                            if (gameState.progress >= gameState.targetProgress) {
                                finishBtn.classList.remove('hidden');
                            }
                        }
                        spawnRealFlower(); continue;
                    }
                    if (playerObj.position.distanceTo(flGroup.position) > 400) {
                        scene.remove(flGroup); gameObjects.splice(i, 1); spawnRealFlower();
                    }
                }

                npcButterflies.forEach((npc, i) => {
                    npc.translateZ(25 * delta); 
                    npc.rotation.y += Math.sin(time * 0.5 + i) * delta; 
                    npc.position.y += Math.cos(time + i) * 0.1; 
                    
                    let nth = getTerrainHeight(npc.position.x, npc.position.z);
                    if(npc.position.y < nth + 5) npc.position.y = nth + 5;
                    if(npc.position.y > 150) npc.position.y = 150;

                    if(npc.position.distanceTo(playerObj.position) > 600) {
                        npc.position.copy(playerObj.position).add(new THREE.Vector3((Math.random()-0.5)*400, (Math.random()-0.5)*100, (Math.random()-0.5)*400));
                    }

                    let nFlap = Math.sin(time * 15 + i) * 0.8 - 0.2;
                    if (npc.userData.lWing) {
                        npc.userData.lWing.rotation.z = nFlap;
                        npc.userData.rWing.rotation.z = -nFlap;
                    }
                });

                let flatForward = new THREE.Vector3(0, 0, 1).applyQuaternion(playerObj.quaternion);
                flatForward.y = 0; 
                if (flatForward.lengthSq() < 0.001) {
                    flatForward = new THREE.Vector3(0, 0, 1).applyQuaternion(camera.quaternion);
                    flatForward.y = 0;
                    if (flatForward.lengthSq() < 0.001) flatForward.set(0, 0, 1);
                }
                flatForward.normalize();
                
                let targetCamPos = playerObj.position.clone().add(flatForward.multiplyScalar(-15)).add(new THREE.Vector3(0, 6, 0));
                let tcHeight = getTerrainHeight(targetCamPos.x, targetCamPos.z);
                if(targetCamPos.y < tcHeight + 2) targetCamPos.y = tcHeight + 2;

                camera.position.lerp(targetCamPos, 0.1);
                camera.up.set(0, 1, 0);
                camera.lookAt(playerObj.position);
            }
            else if (gameState.phase === 5) {
                if (camera.parent) camera.parent.remove(camera);
                camera.position.x = Math.sin(time * 0.3) * 30;
                camera.position.z = Math.cos(time * 0.3) * 30;
                camera.position.y = 20 + Math.sin(time * 0.5) * 10;
                camera.up.set(0, 1, 0);
                camera.lookAt(0, 10, 0);
            }

            renderer.render(scene, camera);
        }

        startBtn.addEventListener('click', () => {
            startBtn.classList.add('hidden');
            startPhase1();
        });

        animate();

    </script>
</body>
</html>
✨ ピックアップ

アゲハ蝶の一生

👁 55 回閲覧
📅 投稿:2026年3月8日 🔄 更新:2026年8月13日
応援スタンプを押してみよう!