🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
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>Godzilla 3D Model</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #a39182; /* 煙と夕日を感じるダスティな色に変更 */
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        #canvas-container {
            width: 100vw;
            height: 100vh;
        }
        .loading {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 1.5rem;
        }
    </style>
</head>
<body>

    <div id="canvas-container">
        <div class="loading" id="loading-text"></div>
    </div>

    <!-- Three.js と OrbitControls の読み込み -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>

    <script>
        window.onload = function() {
            document.getElementById('loading-text').style.display = 'none';

            // 1. シーン、カメラ、レンダラーのセットアップ
            const container = document.getElementById('canvas-container');
            const scene = new THREE.Scene();
            
            scene.fog = new THREE.FogExp2(0xa39182, 0.006);

            const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(80, 50, 120);

            const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            container.appendChild(renderer.domElement);

            const controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.target.set(0, 25, 0);
            controls.enableKeys = false; // カメラの矢印キー操作を無効化して首の操作と競合しないようにする

            // 2. 照明のセットアップ
            const ambientLight = new THREE.AmbientLight(0xffeedd, 0.4);
            scene.add(ambientLight);

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

            const fireLight1 = new THREE.PointLight(0xff5500, 2.0, 150);
            fireLight1.position.set(40, 10, 20);
            scene.add(fireLight1);

            const fireLight2 = new THREE.PointLight(0xff3300, 2.5, 200);
            fireLight2.position.set(-30, 5, -40);
            scene.add(fireLight2);

            // 発光アニメーション用の配列
            const spikeMaterials = [];

            // 3. ゴジラの構築
            const godzilla = new THREE.Group();
            
            const bodyColor = 0x161412; 
            const bellyColor = 0x221e1a; 
            const finColor = 0x1a1816; 
            const mouthColor = 0x882222; 
            const teethColor = 0xddddcc; 
            const eyeColor = 0xffaa00; 

            const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor, roughness: 0.9, metalness: 0.1 });
            const bellyMat = new THREE.MeshStandardMaterial({ color: bellyColor, roughness: 0.9, metalness: 0.1 });
            const finMat = new THREE.MeshStandardMaterial({ color: finColor, roughness: 0.8, metalness: 0.2 });
            const mouthMat = new THREE.MeshStandardMaterial({ color: mouthColor, roughness: 0.5 });
            const teethMat = new THREE.MeshStandardMaterial({ color: teethColor, roughness: 0.5 });
            const eyeMat = new THREE.MeshBasicMaterial({ color: eyeColor }); 

            function createBox(w, h, d, mat, castShadow = true) {
                const mesh = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), mat);
                mesh.castShadow = castShadow;
                mesh.receiveShadow = true;
                return mesh;
            }

            // 胴体
            const body = new THREE.Group();
            const lowerBody = createBox(13, 11, 12, bodyMat);
            lowerBody.position.y = 8;
            lowerBody.rotation.x = 0.1; 
            body.add(lowerBody);

            const upperBody = createBox(10, 9, 10, bodyMat);
            upperBody.position.set(0, 16, 2); 
            upperBody.rotation.x = 0.2; 
            body.add(upperBody);

            const chest = createBox(8, 7, 4, bellyMat);
            chest.position.set(0, 16, 6.5);
            chest.rotation.x = 0.3;
            body.add(chest);

            godzilla.add(body);

            // 頭と首
            const neckAndHead = new THREE.Group();
            const neck = createBox(7, 6.5, 7, bodyMat);
            neck.position.set(0, 19.5, 6); 
            neck.rotation.x = 0.3;
            neckAndHead.add(neck);

            const head = new THREE.Group();
            head.position.set(0, 22.5, 9);
            head.rotation.x = -0.1; 
            head.scale.set(0.85, 0.85, 0.85); 
            
            const upperJaw = createBox(4.5, 3, 6, bodyMat);
            head.add(upperJaw);

            const snout = createBox(3.5, 2, 3, bodyMat);
            snout.position.set(0, -0.5, 4);
            head.add(snout);

            const leftEye = createBox(0.4, 0.4, 0.4, eyeMat, false);
            leftEye.position.set(-2.3, 0.5, 1);
            head.add(leftEye);
            const rightEye = createBox(0.4, 0.4, 0.4, eyeMat, false);
            rightEye.position.set(2.3, 0.5, 1);
            head.add(rightEye);

            for(let i=-1; i<=1; i+=2) {
                const fang = new THREE.Mesh(new THREE.ConeGeometry(0.2, 1, 4), teethMat);
                fang.position.set(i*1.2, -1.5, 4.5);
                fang.rotation.x = Math.PI;
                head.add(fang);
            }

            // 下顎 (デフォルトは口を閉じる)
            const jawGroup = new THREE.Group();
            jawGroup.position.set(0, -1.5, 1);
            jawGroup.rotation.x = 0.05; // 口を閉じた状態
            
            const lowerJaw = createBox(3.8, 1.5, 6.5, bodyMat);
            lowerJaw.position.set(0, -0.75, 2);
            jawGroup.add(lowerJaw);

            const tongue = createBox(2.5, 0.8, 4, mouthMat);
            tongue.position.set(0, 0.2, 2.5);
            jawGroup.add(tongue);

            for(let i=-1; i<=1; i+=2) {
                const fang = new THREE.Mesh(new THREE.ConeGeometry(0.2, 1.2, 4), teethMat);
                fang.position.set(i*1.2, 0.5, 4.5);
                jawGroup.add(fang);
            }

            head.add(jawGroup);

            // --- 放射熱線のビーム生成 ---
            const beamGroup = new THREE.Group();
            // コア(中心の白い光)
            const coreGeo = new THREE.CylinderGeometry(0.6, 2.0, 150, 16);
            coreGeo.rotateX(Math.PI / 2); // Z軸方向に向ける
            coreGeo.translate(0, 0, 75);  // 前方に伸ばす
            const coreMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, blending: THREE.AdditiveBlending });
            const beamCore = new THREE.Mesh(coreGeo, coreMat);
            beamGroup.add(beamCore);

            // オーラ(周囲の青い光)
            const auraGeo = new THREE.CylinderGeometry(2.0, 4.5, 150, 16);
            auraGeo.rotateX(Math.PI / 2);
            auraGeo.translate(0, 0, 75);
            const auraMat = new THREE.MeshBasicMaterial({ color: 0x0066ff, transparent: true, opacity: 0.6, blending: THREE.AdditiveBlending });
            const beamAura = new THREE.Mesh(auraGeo, auraMat);
            beamGroup.add(beamAura);

            // 口をもっと大きく開けるため、発射位置を上顎から離して喉の奥(下方向)へ調整
            beamGroup.position.set(0, -1.5, 4.0); 
            beamGroup.visible = false;
            head.add(beamGroup);

            // --- 追加:ビームの向きを取得するためのターゲット ---
            const beamTarget = new THREE.Object3D();
            beamTarget.position.set(0, 0, 100);
            beamGroup.add(beamTarget);
            // ---------------------------

            neckAndHead.add(head);
            godzilla.add(neckAndHead);

            // 腕
            const createArm = (isLeft) => {
                const arm = new THREE.Group();
                const sign = isLeft ? -1 : 1;
                
                const upperArm = createBox(3.5, 5.5, 4.5, bodyMat);
                upperArm.position.set(sign * 6.5, 16, 5);
                upperArm.rotation.z = sign * 0.4;
                upperArm.rotation.x = -0.5; 
                arm.add(upperArm);

                const lowerArm = createBox(2.8, 5.5, 3.5, bodyMat);
                lowerArm.position.set(sign * 7.5, 12, 8);
                lowerArm.rotation.x = -1.2; 
                lowerArm.rotation.y = sign * -0.2;
                arm.add(lowerArm);

                const hand = createBox(2.5, 2, 3, bodyMat);
                hand.position.set(sign * 6.5, 9, 10.5);
                hand.rotation.x = -1.5;
                arm.add(hand);

                for(let i=-1; i<=1; i++) {
                    const claw = new THREE.Mesh(new THREE.ConeGeometry(0.3, 1.5, 4), teethMat);
                    claw.position.set(sign * 6.5 + (i*0.8), 8, 11.5);
                    claw.rotation.x = -1.8;
                    arm.add(claw);
                }
                return arm;
            };

            godzilla.add(createArm(true));
            godzilla.add(createArm(false));

            // 足
            const createLeg = (isLeft) => {
                const leg = new THREE.Group();
                const sign = isLeft ? -1 : 1;

                const thigh = createBox(8.5, 11, 9, bodyMat);
                thigh.position.set(sign * 7.5, 8, -1);
                thigh.rotation.x = -0.2; 
                thigh.rotation.z = sign * -0.2;
                leg.add(thigh);

                const thighMuscle = createBox(6, 8, 5, bodyMat);
                thighMuscle.position.set(sign * 7.5, 7, 3);
                thighMuscle.rotation.x = -0.4;
                leg.add(thighMuscle);

                const calf = createBox(6, 6, 7, bodyMat);
                calf.position.set(sign * 8, 4, 1.5);
                leg.add(calf);

                const foot = createBox(7, 3.5, 9, bodyMat);
                foot.position.set(sign * 8.5, 1.75, 4.5); 
                foot.rotation.y = sign * 0.2; 
                leg.add(foot);

                for(let i=-1; i<=1; i++) {
                    const claw = new THREE.Mesh(new THREE.ConeGeometry(0.6, 3.0, 4), teethMat);
                    claw.position.set(sign * 8.5 + (i*2.0), 1.0, 9.0);
                    claw.rotation.x = -1.2;
                    claw.rotation.y = sign * (i*0.1);
                    leg.add(claw);
                }

                if (!isLeft) {
                    leg.position.z = -2;
                } else {
                    leg.position.z = 1;
                }
                return leg;
            };

            godzilla.add(createLeg(true));
            godzilla.add(createLeg(false));

            // 尻尾
            const tail = new THREE.Group();
            const tailSegments = 12; 
            const tailSpikeMats = []; // 尻尾のトゲのマテリアルを一時保存
            
            for (let i = 0; i < tailSegments; i++) {
                const size = 9 - (i * 0.7);
                const seg = createBox(size, size*0.9, size, bodyMat);
                
                const zPos = -6 - (i * 4);
                let yPos = 8 - (i * 0.8);
                if (i > 5) yPos = 4 - ((i-5) * 0.3); 
                if (yPos < size/2) yPos = size/2; 
                
                seg.position.set(0, yPos, zPos);
                
                if (i % 2 === 0 && i < 10) {
                    const sMat = finMat.clone(); // 発光用にクローン
                    const spike = new THREE.Mesh(new THREE.ConeGeometry(size*0.2, size*0.8, 4), sMat);
                    spike.position.set(0, size*0.5, 0);
                    seg.add(spike);
                    tailSpikeMats.push(sMat);
                }

                tail.add(seg);
            }
            godzilla.add(tail);

            // 尻尾の先端(配列の最後)から順番にするためリバースして追加
            spikeMaterials.push(...tailSpikeMats.reverse());

            // 背びれ
            const fins = new THREE.Group();
            const finPositions = [
                {y: 22, z: 6, size: 2.5},   // 首
                {y: 19.5, z: 2, size: 4.5},
                {y: 16, z: -3, size: 7.5},  // 背中
                {y: 11, z: -7, size: 6.0},
                {y: 7, z: -10, size: 4.0},  
                {y: 4, z: -12, size: 2.0},  // 尻尾の付け根
            ];

            function createSpikyFin(baseSize, matArray) {
                const group = new THREE.Group();
                const mainMat = finMat.clone(); // 発光用にクローン
                matArray.push(mainMat);
                
                const main = new THREE.Mesh(new THREE.ConeGeometry(baseSize*0.35, baseSize*3.5, 4), mainMat);
                main.castShadow = true;
                group.add(main);

                const spikeCount = Math.floor(baseSize * 1.5);
                for(let j=0; j<spikeCount; j++) {
                    const sSize = baseSize * (0.3 + Math.random()*0.4);
                    const subMat = finMat.clone();
                    matArray.push(subMat);
                    
                    const spike = new THREE.Mesh(new THREE.ConeGeometry(sSize*0.3, sSize*3, 4), subMat);
                    spike.position.set(
                        (Math.random()-0.5) * baseSize * 0.6,
                        (Math.random()-0.5) * baseSize * 1.5,
                        (Math.random()-0.5) * baseSize * 0.6
                    );
                    spike.rotation.set(
                        (Math.random()-0.5) * 0.8, 0, (Math.random()-0.5) * 0.8
                    );
                    spike.castShadow = true;
                    group.add(spike);
                }
                return group;
            }

            // 尻尾側から首側へ向かって背びれを生成&マテリアル登録
            const reversedFins = [...finPositions].reverse();
            reversedFins.forEach((pos, i) => {
                const cFin = createSpikyFin(pos.size, spikeMaterials);
                cFin.position.set(0, pos.y, pos.z);
                cFin.rotation.x = -0.3 - ((reversedFins.length - 1 - i)*0.15); 
                fins.add(cFin);

                if (pos.size > 3) {
                    const lFin = createSpikyFin(pos.size * 0.6, spikeMaterials);
                    lFin.position.set(-pos.size*0.6, pos.y - pos.size*0.4, pos.z + 0.5);
                    lFin.rotation.x = -0.3 - ((reversedFins.length - 1 - i)*0.15);
                    lFin.rotation.z = 0.5; 
                    fins.add(lFin);

                    const rFin = createSpikyFin(pos.size * 0.6, spikeMaterials);
                    rFin.position.set(pos.size*0.6, pos.y - pos.size*0.4, pos.z + 0.5);
                    rFin.rotation.x = -0.3 - ((reversedFins.length - 1 - i)*0.15);
                    rFin.rotation.z = -0.5;
                    fins.add(rFin);
                }
            });
            godzilla.add(fins);

            godzilla.scale.set(2.5, 2.5, 2.5);
            scene.add(godzilla);

            // --- 破壊ギミック用の変数 ---
            const destructibles = []; // 破壊可能なオブジェクトのリスト
            const activeDebris = []; // 飛び散っている最中の破片リスト
            const beamRaycaster = new THREE.Raycaster();
            
            // ビーム着弾時に光るライト
            const hitLight = new THREE.PointLight(0xff5500, 0, 150);
            scene.add(hitLight);


            // 4. 破壊された街並み (既存のまま)
            const floorGeo = new THREE.PlaneGeometry(300, 300);
            const floorMat = new THREE.MeshStandardMaterial({ color: 0x332b22, roughness: 1.0 }); 
            const floor = new THREE.Mesh(floorGeo, floorMat);
            floor.rotation.x = -Math.PI / 2;
            floor.receiveShadow = true;
            scene.add(floor);

            const buildings = new THREE.Group();
            const buildingMats = [
                new THREE.MeshLambertMaterial({ color: 0x555550 }), 
                new THREE.MeshLambertMaterial({ color: 0x444b55 }), 
                new THREE.MeshLambertMaterial({ color: 0x665a44 }), 
                new THREE.MeshLambertMaterial({ color: 0x333333 }), 
                new THREE.MeshLambertMaterial({ color: 0x4a2a22 })  
            ];

            for (let i = -6; i <= 6; i++) {
                createBuilding(-25 - Math.random() * 5, i * 20 + (Math.random() - 0.5) * 10);
                createBuilding(25 + Math.random() * 5, i * 20 + (Math.random() - 0.5) * 10);
                createBuilding(-50 - Math.random() * 15, i * 25);
                createBuilding(50 + Math.random() * 15, i * 25);
            }

            function createBuilding(x, z) {
                if (Math.abs(x - (-25)) < 6 && Math.abs(z - 15) < 6) {
                    const wakoMat = buildingMats[0]; 
                    const base = new THREE.Mesh(new THREE.BoxGeometry(16, 15, 16), wakoMat);
                    base.position.set(x, 7.5, z);
                    base.castShadow = true;
                    base.receiveShadow = true;
                    buildings.add(base);
                    destructibles.push(base); // 追加:破壊対象に登録
                    
                    const tower = new THREE.Mesh(new THREE.BoxGeometry(6, 10, 6), wakoMat);
                    tower.position.set(x + 2, 19, z - 2);
                    tower.rotation.z = -0.4;
                    tower.rotation.x = 0.2;
                    tower.castShadow = true;
                    buildings.add(tower);
                    destructibles.push(tower); // 追加

                    const debris = new THREE.Mesh(new THREE.BoxGeometry(7, 5, 8), wakoMat);
                    debris.position.set(x + 6, 2.5, z + 6);
                    debris.rotation.set(0.5, 0.8, -0.2);
                    debris.castShadow = true;
                    buildings.add(debris);
                    destructibles.push(debris); // 追加
                    return;
                }

                const width = 10 + Math.random() * 12;
                const depth = 10 + Math.random() * 12;
                const height = 5 + Math.random() * 15; 
                
                const mat = buildingMats[Math.floor(Math.random() * buildingMats.length)];
                const bldg = new THREE.Mesh(new THREE.BoxGeometry(width, height, depth), mat);
                bldg.position.set(x, height / 2, z);
                
                if (Math.random() > 0.8) {
                    bldg.rotation.x = (Math.random() - 0.5) * 0.3;
                    bldg.rotation.z = (Math.random() - 0.5) * 0.3;
                }
                
                bldg.castShadow = true;
                bldg.receiveShadow = true;
                buildings.add(bldg);
                destructibles.push(bldg); // 追加

                const topDebrisCount = Math.floor(Math.random() * 3) + 1;
                for(let j=0; j<topDebrisCount; j++) {
                    const tWidth = width * (0.3 + Math.random() * 0.5);
                    const tDepth = depth * (0.3 + Math.random() * 0.5);
                    const tHeight = 2 + Math.random() * 6;
                    const topObj = new THREE.Mesh(new THREE.BoxGeometry(tWidth, tHeight, tDepth), mat);
                    topObj.position.set(
                        x + (Math.random() - 0.5) * (width - tWidth),
                        height + tHeight / 2 - 1,
                        z + (Math.random() - 0.5) * (depth - tDepth)
                    );
                    topObj.rotation.set((Math.random()-0.5), (Math.random()-0.5), (Math.random()-0.5));
                    topObj.castShadow = true;
                    buildings.add(topObj);
                    destructibles.push(topObj); // 追加
                }
            }
            scene.add(buildings);

            const rubbleGeo = new THREE.BoxGeometry(1, 1, 1);
            const rubbleMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 1.0 });
            const emberMat = new THREE.MeshBasicMaterial({ color: 0xff4400 }); 

            // --- 追加:ガラガラと崩れて燃える破片を生成する関数 ---
            function createExplosion(position, scaleFactor) {
                const count = Math.floor(Math.random() * 8) + 5; 
                for (let i = 0; i < count; i++) {
                    const mesh = new THREE.Mesh(rubbleGeo, emberMat); // 最初は燃えている
                    const size = Math.random() * 1.5 + Math.min(scaleFactor * 0.1, 2);
                    mesh.scale.set(size, size, size);
                    
                    mesh.position.copy(position);
                    mesh.position.x += (Math.random() - 0.5) * scaleFactor;
                    mesh.position.y += (Math.random() - 0.5) * scaleFactor;
                    mesh.position.z += (Math.random() - 0.5) * scaleFactor;
                    
                    scene.add(mesh);
                    
                    activeDebris.push({
                        mesh: mesh,
                        velocity: new THREE.Vector3(
                            (Math.random() - 0.5) * 40,
                            Math.random() * 30 + 10, // 上方向に吹き飛ぶ
                            (Math.random() - 0.5) * 40
                        ),
                        life: 1.5 + Math.random() * 2.0, // 燃え続ける時間(秒)
                        age: 0,
                        size: size
                    });
                }
            }
            // --------------------------------------------------

            for (let i = 0; i < 300; i++) {
                const isEmber = Math.random() > 0.85; 
                const mesh = new THREE.Mesh(rubbleGeo, isEmber ? emberMat : rubbleMat);
                
                const size = isEmber ? (Math.random() * 1.5 + 0.5) : (Math.random() * 4 + 1);
                mesh.scale.set(size, size, size);
                
                const rx = (Math.random() - 0.5) * 120;
                const rz = (Math.random() - 0.5) * 120;
                const ry = (isEmber && Math.random() > 0.5) ? Math.random() * 20 : size / 2;
                
                mesh.position.set(rx, ry, rz);
                mesh.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
                
                if (!isEmber) {
                    mesh.castShadow = true;
                    mesh.receiveShadow = true;
                }
                scene.add(mesh);
                destructibles.push(mesh); // 追加:既存の瓦礫もビームで吹き飛ぶようにする
            }

            const trackGroup = new THREE.Group();
            const railMat = new THREE.MeshStandardMaterial({ color: 0x555555, metalness: 0.8, roughness: 0.5 });
            const sleeperMat = new THREE.MeshStandardMaterial({ color: 0x3a2a1a, roughness: 1.0 });

            const trackZ = 35;
            for(let x = -100; x <= 100; x += 3) {
                if (Math.random() > 0.3) { 
                    const sleeper = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.5, 6), sleeperMat);
                    sleeper.position.set(x, 0.25, trackZ + (Math.random()-0.5)*0.5);
                    sleeper.rotation.y = (Math.random() - 0.5) * 0.2;
                    sleeper.castShadow = true;
                    sleeper.receiveShadow = true;
                    trackGroup.add(sleeper);
                    destructibles.push(sleeper); // 追加
                }
            }

            for(let x = -100; x <= 100; x += 10) {
                if(Math.random() > 0.2) { 
                    const rail1 = new THREE.Mesh(new THREE.BoxGeometry(10, 0.8, 0.5), railMat);
                    rail1.position.set(x, 0.6, trackZ - 1.5);
                    if(Math.random() > 0.7) { 
                        rail1.rotation.y = (Math.random()-0.5) * 0.8;
                        rail1.position.y += Math.random() * 2.5; 
                        rail1.rotation.z = (Math.random()-0.5) * 0.5;
                    }
                    rail1.castShadow = true;
                    trackGroup.add(rail1);
                    destructibles.push(rail1); // 追加

                    const rail2 = new THREE.Mesh(new THREE.BoxGeometry(10, 0.8, 0.5), railMat);
                    rail2.position.set(x, 0.6, trackZ + 1.5);
                    if(Math.random() > 0.7) {
                        rail2.rotation.y = (Math.random()-0.5) * 0.8;
                        rail2.position.y += Math.random() * 2.5;
                        rail2.rotation.z = (Math.random()-0.5) * 0.5;
                    }
                    rail2.castShadow = true;
                    trackGroup.add(rail2);
                    destructibles.push(rail2); // 追加
                }
            }
            scene.add(trackGroup);

            const planes = [];
            const planeMat = new THREE.MeshStandardMaterial({ color: 0x3a4a3a, metalness: 0.6, roughness: 0.5 }); 
            const propMat = new THREE.MeshStandardMaterial({ color: 0x111111 });

            function createPlane() {
                const plane = new THREE.Group();
                const bodyGeo = new THREE.CylinderGeometry(0.5, 1.2, 8, 8);
                bodyGeo.rotateX(Math.PI / 2); 
                const planeBody = new THREE.Mesh(bodyGeo, planeMat);
                plane.add(planeBody);

                const mainWing = new THREE.Mesh(new THREE.BoxGeometry(14, 0.2, 2.5), planeMat);
                mainWing.position.set(0, 0, 1.5);
                plane.add(mainWing);

                const vWing1 = new THREE.Mesh(new THREE.BoxGeometry(0.2, 2.5, 2), planeMat);
                vWing1.position.set(-4.5, 1, 1.5);
                plane.add(vWing1);
                const vWing2 = new THREE.Mesh(new THREE.BoxGeometry(0.2, 2.5, 2), planeMat);
                vWing2.position.set(4.5, 1, 1.5);
                plane.add(vWing2);

                const frontWing = new THREE.Mesh(new THREE.BoxGeometry(5, 0.2, 1.2), planeMat);
                frontWing.position.set(0, 0, -3);
                plane.add(frontWing);

                const canopyMat = new THREE.MeshStandardMaterial({ color: 0x112233, roughness: 0.1, metalness: 0.9 });
                const canopy = new THREE.Mesh(new THREE.BoxGeometry(1.0, 1.2, 2.5), canopyMat);
                canopy.position.set(0, 0.8, -0.5);
                plane.add(canopy);

                const propeller = new THREE.Group();
                for(let j=0; j<6; j++) { 
                    const blade = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.1, 0.3), propMat);
                    blade.rotation.z = (Math.PI / 3) * j;
                    propeller.add(blade);
                }
                propeller.position.set(0, 0, 4.2);
                plane.add(propeller);
                plane.propeller = propeller; 

                plane.scale.set(1.5, 1.5, 1.5); 
                plane.castShadow = true;
                return plane;
            }

            for(let i=0; i<3; i++) {
                const p = createPlane();
                scene.add(p);
                planes.push({
                    mesh: p,
                    angle: Math.PI * 0.6 * i, 
                    radius: 70 + i * 15, 
                    speed: 1.2 + i * 0.1, 
                    yOffset: 65 + i * 10 
                });
            }

            // --- インタラクションとシークエンス制御 ---
            let isFiring = false;
            let fireStartTime = 0;

            // クリック検知 (Raycaster)
            const raycaster = new THREE.Raycaster();
            const mouse = new THREE.Vector2();

            window.addEventListener('pointerdown', (event) => {
                // 画面クリック時に確実にフォーカスを当ててキーボード入力を受け付けるようにする
                window.focus(); 

                if (isFiring) return; // 既に発射中なら無視
                
                mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
                mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
                raycaster.setFromCamera(mouse, camera);
                
                // ゴジラをクリックしたか判定
                const intersects = raycaster.intersectObject(godzilla, true);
                if (intersects.length > 0) {
                    isFiring = true;
                    fireStartTime = clock.getElapsedTime();
                }
            });

            // キーボード操作 (十字キーで首を動かす)
            const keys = { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false };
            let keyRotX = 0; // キーによる縦の回転オフセット
            let keyRotY = 0; // キーによる横の回転オフセット

            // documentに対してイベントを付けることで、入力を検知しやすくする
            document.addEventListener('keydown', (e) => {
                if (keys.hasOwnProperty(e.code)) {
                    keys[e.code] = true;
                    e.preventDefault(); // 画面のスクロールを防ぐ
                }
            }, { passive: false });
            
            document.addEventListener('keyup', (e) => {
                if (keys.hasOwnProperty(e.code)) {
                    keys[e.code] = false;
                }
            });


            // 5. アニメーション
            const clock = new THREE.Clock();
            let lastTime = 0; // 追加:経過時間を正確に取るため

            function animate() {
                requestAnimationFrame(animate);

                const time = clock.getElapsedTime();
                let dt = time - lastTime;
                if (dt > 0.1) dt = 0.016; // タブ復帰時の経過時間異常を防ぐ
                lastTime = time;

                // 十字キーによる首の回転オフセットの更新
                if (keys.ArrowUp) keyRotX -= 0.03;
                if (keys.ArrowDown) keyRotX += 0.03;
                if (keys.ArrowLeft) keyRotY += 0.03;
                if (keys.ArrowRight) keyRotY -= 0.03;
                // 限界角を設定
                keyRotX = THREE.MathUtils.clamp(keyRotX, -0.6, 0.6);
                keyRotY = THREE.MathUtils.clamp(keyRotY, -0.8, 0.8);

                // 基本モーションの変数
                let breathY = Math.sin(time * 1.0) * 0.15;
                let neckBreathY = Math.sin(time * 1.0 - 0.5) * 0.2;
                let animBodyX = 0.2; // 上半身の基本角度
                let animHeadX = -0.1; // 頭の基本角度
                let animJawX = 0.05; // 基本は口を閉じる
                let shakeX = 0; // 振動エフェクト
                let shakeY = 0;

                if (isFiring) {
                    const t = time - fireStartTime;

                    if (t < 3.0) {
                        // --- フェーズ1:チャージ(背びれが光り、力を込めて下を向く) ---
                        const progress = t / 3.0; // 0.0 ~ 1.0
                        
                        // 尻尾から順番に発光させる
                        const glowIndex = Math.floor(progress * spikeMaterials.length);
                        spikeMaterials.forEach((mat, index) => {
                            if (index <= glowIndex) {
                                // 光った場所は明滅する
                                const intensity = 0.5 + Math.sin(time * 20) * 0.5;
                                mat.emissive.setHex(0x0088ff);
                                mat.emissiveIntensity = intensity * 2.0; 
                            } else {
                                mat.emissive.setHex(0x000000);
                            }
                        });

                        // 力を込めてゆっくり下を向くモーション (後半から)
                        if (t > 1.5) {
                            const dipProgress = (t - 1.5) / 1.5; 
                            animHeadX = -0.1 + dipProgress * 0.7; // 下を向く
                            animBodyX = 0.2 + dipProgress * 0.25; // 前のめり
                            
                            // プルプルと力みで震える
                            shakeX = (Math.random() - 0.5) * 0.15;
                            shakeY = (Math.random() - 0.5) * 0.15;
                        }

                    } else if (t < 3.6) {
                        // --- フェーズ2:振り上げ(顔を上げ、ガバッと口を開ける) ---
                        const upProgress = (t - 3.0) / 0.6; // 0.0 ~ 1.0
                        
                        animHeadX = 0.6 - upProgress * 1.0; // 下から上へ一気に振り上げる
                        animBodyX = 0.45 - upProgress * 0.2; // 少し起き上がる
                        animJawX = 0.05 + upProgress * 1.15; // 顎が外れるくらい大きく口を開ける

                        // 背びれ全体が最大発光
                        spikeMaterials.forEach(mat => {
                            mat.emissive.setHex(0x00aaff);
                            mat.emissiveIntensity = 2.0 + Math.sin(time * 30) * 1.0;
                        });

                    } else if (t < 6.5) {
                        // --- フェーズ3:熱線放射! ---
                        animHeadX = -0.4; // 放射中の基本角度 (少し上向き)
                        animJawX = 1.2;  // 全開(大きく開いた状態をキープ)
                        beamGroup.visible = true;

                        // ビームの揺らぎエフェクト
                        beamCore.scale.set(1 + Math.random()*0.3, 1, 1 + Math.random()*0.3);
                        beamAura.scale.set(1 + Math.random()*0.4, 1, 1 + Math.random()*0.4);
                        beamAura.material.opacity = 0.4 + Math.random()*0.6;

                        // 激しい発光
                        spikeMaterials.forEach(mat => {
                            mat.emissive.setHex(0x00aaff);
                            mat.emissiveIntensity = 2.5 + Math.random() * 1.5;
                        });

                        // 放射の反動で激しく震える
                        shakeX = (Math.random() - 0.5) * 0.4;
                        shakeY = (Math.random() - 0.5) * 0.4;

                        // --- 追加:ビームの当たり判定と破壊処理 ---
                        const beamStart = new THREE.Vector3();
                        beamGroup.getWorldPosition(beamStart);
                        const beamEnd = new THREE.Vector3();
                        beamTarget.getWorldPosition(beamEnd);
                        // ビームの進む方向を計算
                        const beamDir = new THREE.Vector3().subVectors(beamEnd, beamStart).normalize();
                        
                        beamRaycaster.set(beamStart, beamDir);
                        const intersects = beamRaycaster.intersectObjects(destructibles, false);
                        
                        if (intersects.length > 0) {
                            const hitPoint = intersects[0].point;
                            hitLight.position.copy(hitPoint);
                            hitLight.intensity = 3.0; // 着弾点をオレンジ色に強く光らせる
                            
                            // ヒットポイント周辺のオブジェクトを巻き込んで破壊
                            const destroyRadius = 15; 
                            for (let i = destructibles.length - 1; i >= 0; i--) {
                                const obj = destructibles[i];
                                if (obj.position.distanceTo(hitPoint) < destroyRadius) {
                                    let scaleStr = 5;
                                    if (obj.geometry && obj.geometry.parameters && obj.geometry.parameters.height) {
                                        scaleStr = obj.geometry.parameters.height;
                                    }
                                    
                                    // 爆発(破片)生成
                                    createExplosion(obj.position, scaleStr);
                                    
                                    // ビルや瓦礫をシーンから削除
                                    if (obj.parent) obj.parent.remove(obj);
                                    destructibles.splice(i, 1);
                                }
                            }
                        }
                        // ------------------------------------

                    } else if (t < 8.0) {
                        // --- フェーズ4:クールダウン ---
                        const coolProgress = (t - 6.5) / 1.5;
                        beamGroup.visible = false;
                        animJawX = 1.2 * (1.0 - coolProgress); // ゆっくり口を閉じる

                        // 発光がスーッと消えていく
                        spikeMaterials.forEach(mat => {
                            mat.emissiveIntensity = (1.0 - coolProgress) * 2.5;
                        });

                    } else {
                        // 終了して元に戻る
                        isFiring = false;
                        beamGroup.visible = false;
                        spikeMaterials.forEach(mat => {
                            mat.emissive.setHex(0x000000);
                            mat.emissiveIntensity = 0;
                        });
                    }
                }

                // モーションの適用(アニメーションの基本姿勢 + 揺れ)
                body.position.y = breathY;
                body.position.x = shakeX;
                upperBody.rotation.x = animBodyX;
                jawGroup.rotation.x = animJawX;
                
                neckAndHead.position.y = neckBreathY;
                neckAndHead.position.x = shakeX;

                // 頭の角度は「アニメーションの基本角度 + 十字キーでの操作角度」を滑らかに適用
                const targetHeadX = animHeadX + keyRotX;
                const targetHeadY = keyRotY;
                head.rotation.x += (targetHeadX - head.rotation.x) * 0.15;
                head.rotation.y += (targetHeadY - head.rotation.y) * 0.15;

                // 尻尾のゆらゆら
                tail.children.forEach((seg, index) => {
                    seg.position.x = Math.sin(time * 1.5 - index * 0.3) * (index * 0.2);
                });

                // 戦闘機のアニメーション
                planes.forEach(planeData => {
                    const p = planeData.mesh;
                    const currentAngle = planeData.angle - time * planeData.speed; 
                    p.position.x = Math.cos(currentAngle) * planeData.radius;
                    p.position.z = Math.sin(currentAngle) * planeData.radius;
                    p.position.y = planeData.yOffset + Math.sin(time * 3 + planeData.angle) * 3; 
                    p.rotation.y = -currentAngle; 
                    p.rotation.z = -0.4; 
                    p.rotation.x = 0.1;  
                    p.propeller.rotation.z += 0.8;
                });

                // --- 追加:飛び散る破片の物理挙動と燃焼効果の更新 ---
                for (let i = activeDebris.length - 1; i >= 0; i--) {
                    const debris = activeDebris[i];
                    debris.age += dt;
                    
                    debris.velocity.y -= 80 * dt; // 重力で落ちる
                    debris.mesh.position.addScaledVector(debris.velocity, dt);
                    debris.mesh.rotation.x += dt * 5;
                    debris.mesh.rotation.y += dt * 5;
                    
                    // 床でのバウンドと摩擦
                    if (debris.mesh.position.y < debris.size / 2) {
                        debris.mesh.position.y = debris.size / 2;
                        debris.velocity.y *= -0.4; // 弾む
                        debris.velocity.x *= 0.7;  // 摩擦で減速
                        debris.velocity.z *= 0.7;
                    }
                    
                    // 燃え尽きたら色を黒い瓦礫に戻す
                    if (debris.age > debris.life && debris.mesh.material === emberMat) {
                        debris.mesh.material = rubbleMat; 
                    }
                    
                    // 動かなくなってしばらくしたら更新対象から外す(ただの瓦礫として残す)
                    if (debris.age > debris.life + 3.0) {
                        activeDebris.splice(i, 1);
                    }
                }
                
                // 着弾ライトを徐々に暗くする
                if (hitLight.intensity > 0) {
                    hitLight.intensity = Math.max(0, hitLight.intensity - dt * 8.0);
                }
                // ----------------------------------------------------

                controls.update();
                renderer.render(scene, camera);
            }

            animate();

            window.addEventListener('resize', () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });
        };
    </script>
</body>
</html>
✨ ピックアップ

gozira

ゴジラがビームを出します。

🤖 使用したAI
Gemini
💡 工夫したこと・プロンプトのポイント
ゴジラの動きを作るのに苦心しました。
👁 51 回閲覧
📅 投稿:2026年3月21日 🔄 更新:2026年8月13日
応援スタンプを押してみよう!