🔬 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">
    <title>怪獣大進撃 3D - Kaiju City Destruction</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
    <style>
        body { margin: 0; overflow: hidden; background-color: #000; font-family: sans-serif; }
        #start-screen {
            backdrop-filter: blur(8px);
        }
        #ui-layer {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            pointer-events: none;
            z-index: 10;
            display: none; 
            flex-direction: column;
            justify-content: space-between;
            padding: 1rem;
        }
        .pointer-events-auto { pointer-events: auto; }
        ::-webkit-scrollbar { width: 6px; }
        ::-webkit-scrollbar-track { background: rgba(0,0,0,0.5); }
        ::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 3px; }
    </style>
</head>
<body>

    <div id="canvas-container" class="absolute inset-0 z-0"></div>

    <!-- スタート画面 (怪獣選択) -->
    <div id="start-screen" class="fixed inset-0 bg-black/70 z-50 flex flex-col items-center justify-center pointer-events-auto transition-opacity duration-500">
        <h1 class="text-5xl md:text-7xl font-bold text-white mb-4 tracking-widest drop-shadow-[0_0_15px_rgba(255,255,255,0.5)]">怪獣大進撃 3D</h1>
        <p class="text-gray-300 mb-12 text-lg drop-shadow-md">果てしない世界を破壊する大怪獣を選択してください</p>
        
        <div class="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl w-full px-4">
            <button onclick="startGame('godzilla')" class="group bg-neutral-900/80 border-2 border-blue-900 rounded-xl p-6 hover:bg-blue-900/50 hover:border-blue-500 transition-all transform hover:-translate-y-2 shadow-lg">
                <h2 class="text-3xl font-bold text-blue-400 mb-2">ゴジラ</h2>
                <p class="text-sm text-gray-400">圧倒的な耐久力と強力な「放射熱線」で全てを焼き尽くす怪獣王。</p>
            </button>
            <button onclick="startGame('rodan')" class="group bg-neutral-900/80 border-2 border-orange-900 rounded-xl p-6 hover:bg-orange-900/50 hover:border-orange-500 transition-all transform hover:-translate-y-2 shadow-lg">
                <h2 class="text-3xl font-bold text-orange-500 mb-2">ラドン</h2>
                <p class="text-sm text-gray-400">上空から突風で街を粉砕し「マグマ熱線」を放つ空の大怪獣。</p>
            </button>
            <button onclick="startGame('anguirus')" class="group bg-neutral-900/80 border-2 border-amber-900 rounded-xl p-6 hover:bg-amber-900/50 hover:border-amber-500 transition-all transform hover:-translate-y-2 shadow-lg">
                <h2 class="text-3xl font-bold text-amber-500 mb-2">アンギラス</h2>
                <p class="text-sm text-gray-400">背中に無数の鋭いトゲを持つ暴龍。四足歩行の突進と「超音波咆哮」で街を粉砕する。</p>
            </button>
            <button onclick="startGame('gamera')" class="group bg-neutral-900/80 border-2 border-green-900 rounded-xl p-6 hover:bg-green-900/50 hover:border-green-500 transition-all transform hover:-translate-y-2 shadow-lg">
                <h2 class="text-3xl font-bold text-green-400 mb-2">ガメラ</h2>
                <p class="text-sm text-gray-400">甲羅からジェット噴射して飛び、「プラズマ火球」を放つ大怪獣。</p>
            </button>
            <button onclick="startGame('gyaos')" class="group bg-neutral-900/80 border-2 border-red-900 rounded-xl p-6 hover:bg-red-900/50 hover:border-red-500 transition-all transform hover:-translate-y-2 shadow-lg">
                <h2 class="text-3xl font-bold text-red-500 mb-2">ギャオス</h2>
                <p class="text-sm text-gray-400">超高速で飛行し、全てを両断する「超音波メス」を放つ凶鳥。</p>
            </button>
        </div>
    </div>

    <!-- ゲームUI -->
    <div id="ui-layer">
        <div class="flex flex-col md:flex-row gap-4 justify-between items-start pointer-events-auto">
            <div class="bg-neutral-900/80 backdrop-blur-sm p-4 rounded-xl border border-neutral-700 w-full md:w-80 shadow-lg">
                <div class="flex justify-between items-center mb-3 pb-2 border-b border-neutral-600">
                    <h1 id="monster-name" class="text-lg font-bold text-white">Monster City Destruction</h1>
                    <div class="flex items-center gap-2">
                        <div class="text-sm font-bold bg-neutral-800 px-3 py-1 rounded text-white border border-neutral-600">
                            <span id="day-counter">1</span>日目 <span id="time-display" class="text-yellow-300 ml-1">12:00</span>
                        </div>
                        <button onclick="returnToHome()" class="text-xs bg-red-900 hover:bg-red-700 text-white px-2 py-1 rounded border border-red-700 transition">終了</button>
                    </div>
                </div>
                <div class="space-y-3">
                    <div><div class="flex justify-between text-xs mb-1 text-gray-200"><span>❤️ HP (無敵)</span><span id="hp-text">100</span></div></div>
                    <div><div class="flex justify-between text-xs mb-1 text-gray-200"><span>🍖 満腹度</span><span id="food-text">100</span></div></div>
                    <div><div class="flex justify-between text-xs mb-1 text-gray-200"><span>💧 水分</span><span id="water-text">100</span></div></div>
                    <div><div class="flex justify-between text-xs mb-1 text-gray-200"><span>⚡ スタミナ (無尽蔵)</span><span id="energy-text">100</span></div></div>
                </div>
            </div>

            <!-- PC用操作ガイド (スマホでは非表示) -->
            <div class="hidden md:flex bg-neutral-900/80 backdrop-blur-sm p-4 rounded-xl border border-neutral-700 text-gray-200 text-sm shadow-lg flex-col gap-3">
                <div>
                    <h2 class="font-bold text-gray-400 mb-1 border-b border-neutral-700 pb-1">🎮 操作方法</h2>
                    <p>十字キー / WASD : 移動 (Shiftでダッシュ)</p>
                    <p>Spaceキー : <span id="action-beam-name" class="font-bold">ビーム (撃ち放題)</span></p>
                    <p>体や翼でビル・敵部隊を粉砕!</p>
                </div>
                <div>
                    <h2 class="font-bold text-gray-400 mb-1 border-b border-neutral-700 pb-1">🎒 所持品 & アクション</h2>
                    <ul class="space-y-1">
                        <li class="flex justify-between items-center"><button onclick="gameAction('eat')" class="hover:text-orange-400 transition">[2] 🍖 食料</button> <span id="inv-food" class="font-bold">2</span></li>
                        <li class="flex justify-between items-center"><button onclick="gameAction('drink')" class="hover:text-blue-400 transition">[3] 💧 水</button> <span id="inv-water" class="font-bold">2</span></li>
                    </ul>
                    <button onclick="gameAction('sleep')" class="w-full mt-2 bg-indigo-700 hover:bg-indigo-600 text-white rounded py-1 transition">[5] ⛺ 休む</button>
                </div>
            </div>
        </div>

        <div class="flex justify-start pointer-events-auto max-w-lg mt-auto mb-32 md:mb-0">
            <div id="log-container" class="bg-black/60 backdrop-blur p-3 rounded-xl border border-neutral-800 w-full h-24 md:h-32 overflow-y-auto text-xs text-gray-300 space-y-1 flex flex-col-reverse shadow-lg"></div>
        </div>
        
        <!-- モバイル用タッチコントローラー -->
        <div class="md:hidden absolute bottom-4 left-0 w-full px-4 flex justify-between items-end pointer-events-auto z-20">
            <!-- ジョイスティック (左下) -->
            <div id="joystick-zone" class="relative w-32 h-32 bg-white/10 rounded-full border border-white/20 touch-none flex items-center justify-center">
                <div id="joystick-knob" class="w-12 h-12 bg-white/40 rounded-full absolute"></div>
            </div>

            <!-- アクションボタン (右下) -->
            <div class="flex flex-col gap-2 items-end">
                <div class="flex gap-2">
                    <button onclick="gameAction('eat')" class="w-12 h-12 bg-orange-900/80 text-white rounded-full flex items-center justify-center text-xl shadow-lg border border-orange-700">🍖</button>
                    <button onclick="gameAction('drink')" class="w-12 h-12 bg-blue-900/80 text-white rounded-full flex items-center justify-center text-xl shadow-lg border border-blue-700">💧</button>
                </div>
                <button onclick="gameAction('beam')" class="w-20 h-20 bg-red-900/80 text-white font-bold rounded-full flex items-center justify-center text-lg shadow-lg border border-red-700 mt-2">攻撃</button>
            </div>
        </div>
    </div>

    <!-- ゲームオーバー -->
    <div id="game-over-modal" class="fixed inset-0 bg-red-950/90 z-50 hidden flex flex-col items-center justify-center pointer-events-auto">
        <h2 class="text-5xl font-bold text-red-500 mb-4 drop-shadow-lg">GAME OVER</h2>
        <p id="death-reason" class="text-xl text-gray-300 mb-8">討伐されてしまった...</p>
        <p class="text-lg text-gray-400 mb-8 border border-neutral-700 p-4 rounded bg-black/50">生存日数: <span id="final-days" class="text-white font-bold text-2xl"></span> 日</p>
        <button onclick="returnToHome()" class="bg-red-700 hover:bg-red-600 text-white font-bold py-3 px-8 rounded-lg text-xl shadow-lg transition">タイトルへ戻る</button>
    </div>

    <script>
        // === 怪獣データ定義 ===
        const MONSTERS = {
            godzilla: { name: 'ゴジラ', type: 'walk', height: 0, speed: 0.15, runMult: 2.0, uiClass: 'text-blue-400', color: 0x111111, emissive: 0x0088ff, beamName: '放射熱線', beamColor: 0x88ffff },
            rodan: { name: 'ラドン', type: 'fly', height: 12, speed: 0.25, runMult: 2.0, uiClass: 'text-orange-500', color: 0x3b1510, emissive: 0xff3300, beamName: 'マグマ熱線', beamColor: 0xffaa00 },
            anguirus: { name: 'アンギラス', type: 'walk', height: 0, speed: 0.18, runMult: 2.2, uiClass: 'text-amber-500', color: 0x8b5a2b, emissive: 0x442200, beamName: '超音波咆哮', beamColor: 0xdddddd },
            gamera: { name: 'ガメラ', type: 'walk', height: 0, speed: 0.12, runMult: 1.5, uiClass: 'text-green-400', color: 0x112211, emissive: 0xffaa00, beamName: 'プラズマ火球', beamColor: 0xff8800 },
            gyaos: { name: 'ギャオス', type: 'fly', height: 10, speed: 0.28, runMult: 2.2, uiClass: 'text-red-500', color: 0x4a2525, emissive: 0xaa0000, beamName: '超音波メス', beamColor: 0xffffaa }
        };

        const state = {
            isPlaying: false, charId: null,
            day: 1, gameTime: 12, isGameOver: false,
            stats: { hp: 100, food: 100, water: 100, energy: 100 },
            inventory: { food: 2, water: 2, wood: 0 },
            cooldowns: { beam: 0 } 
        };

        const scene = new THREE.Scene();
        scene.fog = new THREE.FogExp2(0x87CEEB, 0.015);

        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        renderer.outputEncoding = THREE.sRGBEncoding;
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        document.getElementById('canvas-container').appendChild(renderer.domElement);

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

        const sunLight = new THREE.DirectionalLight(0xffffff, 1.0);
        sunLight.castShadow = true;
        sunLight.shadow.mapSize.width = 2048; sunLight.shadow.mapSize.height = 2048;
        sunLight.shadow.camera.near = 0.5; sunLight.shadow.camera.far = 500;
        sunLight.shadow.camera.left = -100; sunLight.shadow.camera.right = 100;
        sunLight.shadow.camera.top = 100; sunLight.shadow.camera.bottom = -100;
        scene.add(sunLight);
        scene.add(sunLight.target); // ターゲットもシーンに追加して追従させる

        const simplex = new SimplexNoise();

        // --- テクスチャ・マテリアル生成 ---
        function createBumpMap() {
            const size = 512;
            const canvas = document.createElement('canvas'); canvas.width = canvas.height = size;
            const ctx = canvas.getContext('2d');
            const imgData = ctx.createImageData(size, size);
            for (let i = 0; i < size; i++) {
                for (let j = 0; j < size; j++) {
                    const v = Math.pow(1.0 - Math.abs(simplex.noise2D(i/30, j/30)), 4.0);
                    const val = Math.floor((simplex.noise2D(i/10, j/10)*0.5+0.5) * 128 + v * 127);
                    const idx = (i * size + j) * 4;
                    imgData.data[idx] = imgData.data[idx+1] = imgData.data[idx+2] = val; imgData.data[idx+3] = 255;
                }
            }
            ctx.putImageData(imgData, 0, 0);
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = tex.wrapT = THREE.RepeatWrapping; tex.repeat.set(3, 3);
            return tex;
        }

        function createCityGround() {
            const size = 512, canvas = document.createElement('canvas'), ctx = canvas.getContext('2d');
            canvas.width = canvas.height = size;
            ctx.fillStyle = '#222'; ctx.fillRect(0, 0, size, size);
            ctx.fillStyle = '#3a3a3a'; ctx.fillRect(60, 60, size-120, size-120);
            ctx.fillStyle = '#ddd';
            for(let i=0; i<size; i+=30) {
                ctx.fillRect(i, 58, 15, 4); ctx.fillRect(i, size-62, 15, 4);
                ctx.fillRect(58, i, 4, 15); ctx.fillRect(size-62, i, 4, 15);
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = tex.wrapT = THREE.RepeatWrapping; 
            // 800サイズの平面に対して、1単位=10になるように設定 (10x80=800)
            tex.repeat.set(80, 80); 
            return tex;
        }

        const bumpTex = createBumpMap();
        const matGround = new THREE.MeshStandardMaterial({ color: 0x333333, map: createCityGround(), roughness: 0.9 });
        const matTank = new THREE.MeshStandardMaterial({ color: 0x4b5320, roughness: 0.8 }); 
        const matBullet = new THREE.MeshBasicMaterial({ color: 0xffcc00 }); 
        const matRubble = new THREE.MeshLambertMaterial({ color: 0x444444 }); 
        
        // 空と地面
        const sky = new THREE.Mesh(new THREE.SphereGeometry(500, 32, 32), new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }));
        scene.add(sky);
        
        const ground = new THREE.Mesh(new THREE.PlaneGeometry(800, 800), matGround);
        ground.rotation.x = -Math.PI / 2;
        ground.receiveShadow = true;
        scene.add(ground);


        // --- 都市のビル用マテリアル (グローバル) ---
        const bMats = ['#555', '#333', '#433', '#234'].map(c => {
            const cMap = document.createElement('canvas'); cMap.width=cMap.height=256;
            const ctx = cMap.getContext('2d'); ctx.fillStyle = c; ctx.fillRect(0,0,256,256);
            ctx.fillStyle='#111'; for(let i=10;i<250;i+=30)for(let j=10;j<250;j+=40) ctx.fillRect(i,j,20,25);
            
            const eMap = document.createElement('canvas'); eMap.width=eMap.height=256;
            const eCtx = eMap.getContext('2d'); eCtx.fillStyle='#000'; eCtx.fillRect(0,0,256,256);
            eCtx.fillStyle='#ffddaa'; for(let i=10;i<250;i+=30)for(let j=10;j<250;j+=40) if(Math.random()>0.5) eCtx.fillRect(i,j,20,25);
            
            const tm = new THREE.CanvasTexture(cMap); tm.wrapS=tm.wrapT=THREE.RepeatWrapping;
            const em = new THREE.CanvasTexture(eMap); em.wrapS=em.wrapT=THREE.RepeatWrapping;
            return new THREE.MeshStandardMaterial({color:0xffffff, map:tm, emissive:0xffffff, emissiveMap:em, emissiveIntensity:0, roughness:0.6});
        });


        let matBody; // キャラクターごとに生成
        let torso; // アニメーション操作用に保存
        const player = new THREE.Group();
        scene.add(player);
        
        let playerMeshes = {}; // アニメーション用パーツ格納

        // --- パーツ生成ヘルパー ---
        function createOrganicPart(r, sx, sy, sz, mat, castShadow=true) {
            const geo = new THREE.SphereGeometry(r, 16, 16); geo.scale(sx, sy, sz);
            const mesh = new THREE.Mesh(geo, mat); mesh.castShadow = castShadow; return mesh;
        }

        function createTentacle(parent, num, scaleBase, decay, rotXBase, mat, saveTo) {
            const segments = []; let currentParent = parent; let currentScale = scaleBase;
            for(let i=0; i<num; i++) {
                const segGroup = new THREE.Group();
                const mesh = new THREE.Mesh(new THREE.CylinderGeometry(currentScale*0.9, currentScale*0.8, 1.2, 12), mat);
                mesh.rotation.x = Math.PI/2; mesh.position.z = -0.6; mesh.castShadow = true;
                segGroup.add(mesh);
                let rx = typeof rotXBase === 'function' ? rotXBase(i) : rotXBase;
                segGroup.rotation.x = rx; segGroup.userData.baseAngleX = rx;
                segGroup.position.z = i === 0 ? 0 : -1.2;
                currentParent.add(segGroup); segments.push(segGroup); currentParent = segGroup;
                currentScale *= decay;
            }
            if (saveTo) saveTo.push({ segments: segments });
            return currentParent;
        }

        function createWing(side, parent, mat, scaleFactor=1.0, shape='normal') {
            const pivot = new THREE.Group(); pivot.position.set(side * 1.2 * scaleFactor, 0.2, 0.5);
            parent.add(pivot);
            const innerGeo = new THREE.BoxGeometry(7.0 * scaleFactor, 0.2, 5.0 * scaleFactor);
            innerGeo.translate(side * 3.5 * scaleFactor, 0, -1.0);
            const inner = new THREE.Mesh(innerGeo, mat); inner.castShadow = true; pivot.add(inner);

            const outerPivot = new THREE.Group(); outerPivot.position.set(side * 7.0 * scaleFactor, 0, -1.0 * scaleFactor);
            pivot.add(outerPivot);
            const outerGeo = new THREE.BoxGeometry(6.0 * scaleFactor, 0.1, 4.0 * scaleFactor);
            const posAttr = outerGeo.attributes.position;
            for(let i=0; i<posAttr.count; i++) {
                if((side===1 && posAttr.getX(i)>0) || (side===-1 && posAttr.getX(i)<0)) {
                    posAttr.setZ(i, posAttr.getZ(i) - 2.0 * scaleFactor);
                    if(shape==='sharp') posAttr.setX(i, posAttr.getX(i) + side*2.0); 
                }
            }
            outerGeo.computeVertexNormals(); outerGeo.translate(side * 3.0 * scaleFactor, 0, 0);
            const outer = new THREE.Mesh(outerGeo, mat); outer.castShadow = true; outerPivot.add(outer);
            
            playerMeshes.wings.push({ pivot: pivot, outer: outerPivot, side: side });
        }

        function attachHead(parent, mat, shape='normal') {
            const headGrp = new THREE.Group();
            let headMesh;
            const matTooth = new THREE.MeshStandardMaterial({ color: 0xeeeee0, roughness: 0.5 }); 
            const matEye = new THREE.MeshBasicMaterial({ color: 0xff5500 }); 

            if(shape==='sharp') { // ギャオス
                headMesh = new THREE.Mesh(new THREE.ConeGeometry(0.8, 2.5, 4), mat);
                headMesh.rotation.x = -Math.PI/2;
            } else if (shape === 'gamera') {
                headMesh = createOrganicPart(0.9, 1.0, 0.8, 1.2, mat);
                const muzzle = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.4, 1.0), mat);
                muzzle.position.set(0, -0.1, 0.8); headMesh.add(muzzle);
                
                for (let i=-1; i<=1; i+=0.5) {
                    if(Math.abs(i) < 0.2) continue;
                    const tooth = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.2, 4), matTooth);
                    tooth.position.set(i*0.4, -0.3, 0.8 + Math.abs(i)*0.2);
                    tooth.rotation.x = Math.PI;
                    headMesh.add(tooth);
                }

                const brow = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.2, 0.6), mat);
                brow.position.set(0, 0.5, 0.5); headMesh.add(brow);

                const jawGrp = new THREE.Group();
                jawGrp.position.set(0, -0.3, 0.2);
                const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.3, 1.1), mat);
                jaw.position.set(0, -0.1, 0.6); jawGrp.add(jaw);

                for (let side of [-1, 1]) {
                    const bigTusk = new THREE.Mesh(new THREE.ConeGeometry(0.12, 0.8, 8), matTooth);
                    bigTusk.position.set(side * 0.4, 0.2, 1.0);
                    bigTusk.rotation.x = 0.2; 
                    bigTusk.rotation.z = side * -0.2; 
                    jawGrp.add(bigTusk);
                    
                    const eyeWhite = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 8), new THREE.MeshBasicMaterial({color: 0xddddcc}));
                    eyeWhite.position.set(side * 0.45, 0.3, 0.4);
                    const eyePupil = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 8), new THREE.MeshBasicMaterial({color: 0x114411}));
                    eyePupil.position.set(side * 0.52, 0.3, 0.45);
                    headMesh.add(eyeWhite);
                    headMesh.add(eyePupil);
                }
                
                for (let i=-1; i<=1; i+=0.5) {
                    if(Math.abs(i) < 0.2) continue;
                    const tooth = new THREE.Mesh(new THREE.ConeGeometry(0.05, 0.2, 4), matTooth);
                    tooth.position.set(i*0.4, 0.05, 0.8 + Math.abs(i)*0.2);
                    jawGrp.add(tooth);
                }
                headMesh.add(jawGrp);
            } else if (shape === 'ghidorah') {
                headMesh = createOrganicPart(0.7, 0.9, 0.8, 1.2, mat);
                const muzzle = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.3, 1.0), mat);
                muzzle.position.set(0, -0.1, 0.8); headMesh.add(muzzle);
                
                // ギドラ特有の三日月型の角と赤い目
                for (let side of [-1, 1]) {
                    const horn = new THREE.Mesh(new THREE.ConeGeometry(0.1, 0.7, 4), mat);
                    horn.position.set(side * 0.35, 0.3, -0.2);
                    horn.rotation.x = -0.6;
                    horn.rotation.z = side * -0.3;
                    headMesh.add(horn);

                    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.06, 8, 8), new THREE.MeshBasicMaterial({color: 0xff0000}));
                    eye.position.set(side * 0.3, 0.2, 0.4);
                    headMesh.add(eye);
                }
                
                const jawGrp = new THREE.Group();
                jawGrp.position.set(0, -0.2, 0.2); 
                jawGrp.rotation.x = 0.3; 
                const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.2, 0.9), mat);
                jaw.position.set(0, -0.1, 0.5); jawGrp.add(jaw);
                headMesh.add(jawGrp);
            } else {
                headMesh = createOrganicPart(0.8, 0.9, 0.8, 1.2, mat);
                const muzzle = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.4, 1.2), mat);
                muzzle.position.set(0, -0.1, 0.8); headMesh.add(muzzle);
                
                const jawGrp = new THREE.Group();
                jawGrp.position.set(0, -0.2, 0.2); 
                jawGrp.rotation.x = 0.3; 
                const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.35, 1.1), mat);
                jaw.position.set(0, -0.15, 0.6); jawGrp.add(jaw);
                headMesh.add(jawGrp);

                for (let side of [-1, 1]) {
                    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.06, 8, 8), matEye);
                    eye.position.set(side * 0.4, 0.25, 0.4);
                    headMesh.add(eye);
                }
            }
            headGrp.add(headMesh);
            
            const mouthPos = new THREE.Object3D(); mouthPos.position.set(0, 0, 1.5);
            headGrp.add(mouthPos);
            parent.add(headGrp);
            playerMeshes.heads.push({ group: headGrp, mouth: mouthPos });
        }

        function createLegWalk(side, parent, mat, shape='normal') {
            const legGrp = new THREE.Group(); legGrp.position.set(side*1.2, -0.5, 0); parent.add(legGrp);
            const isGamera = shape === 'gamera';
            const thighRad = isGamera ? 1.2 : 1.0;
            const thigh = createOrganicPart(thighRad, 0.8, 1.4, 1.0, mat); 
            thigh.rotation.x = -0.2; legGrp.add(thigh);
            const shinGrp = new THREE.Group(); shinGrp.position.set(0, -1.2, 0.2); thigh.add(shinGrp);
            const shinRad = isGamera ? 0.8 : 0.6;
            const shin = new THREE.Mesh(new THREE.CylinderGeometry(shinRad, shinRad*0.8, 1.4, 8), mat); 
            shin.position.y = -0.7; shin.rotation.x = 0.2; shinGrp.add(shin);
            
            const foot = new THREE.Mesh(new THREE.BoxGeometry(shinRad*2, 0.4, 1.5), mat);
            foot.position.set(0, -1.4, 0.4);
            shinGrp.add(foot);

            const matClaw = new THREE.MeshStandardMaterial({ color: isGamera ? 0xddddaa : 0xcca300, roughness: 0.6, metalness: 0.3 }); 
            for(let i=-1; i<=1; i++) {
                const claw = new THREE.Mesh(new THREE.ConeGeometry(0.12, 0.6, 8), matClaw);
                claw.position.set(i*shinRad*0.7, -1.4, 1.15);
                claw.rotation.x = -Math.PI/2 + 0.1;
                shinGrp.add(claw);
            }
            playerMeshes.legs.push({ thigh: thigh, shin: shinGrp });
        }
        function createArmWalk(side, parent, mat, shape='normal') {
            const armGrp = new THREE.Group(); armGrp.position.set(side*1.5, 0.5, 0.8); parent.add(armGrp);
            const isGamera = shape === 'gamera';
            const rad = isGamera ? 0.4 : 0.3;
            
            // バニシング・フィスト (右腕のみ発光マテリアル)
            const isRightArm = side === 1;
            const armMat = (isGamera && isRightArm) ? new THREE.MeshStandardMaterial({
                color: 0xffaa00, emissive: 0xff5500, emissiveIntensity: 2.0, transparent: true, opacity: 0.9, roughness: 0.2
            }) : mat;

            const upper = new THREE.Mesh(new THREE.CylinderGeometry(rad, rad*0.8, 1.2, 8), armMat); 
            upper.rotation.set(0.5, 0, side*0.3); armGrp.add(upper);
            
            const hand = new THREE.Mesh(new THREE.BoxGeometry(rad*2, rad*2, rad*2), armMat);
            hand.position.set(0, -0.6, 0);
            upper.add(hand);

            const matClaw = (isGamera && isRightArm) ? armMat : new THREE.MeshStandardMaterial({ color: isGamera ? 0xddddaa : 0xcca300, roughness: 0.6, metalness: 0.3 }); 
            for(let i=-1; i<=1; i+=0.8) {
                const claw = new THREE.Mesh(new THREE.ConeGeometry(0.08, 0.4, 4), matClaw);
                claw.position.set(i*0.2, -0.8, 0.1);
                claw.rotation.x = -Math.PI/2;
                claw.rotation.y = i * 0.2;
                upper.add(claw);
            }
            
            // ガメラのポーズ調整 (右腕を前に出す)
            if (isGamera) {
                if (isRightArm) {
                    armGrp.rotation.x = -0.5; // 前に突き出す
                    upper.rotation.z = 0.5; // 少し内側に
                } else {
                    armGrp.rotation.x = 0.5; // 左腕は下ろす
                }
            }

            playerMeshes.arms.push(upper);
        }

        // === モンスター動的構築 ===
        function buildMonster(id) {
            while(player.children.length > 0) player.remove(player.children[0]);
            playerMeshes = { heads: [], tails: [], wings: [], legs: [], arms: [], necks: [] };
            const info = MONSTERS[id];
            player.position.set(0, info.height, 0);

            matBody = new THREE.MeshStandardMaterial({
                color: info.color, emissive: info.emissive, emissiveIntensity: 0,
                roughness: 0.9, metalness: (id==='ghidorah'?0.8:0.1), bumpMap: bumpTex, bumpScale: 0.1
            });

            torso = createOrganicPart(1.6, 1.0, 1.2, 1.0, matBody);
            player.add(torso);

            if (id === 'godzilla') {
                torso.position.y = 2.4;
                torso.rotation.x = 0.0; // 直立に近い姿勢

                const chest = createOrganicPart(1.6, 1.2, 1.1, 1.0, matBody); // 胸を厚く
                chest.position.set(0, 1.4, 0.2); 
                chest.rotation.x = -0.1;
                torso.add(chest);

                const neck = createOrganicPart(1.0, 1.0, 1.2, 1.0, matBody); // 首を太く
                neck.position.set(0, 1.2, 0.1); 
                neck.rotation.x = 0.1;
                chest.add(neck);

                // --- レジェゴジ風のマッシブな頭部 ---
                const headGrp = new THREE.Group();
                const headMesh = createOrganicPart(0.8, 0.9, 0.7, 1.1, matBody); // やや四角く
                headMesh.position.set(0, 0, 0);
                
                const muzzle = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.45, 1.2), matBody);
                muzzle.position.set(0, -0.1, 0.7); 
                headMesh.add(muzzle);
                
                // 口
                const jawGrp = new THREE.Group();
                jawGrp.position.set(0, -0.2, 0.2); 
                jawGrp.rotation.x = 0.2; 
                const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.3, 1.1), matBody);
                jaw.position.set(0, -0.1, 0.6); 
                jawGrp.add(jaw);

                // 牙
                const matTooth = new THREE.MeshStandardMaterial({ color: 0xddccaa, roughness: 0.5 });
                for(let i=-1; i<=1; i+=0.4) {
                    if(Math.abs(i) < 0.2) continue;
                    const topTooth = new THREE.Mesh(new THREE.ConeGeometry(0.04, 0.15, 4), matTooth);
                    topTooth.position.set(i*0.3, -0.2, 0.7 + Math.abs(i)*0.1);
                    topTooth.rotation.x = Math.PI;
                    headMesh.add(topTooth);
                    
                    const btmTooth = new THREE.Mesh(new THREE.ConeGeometry(0.04, 0.15, 4), matTooth);
                    btmTooth.position.set(i*0.3, 0.05, 0.6 + Math.abs(i)*0.1);
                    jawGrp.add(btmTooth);
                }
                headMesh.add(jawGrp);

                // 小さく鋭い目
                const matEye = new THREE.MeshBasicMaterial({ color: 0xffcc00 }); // 黄色っぽい目
                for (let side of [-1, 1]) {
                    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.04, 8, 8), matEye);
                    eye.position.set(side * 0.38, 0.2, 0.4);
                    const brow = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.1, 0.2), matBody);
                    brow.position.set(side * 0.38, 0.25, 0.4);
                    brow.rotation.z = side * -0.2;
                    headMesh.add(eye);
                    headMesh.add(brow);
                }

                headGrp.add(headMesh);
                const mouthPos = new THREE.Object3D(); mouthPos.position.set(0, 0, 1.5);
                headGrp.add(mouthPos);
                neck.add(headGrp);
                playerMeshes.heads.push({ group: headGrp, mouth: mouthPos });

                createTentacle(torso, 24, 1.4, 0.94, (i)=>i<5? -0.15 : 0.05, matBody, playerMeshes.tails); // 太く長い尻尾
                
                // --- どっしりとした太い脚 ---
                [-1, 1].forEach(side => {
                    const legGrp = new THREE.Group(); legGrp.position.set(side*1.3, -0.5, 0); torso.add(legGrp);
                    const thigh = createOrganicPart(1.3, 0.9, 1.6, 1.2, matBody); thigh.rotation.x = -0.2; legGrp.add(thigh);
                    const shinGrp = new THREE.Group(); shinGrp.position.set(0, -1.5, 0.2); thigh.add(shinGrp);
                    const shin = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.7, 1.6, 12), matBody); shin.position.y = -0.7; shin.rotation.x = 0.2; shinGrp.add(shin);
                    
                    const foot = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.5, 2.0), matBody);
                    foot.position.set(0, -1.5, 0.5);
                    shinGrp.add(foot);

                    const matClaw = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.8 });  // 黒っぽい爪
                    for(let i=-1; i<=1; i+=0.8) {
                        const claw = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.6, 8), matClaw);
                        claw.position.set(i*0.6, -1.5, 1.4);
                        claw.rotation.x = -Math.PI/2 + 0.1;
                        shinGrp.add(claw);
                    }
                    playerMeshes.legs.push({ thigh: thigh, shin: shinGrp });
                });

                // --- 腕 ---
                [-1, 1].forEach(side => {
                    const armGrp = new THREE.Group(); armGrp.position.set(side*1.6, 0.5, 0.6); chest.add(armGrp);
                    const upper = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.3, 1.5, 8), matBody); 
                    upper.rotation.set(0.5, 0, side*0.4); armGrp.add(upper);
                    
                    const handGrp = new THREE.Group();
                    handGrp.position.set(0, -0.8, 0.2);
                    handGrp.rotation.x = -0.6;
                    upper.add(handGrp);

                    const hand = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.5, 0.5), matBody);
                    handGrp.add(hand);

                    const matClaw = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.8 }); 
                    for(let i=-1; i<=1; i+=0.8) {
                        const claw = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.5, 4), matClaw);
                        claw.position.set(i*0.2, -0.3, 0.2);
                        claw.rotation.x = -Math.PI/2;
                        claw.rotation.y = i * 0.2;
                        handGrp.add(claw);
                    }
                    playerMeshes.arms.push(upper);
                });

                // --- 青く強く発光する背びれ ---
                const matSpike = new THREE.MeshStandardMaterial({
                    color: 0x00ffff, // シアン
                    emissive: 0x00aaff, // 強く発光
                    emissiveIntensity: 1.5, // 最初から光らせる
                    roughness: 0.4,
                    transparent: true,
                    opacity: 0.9
                });
                
                // ギザギザとした山型
                const spikeShape = new THREE.Shape();
                spikeShape.moveTo(0, 0);
                spikeShape.lineTo(0.4, 1.0);
                spikeShape.lineTo(0.2, 1.5);
                spikeShape.lineTo(0.6, 2.0);
                spikeShape.lineTo(0, 3.0); // 頂点
                spikeShape.lineTo(-0.6, 2.0);
                spikeShape.lineTo(-0.2, 1.5);
                spikeShape.lineTo(-0.4, 1.0);
                spikeShape.lineTo(0, 0);

                const spikeExtrude = { depth: 0.2, bevelEnabled: true, bevelSegments: 2, steps: 1, bevelSize: 0.05, bevelThickness: 0.05 };
                const spikeGeo = new THREE.ExtrudeGeometry(spikeShape, spikeExtrude);

                // 中央列
                for(let i=0; i<18; i++) {
                    const spike = new THREE.Mesh(spikeGeo, matSpike);
                    let scale = 1.0;
                    if(i < 3) scale = 0.3 + i*0.2; 
                    else if(i < 8) scale = 1.0 + (i-3)*0.15; // 大きめ
                    else scale = 1.6 - (i-8)*0.15; 
                    
                    spike.scale.set(scale, scale, scale);

                    if (i < 3) {
                        spike.position.set(0, 0.6 + i*0.3, -0.5);
                        spike.rotation.x = -0.2;
                        neck.add(spike);
                    } else if (i < 7) {
                        spike.position.set(0, 1.2 - (i-3)*0.4, -0.6);
                        spike.rotation.x = -0.05;
                        chest.add(spike);
                    } else if (i < 11) {
                        spike.position.set(0, 0.7 - (i-7)*0.4, -0.9);
                        spike.rotation.x = 0;
                        torso.add(spike);
                    } else {
                        const tailIdx = i - 11;
                        if(tailIdx < playerMeshes.tails[0].segments.length) {
                            spike.position.set(0, 0.8*scale, -0.4);
                            spike.rotation.x = -0.15;
                            playerMeshes.tails[0].segments[tailIdx].add(spike);
                        }
                    }
                }
                // 左右のサブ列
                for (let side of [-1, 1]) {
                    for(let i=2; i<12; i++) {
                        const spike = new THREE.Mesh(spikeGeo, matSpike);
                        let scale = 0.4;
                        if(i > 3 && i < 8) scale = 0.7;
                        spike.scale.set(scale, scale, scale);
                        
                        let target = torso;
                        let px = side * 0.7;
                        let py = 1.0 - (i-2)*0.3;
                        let pz = -0.7;
                        let rx = -0.05;

                        if (i < 4) { target = neck; px = side * 0.4; py = 0.6; pz = -0.5; rx = -0.2; }
                        else if (i < 7) { target = chest; px = side * 0.6; py = 1.1 - (i-4)*0.4; pz = -0.7; rx = -0.05; }
                        else if (i >= 10) { 
                            const tailIdx = i - 10;
                            if(tailIdx < playerMeshes.tails[0].segments.length) {
                                target = playerMeshes.tails[0].segments[tailIdx];
                                px = side * 0.4; py = 0.6*scale; pz = -0.3; rx = -0.1;
                            }
                        }

                        spike.position.set(px, py, pz);
                        spike.rotation.x = rx;
                        spike.rotation.y = side * 0.2;
                        spike.rotation.z = side * -0.2;
                        target.add(spike);
                    }
                }
            } 
            else if (id === 'rodan') {
                torso.scale.set(1.0, 0.7, 1.6); torso.rotation.x = 0.2;
                const neck = createOrganicPart(0.6, 1.0, 1.0, 1.5, matBody); neck.position.set(0, 0.2, 1.8); torso.add(neck);
                attachHead(neck, matBody);
                createWing(1, torso, matBody); createWing(-1, torso, matBody);
                createTentacle(torso, 6, 0.8, 0.8, 0, matBody, playerMeshes.tails);
            }
            else if (id === 'anguirus') {
                torso.position.y = 1.5;
                torso.rotation.x = -0.3; // 四足歩行気味に前傾
                torso.scale.set(1.2, 1.0, 1.5);

                // トゲトゲの甲羅
                const matShell = new THREE.MeshStandardMaterial({color:0x5c4033, roughness:0.9, bumpMap:bumpTex, bumpScale:0.3});
                const shell = createOrganicPart(1.6, 1.0, 0.6, 1.2, matShell);
                shell.position.set(0, 0.8, -0.2);
                torso.add(shell);

                // 甲羅のトゲ (アンギラスの特徴)
                for(let i=0; i<35; i++) {
                    const spike = new THREE.Mesh(new THREE.ConeGeometry(0.25, 0.8, 4), matShell);
                    const x = (Math.random()-0.5) * 2.5;
                    const z = (Math.random()-0.5) * 3.5;
                    const y = Math.cos(x*0.8)*0.5 + Math.cos(z*0.5)*0.5;
                    if(x*x + (z*0.7)*(z*0.7) < 3.0) {
                        spike.position.set(x, y + 0.4, z - 0.2);
                        spike.rotation.x = -0.2 + z*0.2;
                        spike.rotation.z = -x*0.3;
                        shell.add(spike);
                    }
                }

                const neck = createOrganicPart(0.7, 0.9, 1.0, 1.2, matBody);
                neck.position.set(0, 0.6, 1.4);
                neck.rotation.x = 0.4; // 頭を上げる
                torso.add(neck);

                // 頭部
                const headGrp = new THREE.Group();
                const headMesh = createOrganicPart(0.7, 0.9, 0.8, 1.2, matBody);
                const muzzle = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.4, 1.0), matBody);
                muzzle.position.set(0, -0.1, 0.8); headMesh.add(muzzle);
                
                // 後頭部の角
                for(let i of [-1, 0, 1]) {
                    const horn = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.8, 4), new THREE.MeshStandardMaterial({color: 0xddddcc}));
                    horn.position.set(i*0.3, 0.4, -0.4);
                    horn.rotation.x = -1.0;
                    horn.rotation.z = i*0.2;
                    headMesh.add(horn);
                }

                // 下顎
                const jawGrp = new THREE.Group();
                jawGrp.position.set(0, -0.2, 0.2); 
                jawGrp.rotation.x = 0.2; 
                const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.2, 0.9), matBody);
                jaw.position.set(0, -0.1, 0.5); jawGrp.add(jaw);
                headMesh.add(jawGrp);

                headGrp.add(headMesh);
                const mouthPos = new THREE.Object3D(); mouthPos.position.set(0, 0, 1.5);
                headGrp.add(mouthPos);
                neck.add(headGrp);
                playerMeshes.heads.push({ group: headGrp, mouth: mouthPos });

                // 尻尾
                const tailGrp = createTentacle(torso, 10, 1.0, 0.9, (i)=> -0.1, matBody, playerMeshes.tails);
                // 尻尾にもトゲ
                playerMeshes.tails[0].segments.forEach((seg, i) => {
                    if(i%2===0) {
                        for(let j of [-1, 1]) {
                            const spike = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.6, 4), matShell);
                            spike.position.set(j*(0.8 - i*0.05), 0, -0.5);
                            spike.rotation.z = j * -Math.PI/2;
                            seg.add(spike);
                        }
                    }
                });

                createLegWalk(1, torso, matBody); createLegWalk(-1, torso, matBody);
                
                // 前足 (四足歩行用)
                for(let side of [-1, 1]) {
                    const armGrp = new THREE.Group(); armGrp.position.set(side*1.2, -0.3, 1.0); torso.add(armGrp);
                    const upper = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.4, 1.2, 8), matBody); 
                    upper.rotation.set(0.5, 0, side*0.3); armGrp.add(upper);
                    
                    const hand = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.4, 1.0), matBody);
                    hand.position.set(0, -0.6, 0.2);
                    upper.add(hand);
                    
                    const matClaw = new THREE.MeshStandardMaterial({ color: 0xddddcc, roughness: 0.8 }); 
                    for(let i=-1; i<=1; i++) {
                        const claw = new THREE.Mesh(new THREE.ConeGeometry(0.1, 0.5, 4), matClaw);
                        claw.position.set(i*0.3, -0.2, 0.5);
                        claw.rotation.x = -Math.PI/2 + 0.2;
                        hand.add(claw);
                    }
                    playerMeshes.arms.push(upper);
                }
            }
            else if (id === 'gamera') {
                torso.position.y = 2.0; torso.scale.set(1.3, 1.2, 1.0);
                // リバース風の刺々しい黒緑の甲羅
                const matShell = new THREE.MeshStandardMaterial({color:0x151a15, roughness:0.9, bumpMap:bumpTex, bumpScale:0.4});
                const shell = createOrganicPart(2.2, 1.0, 0.4, 1.1, matShell); shell.position.set(0,0.8,-0.2); shell.rotation.x = -0.2; torso.add(shell);
                
                // 鋭いエッジスパイク
                for(let i=0; i<16; i++) {
                    const angle = (i/16) * Math.PI * 2;
                    if(Math.cos(angle) > 0.6) continue; // 頭の方向は避ける
                    
                    const edgeSpike = new THREE.Mesh(new THREE.ConeGeometry(0.35, 1.0, 4), matShell);
                    edgeSpike.position.set(Math.sin(angle)*2.2, -0.2, Math.cos(angle)*2.4 - 0.2);
                    edgeSpike.rotation.x = Math.PI/2 + 0.4; // より前方に鋭く
                    edgeSpike.rotation.y = angle;
                    shell.add(edgeSpike);
                }
                
                // 背中の隆起
                for(let x=-1; x<=1; x+=1) {
                    for(let z=-1; z<=1; z+=1) {
                        if(x===0 && z===0) continue;
                        const backSpike = new THREE.Mesh(new THREE.ConeGeometry(0.4, 0.8, 6), matShell);
                        backSpike.position.set(x*1.0, 0.4, z*1.0);
                        backSpike.rotation.x = z * -0.2;
                        backSpike.rotation.z = x * -0.2;
                        shell.add(backSpike);
                    }
                }

                // ブロック状の腹部
                const matBelly = new THREE.MeshStandardMaterial({color:0x22221c, roughness:0.8, bumpMap:bumpTex, bumpScale:0.2});
                const bellyPlate = createOrganicPart(1.6, 1.0, 1.0, 0.5, matBelly);
                bellyPlate.position.set(0, -0.3, 0.8);
                torso.add(bellyPlate);

                const neck = createOrganicPart(0.8, 1.1, 1.0, 1.2, matBody); neck.position.set(0,1.0,1.2); torso.add(neck);
                attachHead(neck, matBody, 'gamera');
                
                // ゴツゴツした短い尻尾
                createTentacle(torso, 4, 1.2, 0.6, -0.2, matBody, playerMeshes.tails);
                
                createLegWalk(1, torso, matBody, 'gamera'); createLegWalk(-1, torso, matBody, 'gamera');
                createArmWalk(1, torso, matBody, 'gamera'); createArmWalk(-1, torso, matBody, 'gamera');
            }
            else if (id === 'gyaos') {
                torso.scale.set(0.9, 1.2, 0.8);
                torso.rotation.x = -0.6; // 体を立てる姿勢
                
                const neck = createOrganicPart(0.5, 1.2, 1.0, 0.8, matBody); 
                neck.position.set(0, 1.0, 0.6); 
                neck.rotation.x = 0.6; // 胴体の傾きを相殺して前を向く
                torso.add(neck);

                const headGrp = new THREE.Group();
                headGrp.position.set(0, 0.8, 0.2);
                neck.add(headGrp);

                // 平たい矢尻型の頭部
                const snoutGeo = new THREE.ConeGeometry(0.9, 2.2, 4);
                snoutGeo.rotateY(Math.PI / 4); // ひし形にする
                snoutGeo.rotateX(-Math.PI / 2); // 前に倒す
                snoutGeo.scale(1.0, 0.4, 1.0); // 上下を平たく潰す
                const snout = new THREE.Mesh(snoutGeo, matBody);
                snout.position.set(0, 0, 0.6);
                headGrp.add(snout);

                // 後頭部の突起(耳のような角)
                for(let side of [-1, 1]) {
                    const crest = new THREE.Mesh(new THREE.ConeGeometry(0.2, 1.2, 4), matBody);
                    crest.position.set(side * 0.4, 0.05, -0.4);
                    crest.rotation.x = Math.PI / 2; 
                    crest.rotation.z = side * -0.4; 
                    headGrp.add(crest);
                    
                    // 赤く光る目
                    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 8), new THREE.MeshBasicMaterial({color: 0xff0000}));
                    eye.position.set(side * 0.35, 0.1, 0.4);
                    headGrp.add(eye);
                }

                // 下顎
                const jawGeo = new THREE.ConeGeometry(0.7, 1.8, 4);
                jawGeo.rotateY(Math.PI / 4);
                jawGeo.rotateX(-Math.PI / 2);
                jawGeo.scale(0.9, 0.3, 0.9);
                const jawGrp = new THREE.Group();
                jawGrp.position.set(0, -0.15, 0.5);
                jawGrp.rotation.x = 0.2; 
                const jaw = new THREE.Mesh(jawGeo, matBody);
                jaw.position.set(0, -0.05, 0.2);
                jawGrp.add(jaw);
                headGrp.add(jawGrp);

                const mouthPos = new THREE.Object3D(); mouthPos.position.set(0, 0, 1.5);
                headGrp.add(mouthPos);
                playerMeshes.heads.push({ group: headGrp, mouth: mouthPos });

                createWing(1, torso, matBody, 1.6, 'sharp'); 
                createWing(-1, torso, matBody, 1.6, 'sharp');
                createTentacle(torso, 6, 0.5, 0.8, -0.4, matBody, playerMeshes.tails);

                // 長い脚を追加 (飛行中は垂れ下がる)
                for(let side of [-1, 1]) {
                    const legGrp = new THREE.Group(); legGrp.position.set(side*0.7, -0.6, -0.2); torso.add(legGrp);
                    const thigh = createOrganicPart(0.5, 0.8, 1.4, 0.8, matBody); thigh.rotation.x = 0.6; legGrp.add(thigh);
                    const shinGrp = new THREE.Group(); shinGrp.position.set(0, -1.2, 0.2); thigh.add(shinGrp);
                    const shin = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.15, 1.4, 8), matBody); shin.position.y = -0.7; shin.rotation.x = -0.4; shinGrp.add(shin);
                    
                    // 爪
                    for(let i=-1; i<=1; i+=1) {
                        const claw = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.6, 4), new THREE.MeshStandardMaterial({color: 0x111111}));
                        claw.position.set(i*0.15, -1.4, 0.2);
                        claw.rotation.x = -0.2;
                        shinGrp.add(claw);
                    }
                    playerMeshes.legs.push({ thigh: thigh, shin: shinGrp });
                }
            }
        }

        // --- 街生成 ---
        const buildings = [];
        const activeBuildings = []; // マテリアル制御用
        const generatedChunks = new Set();
        const CHUNK_SIZE = 40;
        const RENDER_DIST = 160;

        function updateEndlessWorld(isInit = false) {
            const px = player.position.x;
            const pz = player.position.z;
            const cx = Math.floor(px / CHUNK_SIZE);
            const cz = Math.floor(pz / CHUNK_SIZE);

            // 地面をプレイヤーに追従させる
            ground.position.set(px, 0, pz);

            // チャンクベースのビル生成
            for (let x = cx - 3; x <= cx + 3; x++) {
                for (let z = cz - 3; z <= cz + 3; z++) {
                    const key = `${x},${z}`;
                    if (!generatedChunks.has(key)) {
                        generatedChunks.add(key);
                        // 初期生成時のプレイヤー周囲は空ける
                        if (isInit && Math.hypot(x*CHUNK_SIZE, z*CHUNK_SIZE) < 30) continue;
                        
                        // 区画内にビルを生成
                        const numBuildings = Math.floor(Math.random() * 2) + 1;
                        for (let b = 0; b < numBuildings; b++) {
                            const building = new THREE.Group();
                            const height = Math.random() * 20 + 8;
                            const width = Math.random() * 5 + 4;
                            const depth = Math.random() * 5 + 4;

                            const baseMat = bMats[Math.floor(Math.random() * bMats.length)];
                            const clonedMat = baseMat.clone();
                            clonedMat.map = baseMat.map.clone();
                            clonedMat.map.repeat.set(width/4, height/4);
                            clonedMat.emissiveMap = baseMat.emissiveMap.clone();
                            clonedMat.emissiveMap.repeat.set(width/4, height/4);
                            activeBuildings.push(clonedMat);

                            const buildingGeo = new THREE.BoxGeometry(width, height, depth);
                            const buildingMesh = new THREE.Mesh(buildingGeo, clonedMat);
                            buildingMesh.position.y = height / 2;
                            buildingMesh.castShadow = true;
                            buildingMesh.receiveShadow = true; 
                            building.add(buildingMesh);

                            if (Math.random() > 0.4) {
                                const roofObjGeo = Math.random() > 0.5 ? new THREE.CylinderGeometry(0.8, 0.8, 2) : new THREE.BoxGeometry(2, 2, 2);
                                const roofObj = new THREE.Mesh(roofObjGeo, new THREE.MeshStandardMaterial({color: 0x666666}));
                                roofObj.position.set((Math.random()-0.5)*(width-3), height + 1.0, (Math.random()-0.5)*(depth-3));
                                roofObj.castShadow = true;
                                building.add(roofObj);
                            }
                            if (height > 18 && Math.random() > 0.6) {
                                const antennaGeo = new THREE.CylinderGeometry(0.05, 0.05, 8);
                                const antenna = new THREE.Mesh(antennaGeo, new THREE.MeshStandardMaterial({color: 0x333333, metalness: 0.8}));
                                antenna.position.set((Math.random()-0.5)*(width-2), height + 4, (Math.random()-0.5)*(depth-2));
                                building.add(antenna);
                            }

                            const bx = x * CHUNK_SIZE + (Math.random() - 0.5) * (CHUNK_SIZE - 10);
                            const bz = z * CHUNK_SIZE + (Math.random() - 0.5) * (CHUNK_SIZE - 10);

                            building.position.set(bx, 0, bz);
                            building.rotation.y = Math.floor(Math.random() * 4) * (Math.PI / 2);
                            
                            scene.add(building);
                            buildings.push({ x: building.position.x, z: building.position.z, height: height, radius: Math.max(width, depth) * 0.6, group: building, mat: clonedMat, chunkKey: key }); 
                        }
                    }
                }
            }

            // 古いビルの削除
            for (let i = buildings.length - 1; i >= 0; i--) {
                const b = buildings[i];
                if (Math.hypot(px - b.x, pz - b.z) > RENDER_DIST) {
                    scene.remove(b.group);
                    const matIndex = activeBuildings.indexOf(b.mat);
                    if(matIndex > -1) activeBuildings.splice(matIndex, 1);
                    buildings.splice(i, 1);
                    generatedChunks.delete(b.chunkKey);
                }
            }

            // 敵とアイテムの無限処理
            for (let i = enemies.tanks.length - 1; i >= 0; i--) {
                const t = enemies.tanks[i];
                if (Math.hypot(px - t.mesh.position.x, pz - t.mesh.position.z) > RENDER_DIST + 50) {
                    scene.remove(t.mesh); enemies.tanks.splice(i, 1);
                }
            }
            while (enemies.tanks.length < 8) {
                enemies.tanks.push(createTank(px, pz));
            }

            for (let i = items.length - 1; i >= 0; i--) {
                const item = items[i];
                if (Math.hypot(px - item.mesh.position.x, pz - item.mesh.position.z) > RENDER_DIST + 50) {
                    scene.remove(item.mesh); items.splice(i, 1);
                }
            }
            if (items.length < 40) {
                if(Math.random() < 0.5) spawnItem('food', px, pz);
                else spawnItem('water', px, pz);
            }
        }

        // --- 敵部隊・アイテム ---
        const enemies = { tanks: [], bullets: [], beams: [] };
        function createTank(px=0, pz=0) {
            const grp = new THREE.Group();
            const body = new THREE.Mesh(new THREE.BoxGeometry(2,1,3), matTank); body.position.y=0.5; body.castShadow=true; grp.add(body);
            const turret = new THREE.Mesh(new THREE.BoxGeometry(1.2,0.6,1.5), matTank); turret.position.y=1.3; turret.castShadow=true; grp.add(turret);
            const barrel = new THREE.Mesh(new THREE.CylinderGeometry(0.15,0.15,2,8), matTank); barrel.rotation.x=Math.PI/2; barrel.position.set(0,1.3,1.5); barrel.castShadow=true; grp.add(barrel);
            let x, z; 
            do { 
                x = px + (Math.random()-0.5)*RENDER_DIST*1.5; 
                z = pz + (Math.random()-0.5)*RENDER_DIST*1.5; 
            } while(Math.hypot(px-x, pz-z) < 40); 
            grp.position.set(x, 0, z); scene.add(grp);
            return { mesh: grp, x: x, z: z, radius: 1.5, cooldown: Math.random()*2 };
        }
        function fireBullet(start, target) {
            const bullet = new THREE.Mesh(new THREE.SphereGeometry(0.2,8,8), matBullet);
            bullet.position.copy(start); scene.add(bullet);
            enemies.bullets.push({ mesh: bullet, dir: new THREE.Vector3().subVectors(target, start).normalize(), speed: 50, life: 3, damage: 2 });
        }

        const items = [];
        function spawnItem(type, px=0, pz=0) {
            let mesh;
            if (type === 'food') mesh = new THREE.Mesh(new THREE.SphereGeometry(0.4,8,8), new THREE.MeshLambertMaterial({color:0xff3333}));
            else if (type === 'water') mesh = new THREE.Mesh(new THREE.BoxGeometry(0.6,0.2,0.6), new THREE.MeshLambertMaterial({color:0x3388ff}));
            else mesh = new THREE.Mesh(new THREE.DodecahedronGeometry(0.3), matRubble);
            mesh.position.set(px + (Math.random()-0.5)*RENDER_DIST*1.5, type==='food'?0.4:0.15, pz + (Math.random()-0.5)*RENDER_DIST*1.5);
            scene.add(mesh); items.push({ type: type, mesh: mesh });
        }

        const particles = [];
        function spawnParticles(pos, count) {
            for(let i=0;i<count;i++){
                const p = new THREE.Mesh(new THREE.DodecahedronGeometry(0.3,0), new THREE.MeshStandardMaterial({color:0x666, roughness:1}));
                p.position.copy(pos).add(new THREE.Vector3((Math.random()-0.5)*2, Math.random()*2, (Math.random()-0.5)*2));
                scene.add(p); particles.push({mesh: p, vel: new THREE.Vector3((Math.random()-0.5)*0.5, Math.random()*0.5+0.2, (Math.random()-0.5)*0.5), life: 2});
            }
        }

        // === ゲーム制御 ===
        let lastEnvUpdate = 0;

        function startGame(id) {
            state.charId = id; state.isPlaying = true;
            state.day = 1; state.gameTime = 12; state.isGameOver = false;
            state.stats = { hp: 100, food: 100, water: 100, energy: 100 };
            state.inventory = { food: 2, water: 2, wood: 0 };
            
            const info = MONSTERS[id];
            document.getElementById('monster-name').innerText = info.name + " City Destruction";
            document.getElementById('monster-name').className = "text-xl font-bold " + info.uiClass;
            document.getElementById('action-beam-name').innerText = info.beamName + " (撃ち放題)";
            document.getElementById('action-beam-name').className = info.uiClass + " font-bold";

            document.getElementById('start-screen').classList.add('opacity-0', 'pointer-events-none');
            setTimeout(() => document.getElementById('start-screen').style.display = 'none', 500);
            document.getElementById('ui-layer').style.display = 'flex';
            document.getElementById('game-over-modal').classList.add('hidden');
            document.getElementById('log-container').innerHTML = '';

            buildMonster(id);
            
            // 環境の初期化
            buildings.forEach(b => scene.remove(b.group)); buildings.length = 0; activeBuildings.length = 0;
            generatedChunks.clear();
            enemies.tanks.forEach(t => scene.remove(t.mesh)); enemies.tanks.length = 0;
            items.forEach(i => scene.remove(i.mesh)); items.length = 0;
            
            updateEndlessWorld(true);
            lastEnvUpdate = 0;

            // カメラ位置を初期化
            const isFly = MONSTERS[id].type === 'fly';
            camera.position.set(0, isFly ? 25 : 15, isFly ? 35 : 35);
            camera.lookAt(0, 0, 0);

            log(`${info.name}が街に現れた。全てを破壊しろ!`, true);
            updateUI();
        }

        function returnToHome() {
            state.isPlaying = false;
            state.isGameOver = false;
            
            document.getElementById('ui-layer').style.display = 'none';
            document.getElementById('game-over-modal').classList.add('hidden');
            
            const startScreen = document.getElementById('start-screen');
            startScreen.style.display = 'flex';
            setTimeout(() => { startScreen.classList.remove('opacity-0', 'pointer-events-none'); }, 10);
            
            while(player.children.length > 0) player.remove(player.children[0]);
        }

        function log(msg, isUrgent = false) {
            const container = document.getElementById('log-container'); const el = document.createElement('div');
            const info = state.isPlaying ? MONSTERS[state.charId] : null;
            el.innerHTML = `<span class="${info ? info.uiClass : 'text-white'}">[Day ${state.day} ${Math.floor(state.gameTime)}:00]</span> <span class="${isUrgent?'text-red-400 font-bold':''}">${msg}</span>`;
            container.prepend(el); if(container.children.length>30) container.removeChild(container.lastChild);
        }
        function modifyStat(stat, amount) {
            // 無敵モード:減少を無効化
            if (amount < 0) return;
            state.stats[stat] = Math.max(0, Math.min(100, state.stats[stat] + amount));
            document.getElementById(`${stat}-text`).innerText = Math.floor(state.stats[stat]);
        }
        function updateUI() {
            document.getElementById('day-counter').innerText = state.day;
            document.getElementById('time-display').innerText = `${Math.floor(state.gameTime).toString().padStart(2,'0')}:${Math.floor((state.gameTime%1)*60).toString().padStart(2,'0')}`;
            document.getElementById('inv-food').innerText = state.inventory.food;
            document.getElementById('inv-water').innerText = state.inventory.water;
        }

        function gameAction(action) {
            if (!state.isPlaying || state.isGameOver) return;
            if (action === 'eat') {
                if (state.inventory.food > 0 && (state.stats.food < 100 || state.stats.hp < 100)) {
                    state.inventory.food--; modifyStat('food', 40); modifyStat('hp', 20); log("🍖 捕食して回復した!(満腹度+40, HP+20)");
                } else log("⚠️ 食料がない、または満タンだ!", true);
            } else if (action === 'drink') {
                if (state.inventory.water > 0 && state.stats.water < 100) {
                    state.inventory.water--; modifyStat('water', 40); log("💧 水分を補給した!(水分+40)");
                } else log("⚠️ 水がない、または満タンだ!", true);
            } else if (action === 'sleep') {
                log("⛺ 休眠に入った...");
                state.gameTime = 6; state.day++; modifyStat('energy', 100); 
                if (Math.random() < 0.2) { log("💥 夜間攻撃を受けたが、傷一つない!", true); } else log("体力を温存し朝を迎えた。");
                updateUI();
            } else if (action === 'beam') {
                if (state.cooldowns.beam > 0) return; 
                
                const info = MONSTERS[state.charId];
                state.cooldowns.beam = 1.0; // 無敵モードなのでクールダウンを短縮、スタミナ消費なし
                log(`🔥 ${info.beamName}を発射した!`);
                
                // ビーム発射時に体全体を青白く照らす効果
                matBody.emissiveIntensity = 2.0;

                const beamLength = 80; const beamRadius = 3.0;
                if(playerMeshes.heads) {
                    playerMeshes.heads.forEach((headObj) => {
                        const startPos = new THREE.Vector3(); headObj.mouth.getWorldPosition(startPos);
                        const dir = new THREE.Vector3(Math.sin(player.rotation.y), 0, Math.cos(player.rotation.y)).normalize();
                        startPos.add(dir.clone().multiplyScalar(2.0));

                        let beamGeo;
                        if(state.charId==='gyaos') beamGeo = new THREE.BoxGeometry(8, 0.2, beamLength);
                        else beamGeo = new THREE.CylinderGeometry(1.5, 3.0, beamLength, 16);
                        if(state.charId!=='gyaos') beamGeo.rotateX(Math.PI/2);
                        
                        const beam = new THREE.Mesh(beamGeo, new THREE.MeshBasicMaterial({ color: info.beamColor, transparent: true, blending: THREE.AdditiveBlending }));
                        beam.position.copy(startPos.clone().add(dir.clone().multiplyScalar(beamLength/2)));
                        beam.lookAt(startPos.clone().add(dir.clone().multiplyScalar(beamLength)));
                        scene.add(beam); enemies.beams.push({ mesh: beam, life: 0.6 });

                        const checkHit = (arr, name) => {
                            for (let i = arr.length-1; i>=0; i--) {
                                const obj = arr[i]; const pos = (obj.group||obj.mesh).position;
                                const t = new THREE.Vector3().subVectors(pos, startPos).dot(dir);
                                if (t>0 && t<beamLength && new THREE.Vector3().subVectors(pos, startPos).sub(dir.clone().multiplyScalar(t)).length() < obj.radius+beamRadius) {
                                    spawnParticles(pos, 30); scene.remove(obj.group||obj.mesh); arr.splice(i, 1);
                                    if (name === 'ビル') {
                                        const idx = activeBuildings.indexOf(obj.mat); if(idx>-1) activeBuildings.splice(idx,1);
                                        generatedChunks.delete(obj.chunkKey);
                                        spawnItem('rubble', pos.x, pos.z);
                                    }
                                    log(`💥 ${name}を粉砕した!`);
                                }
                            }
                        };
                        checkHit(buildings, 'ビル'); checkHit(enemies.tanks, '戦車');
                    });
                }
                updateUI();
            }
        }

        const keys = { w:false, a:false, s:false, d:false, ArrowUp:false, ArrowDown:false, ArrowLeft:false, ArrowRight:false, Shift:false };
        window.addEventListener('keydown', (e) => {
            if (keys.hasOwnProperty(e.key)) keys[e.key] = true;
            if (e.code === 'Space') { e.preventDefault(); gameAction('beam'); }
            if (e.key === '2') gameAction('eat'); if (e.key === '3') gameAction('drink'); if (e.key === '5') gameAction('sleep');
        });
        window.addEventListener('keyup', (e) => { if (keys.hasOwnProperty(e.key)) keys[e.key] = false; });
        window.addEventListener('resize', () => { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });

        // --- モバイルタッチ操作処理 ---
        const touchInputs = { dx: 0, dz: 0 };
        const joystickZone = document.getElementById('joystick-zone');
        const joystickKnob = document.getElementById('joystick-knob');
        let isDragging = false;
        let joyCenter = { x: 0, y: 0 };

        joystickZone.addEventListener('touchstart', (e) => {
            isDragging = true;
            const rect = joystickZone.getBoundingClientRect();
            joyCenter = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
            updateJoystick(e.touches[0]);
        }, { passive: false });

        joystickZone.addEventListener('touchmove', (e) => {
            if (!isDragging) return;
            e.preventDefault(); // スクロール防止
            updateJoystick(e.touches[0]);
        }, { passive: false });

        joystickZone.addEventListener('touchend', () => {
            isDragging = false;
            touchInputs.dx = 0; touchInputs.dz = 0;
            joystickKnob.style.transform = `translate(0px, 0px)`;
        });

        function updateJoystick(touch) {
            const maxDist = 40; // ノブが動く最大範囲
            let dx = touch.clientX - joyCenter.x;
            let dy = touch.clientY - joyCenter.y;
            const dist = Math.hypot(dx, dy);

            if (dist > maxDist) {
                dx = (dx / dist) * maxDist;
                dy = (dy / dist) * maxDist;
            }

            joystickKnob.style.transform = `translate(${dx}px, ${dy}px)`;

            // ゲーム内の移動量にマッピング (-1.0 ~ 1.0)
            touchInputs.dx = dx / maxDist;
            touchInputs.dz = dy / maxDist;
        }

        const clock = new THREE.Clock();
        let animationTime = 0;

        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta(); 

            // 背景描画用 (スタート画面時)
            if (!state.isPlaying) {
                const t = clock.getElapsedTime();
                camera.position.set(Math.sin(t*0.1)*60, 30, Math.cos(t*0.1)*60); camera.lookAt(0,0,0);
                renderer.render(scene, camera); return;
            }
            if (state.isGameOver) return;

            animationTime += delta;
            
            // 環境の無限更新 (負荷軽減のため0.5秒おき)
            if (animationTime - lastEnvUpdate > 0.5) {
                updateEndlessWorld();
                lastEnvUpdate = animationTime;
            }

            // パーティクル&ビーム更新
            for(let i=particles.length-1; i>=0; i--){
                const p = particles[i]; p.mesh.position.add(p.vel); p.vel.y -= 0.98*delta; p.life-=delta; p.mesh.scale.multiplyScalar(0.95);
                if(p.life<=0){ scene.remove(p.mesh); particles.splice(i,1); }
            }
            for(let i=enemies.beams.length-1; i>=0; i--){
                const b=enemies.beams[i]; b.life-=delta; b.mesh.material.opacity=b.life*2; b.mesh.scale.setScalar(1+(0.6-b.life)*2);
                if(b.life<=0){ scene.remove(b.mesh); enemies.beams.splice(i,1); }
            }

            // 環境光・時間
            state.gameTime += delta * 0.25; if (state.gameTime >= 24) { state.gameTime = 0; state.day++; log("🌙 日が変わった。"); updateUI(); }
            let lightIntensity = 1.0, skyColor = new THREE.Color(0x87CEEB), windowGlow = 0;
            if (state.gameTime>18 || state.gameTime<6) { lightIntensity=0.1; skyColor.setHex(0x0a0505); windowGlow=1; } 
            else if (state.gameTime>16) { lightIntensity=0.5; skyColor.setHex(0xff7733); windowGlow=(state.gameTime-16)/2; } 
            else if (state.gameTime<8) { lightIntensity=0.7; skyColor.setHex(0xffaa88); windowGlow=Math.max(0,1-(state.gameTime-6)/2); }
            hemiLight.intensity = lightIntensity*0.6; sunLight.intensity = THREE.MathUtils.lerp(sunLight.intensity, lightIntensity, 0.05);
            sky.material.color.lerp(skyColor, 0.05); scene.fog.color.lerp(skyColor, 0.05);
            activeBuildings.forEach(m => m.emissiveIntensity = windowGlow*1.5);
            
            const sa = ((state.gameTime-6)/12)*Math.PI; 
            sunLight.position.set(player.position.x + Math.cos(sa)*50, player.position.y + Math.sin(sa)*50, player.position.z);
            sunLight.target.position.copy(player.position);
            sunLight.target.updateMatrixWorld();

            if(state.cooldowns.beam > 0) {
                state.cooldowns.beam -= delta; matBody.emissiveIntensity = Math.max(0, state.cooldowns.beam*1.5);
            }

            // 移動入力 (キーボード + タッチ入力)
            const info = MONSTERS[state.charId];
            let isMoving = false, dx=0, dz=0;
            const ms = keys.Shift ? info.speed*info.runMult : info.speed;
            
            // キーボード
            if(keys.w||keys.ArrowUp){ dz-=ms; isMoving=true; } if(keys.s||keys.ArrowDown){ dz+=ms; isMoving=true; }
            if(keys.a||keys.ArrowLeft){ dx-=ms; isMoving=true; } if(keys.d||keys.ArrowRight){ dx+=ms; isMoving=true; }

            // タッチジョイスティック
            if (Math.abs(touchInputs.dx) > 0.1 || Math.abs(touchInputs.dz) > 0.1) {
                // カメラの向きに合わせて移動方向を補正 (ジョイスティックの上=画面奥)
                const camDir = new THREE.Vector3();
                camera.getWorldDirection(camDir);
                camDir.y = 0;
                camDir.normalize();
                
                // カメラの右方向
                const camRight = new THREE.Vector3().crossVectors(camDir, new THREE.Vector3(0,1,0)).normalize();

                // ジョイスティックの入力をワールド座標の移動量に変換
                // touchInputs.dzがマイナス(上)のとき、前進(camDir方向)
                const moveVec = new THREE.Vector3()
                    .addScaledVector(camDir, -touchInputs.dz * ms)
                    .addScaledVector(camRight, -touchInputs.dx * ms);

                dx += moveVec.x;
                dz += moveVec.z;
                isMoving = true;
            }

            // === 共通アニメーション ===
            const t = animationTime; const ws = isMoving ? (keys.Shift?8:4) : 0; const isFly = info.type === 'fly';

            if(isFly) {
                player.position.y = info.height + Math.sin(t*ws*1.5)*(isMoving?0.5:1.0);
                if (playerMeshes.wings) {
                    playerMeshes.wings.forEach(w => { w.pivot.rotation.z = Math.sin(t*(isMoving?10:4))*0.6*w.side; w.outer.rotation.z = Math.sin(t*(isMoving?10:4)-1)*0.4*w.side; });
                }
                
                if (state.charId === 'gamera') {
                    // ガメラの飛行ポーズ
                    torso.rotation.x = THREE.MathUtils.lerp(torso.rotation.x, Math.PI / 2 - 0.1, 0.1); // うつ伏せ
                    if (playerMeshes.arms) {
                        playerMeshes.arms.forEach((a, i) => {
                            const side = i === 0 ? 1 : -1;
                            // 前方に腕を突き出す
                            a.rotation.set(Math.PI - 0.2, 0, side * 0.4 + Math.sin(t * (isMoving ? 12 : 6)) * 0.05 * side);
                        });
                    }
                    if (playerMeshes.heads && playerMeshes.heads.length > 0) {
                        playerMeshes.heads[0].group.parent.rotation.x = -Math.PI / 2 + 0.1; // 首を前へ向ける
                    }
                    if (playerMeshes.jets) {
                        playerMeshes.jets.forEach(j => {
                            j.scale.set(1.0 + Math.random()*0.1, 1.0 + Math.random()*0.3, 1.0 + Math.random()*0.1);
                        });
                    }
                } else if (state.charId === 'gyaos') {
                    // ギャオスの飛行(立ち気味で脚を垂らす)
                    torso.rotation.x = -0.6 + Math.sin(t*2)*0.05; 
                    if (playerMeshes.legs) {
                        playerMeshes.legs.forEach((l, i) => { 
                            l.thigh.rotation.x = 0.6 + Math.sin(t*2)*0.1; 
                        }); 
                    }
                } else {
                    if (playerMeshes.legs) {
                        playerMeshes.legs.forEach(l => { l.thigh.rotation.x = -0.2+Math.sin(t*2)*0.1; });
                    }
                }
            } else {
                player.position.y = info.height + Math.sin(t*ws*2)*0.1;
                if(playerMeshes.legs) playerMeshes.legs.forEach((l,i) => {
                    const wp = t*ws + (i===0?0:Math.PI);
                    if(isMoving){ l.thigh.rotation.x = -0.2+Math.sin(wp)*0.4; l.shin.rotation.x = 0.3+Math.max(0,Math.sin(wp+Math.PI/2))*0.3; }
                    else { l.thigh.rotation.x = THREE.MathUtils.lerp(l.thigh.rotation.x,-0.2,0.1); l.shin.rotation.x = THREE.MathUtils.lerp(l.shin.rotation.x,0.3,0.1); }
                });
                if(playerMeshes.arms) playerMeshes.arms.forEach((a,i) => { 
                    if (state.charId === 'gamera' && i === 0) {
                        // 右腕(バニシング・フィスト)は常に前に構え、少し揺れる
                        a.rotation.x = isMoving ? 0.2 + Math.sin(t*ws)*0.1 : 0.5 + Math.sin(t*2)*0.05; 
                    } else if (state.charId === 'anguirus') {
                        // 四足歩行モーション
                        const wp = t*ws + (i===0?Math.PI:0); // 後脚と互い違いに
                        a.rotation.x = isMoving ? 0.5 + Math.sin(wp)*0.4 : 0.5;
                    } else {
                        a.rotation.x = isMoving ? Math.sin(t*ws+(i===0?Math.PI:0))*0.3 : 0.5; 
                    }
                });
            }

            if(playerMeshes.tails) playerMeshes.tails.forEach((tailGroup, tIdx) => {
                tailGroup.segments.forEach((seg, i) => {
                    seg.rotation.x = seg.userData.baseAngleX + Math.sin(t*2 - i*0.2)*0.02;
                    seg.rotation.y = Math.cos(t*(isMoving?ws:2) - i*0.2)*0.08 * (tIdx===0?1:-1);
                });
            });
            if(playerMeshes.necks) playerMeshes.necks.forEach((neckGroup, nIdx) => {
                neckGroup.segments.forEach((seg, i) => {
                    seg.rotation.y = Math.sin(t*2 - i*0.3)*0.05 * (nIdx===1?-1:1);
                    seg.rotation.x = seg.userData.baseAngleX + Math.cos(t*2 - i*0.3)*0.02;
                });
            });
            if(playerMeshes.heads) playerMeshes.heads.forEach((hGroup, i) => {
                if(!playerMeshes.necks || playerMeshes.necks.length === 0) { 
                    hGroup.group.rotation.y = Math.cos(t*1.5)*0.05;
                }
            });

            // 敵AIと衝突判定
            const pPos = player.position.clone(); pPos.y += isFly ? 0 : 3;
            for (let i = enemies.tanks.length-1; i>=0; i--) {
                const tank = enemies.tanks[i]; const dist = Math.hypot(pPos.x-tank.x, pPos.z-tank.z);
                const tr = Math.atan2(pPos.x-tank.x, pPos.z-tank.z);
                let diff = tr - tank.mesh.rotation.y; while(diff<-Math.PI) diff+=Math.PI*2; while(diff>Math.PI) diff-=Math.PI*2;
                tank.mesh.rotation.y += diff*2*delta;

                if (dist > 30) { tank.mesh.position.x += Math.sin(tank.mesh.rotation.y)*4*delta; tank.mesh.position.z += Math.cos(tank.mesh.rotation.y)*4*delta; } 
                else {
                    tank.cooldown -= delta;
                    if(tank.cooldown<=0){
                        fireBullet(new THREE.Vector3(0,1.3,1.5).applyMatrix4(tank.mesh.matrixWorld), pPos.clone().add(new THREE.Vector3((Math.random()-0.5)*4,(Math.random()-0.5)*4,(Math.random()-0.5)*4)));
                        tank.cooldown = 1.5+Math.random(); spawnParticles(new THREE.Vector3(0,1.3,1.5).applyMatrix4(tank.mesh.matrixWorld), 5);
                    }
                }
                tank.x = tank.mesh.position.x; tank.z = tank.mesh.position.z;
            }

            let dmg = 0;
            for (let i=enemies.bullets.length-1; i>=0; i--) {
                const b = enemies.bullets[i]; b.mesh.position.addScaledVector(b.dir, b.speed*delta); b.life-=delta;
                if(b.mesh.position.distanceTo(pPos) < (isFly?6:4)){ dmg+=b.damage; spawnParticles(b.mesh.position,10); scene.remove(b.mesh); enemies.bullets.splice(i,1); continue; }
                if(b.mesh.position.y<=0 || b.life<=0){ scene.remove(b.mesh); enemies.bullets.splice(i,1); }
            }
            if(dmg>0){ log(`💥 攻撃を受けたが、怪獣には全く効かない!`, true); }

            if (isMoving) {
                const nx = player.position.x+dx, nz = player.position.z+dz;
                const r = isFly ? 6 : 2.5; // 当たり判定
                
                const checkDestruct = (arr, name) => {
                    for(let i=arr.length-1; i>=0; i--){
                        const obj=arr[i]; if(Math.hypot(nx-obj.x, nz-obj.z) < obj.radius+r){
                            if(isFly && obj.height && obj.height < info.height - 3) continue; // 高く飛びすぎているとビルに当たらない
                            spawnParticles((obj.group||obj.mesh).position, 20); scene.remove(obj.group||obj.mesh); arr.splice(i,1);
                            if(name==='ビル'){ 
                                const idx=activeBuildings.indexOf(obj.mat); if(idx>-1)activeBuildings.splice(idx,1); 
                                generatedChunks.delete(obj.chunkKey);
                                spawnItem('rubble', obj.x, obj.z); 
                            }
                            log(`💥 ${name}を粉砕した!`);
                        }
                    }
                };
                checkDestruct(buildings, 'ビル'); checkDestruct(enemies.tanks, '戦車');

                // 無限移動のため制限を削除
                player.position.x=nx; 
                player.position.z=nz;
                
                if(dx!==0||dz!==0){
                    let diff = Math.atan2(dx, dz) - player.rotation.y; while(diff<-Math.PI) diff+=Math.PI*2; while(diff>Math.PI) diff-=Math.PI*2;
                    player.rotation.y += diff*0.1;
                }
            }

            camera.position.lerp(new THREE.Vector3(player.position.x, player.position.y+(isFly?10:15), player.position.z+(isFly?25:35)), 0.1);
            camera.lookAt(player.position.x, player.position.y+(isFly?0:5), player.position.z);

            for (let i=items.length-1; i>=0; i--) {
                const item = items[i];
                if(Math.hypot(player.position.x-item.mesh.position.x, player.position.z-item.mesh.position.z) < 4) {
                    if(item.type==='food'){ state.inventory.food++; log("🌿 食料をゲットした"); }
                    if(item.type==='water'){ state.inventory.water++; log("💧 水分をゲットした"); }
                    scene.remove(item.mesh); items.splice(i,1); updateUI();
                    // アイテムはupdateEndlessWorldで補充されるのでここでは補充しない
                }
            }
            renderer.render(scene, camera);
        }

        // 初期化用に関数を呼び出す(最初は背景描画のみ)
        // startGameが呼ばれるまでは、背景用の初期世界を生成しておく
        updateEndlessWorld(true);
        animate();

    </script>
</body>
</html>

怪獣たち

たくさんの怪獣の中から自分で「これがいい」と選ぶことができます

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