<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gyaos Hyper Flock Simulator</title>
<!-- Tailwind CSS for UI styling -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Three.js and 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>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #0c0f12;
user-select: none;
-webkit-user-select: none;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
/* ネオン調 of ライトエフェクト */
.neon-text {
text-shadow: 0 0 5px rgba(239, 68, 68, 0.5), 0 0 15px rgba(239, 68, 68, 0.7);
}
.neon-border {
box-shadow: 0 0 10px rgba(239, 68, 68, 0.2), inset 0 0 10px rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
}
</style>
</head>
<body class="relative">
<!-- 3D表示用キャンバス -->
<div id="canvas-container" class="absolute inset-0 w-full h-full z-0"></div>
<!-- UIオーバーレイ -->
<div class="absolute inset-0 flex flex-col justify-between p-4 md:p-6 pointer-events-none z-10">
<!-- ヘッダー(世代によってテキストが動的に切り替わります) -->
<header class="flex justify-between items-start w-full">
<div class="bg-black/85 backdrop-blur-md p-4 rounded-lg neon-border pointer-events-auto flex flex-col gap-2">
<h1 id="title-main" class="text-red-500 font-black text-xl md:text-2xl tracking-widest neon-text font-serif">
GAMERA vs GYAOS-HYPER <span id="title-sub" class="text-[10px] text-red-400 font-sans block tracking-wider font-bold">ガメラ 対 ギャオス・ハイパー 大群シミュレーター</span>
</h1>
<p id="title-desc" class="text-gray-400 text-[10px] mt-0.5">ガメラ3 邪神〈イリス〉覚醒 (1999) より</p>
<!-- BGM コントローラー -->
<div class="flex items-center gap-2 mt-2 pt-2 border-t border-red-950/40">
<button id="bgm-btn" class="px-3 py-1 bg-red-950 hover:bg-red-900 text-red-400 text-xs font-bold rounded border border-red-800/40 flex items-center gap-1.5 transition">
<span id="bgm-icon">🔇</span> <span id="bgm-text">BGM OFF</span>
</button>
<span class="text-[9px] text-gray-500 select-none">「裂ける夜空」 BGM 収録</span>
</div>
</div>
<div class="bg-black/85 backdrop-blur-md p-3 rounded-lg text-right text-xs text-gray-400 neon-border hidden sm:block pointer-events-auto">
<div>[ドラッグ] 視点回転 / [スクロール] ズーム</div>
<div>[Space] 超音波メス射撃 / [Enter] プラズマ火球</div>
<div class="text-orange-400 font-bold animate-pulse">両方同時に撃つと中央で激突!</div>
</div>
</header>
<!-- フラッシュエフェクト用画面オーバーレイ -->
<div id="flash-overlay" class="absolute inset-0 bg-red-600/0 pointer-events-none transition-all duration-75"></div>
<!-- ボトムコントロール -->
<footer class="flex flex-col md:flex-row justify-between items-end gap-4 w-full">
<!-- ステータス・操作パネル -->
<div class="bg-black/85 backdrop-blur-md p-4 rounded-lg neon-border w-full md:w-80 pointer-events-auto">
<h3 class="text-red-400 font-bold text-sm mb-3 tracking-wider uppercase border-b border-red-900/50 pb-1">大群コントロールシステム</h3>
<!-- 世代選択(平成版と昭和版の切り替え) -->
<div class="mb-3">
<div class="flex items-center justify-between text-xs text-gray-300 mb-1">
<span>怪獣・世代設定</span>
<div class="flex gap-1" id="gen-controls">
<button id="gen-heisei-btn" class="px-2.5 py-1 bg-red-600 text-white rounded text-[11px] font-bold border border-red-500 transition">
平成 (1999)
</button>
<button id="gen-showa-btn" class="px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-gray-300 rounded text-[11px] border border-zinc-700 transition">
昭和 (1967)
</button>
</div>
</div>
</div>
<!-- スライダー:群れの数(平成モード時のみ表示) -->
<div id="flock-control-container" class="mb-3">
<div class="flex justify-between text-xs text-gray-300 mb-1">
<span>ギャオス個体数</span>
<span id="flock-val">9 匹</span>
</div>
<input id="flock-slider" type="range" min="1" max="15" step="1" value="9"
class="w-full h-1 bg-red-950 rounded-lg appearance-none cursor-pointer accent-red-600">
</div>
<!-- スライダー:編隊の広がり(平成モード時のみ表示) -->
<div id="spread-control-container" class="mb-3">
<div class="flex justify-between text-xs text-gray-300 mb-1">
<span>編隊の間隔</span>
<span id="spread-val">1.0x</span>
</div>
<input id="spread-slider" type="range" min="0.5" max="2.0" step="0.1" value="1.0"
class="w-full h-1 bg-red-950 rounded-lg appearance-none cursor-pointer accent-red-600">
</div>
<!-- 環境選択トグル -->
<div class="flex items-center justify-between text-xs text-gray-300 mb-1">
<span>環境プリセット</span>
<div class="flex gap-1" id="env-controls">
<button id="env-day-btn" class="px-2 py-0.5 bg-red-600 text-white rounded text-[11px] font-bold border border-red-500 transition">
昼間
</button>
<button id="env-studio-btn" class="px-2 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-gray-300 rounded text-[11px] border border-zinc-700 transition">
スタジオ
</button>
<button id="env-night-btn" class="px-2 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-gray-300 rounded text-[11px] border border-zinc-700 transition">
夜間
</button>
</div>
</div>
</div>
<!-- メイン攻撃ボタン -->
<div class="w-full md:w-auto flex flex-col sm:flex-row gap-3 pointer-events-auto">
<button id="beam-btn" class="w-full sm:w-48 py-3 px-4 bg-gradient-to-r from-red-800 to-red-600 hover:from-red-700 hover:to-red-500 text-white font-bold text-sm rounded-xl shadow-[0_0_15px_rgba(239,68,68,0.5)] border border-red-400 transition transform active:scale-95">
超音波メス 一斉掃射
</button>
<button id="gamera-btn" class="w-full sm:w-48 py-3 px-4 bg-gradient-to-r from-orange-800 to-yellow-600 hover:from-orange-700 hover:to-yellow-500 text-white font-bold text-sm rounded-xl shadow-[0_0_15px_rgba(245,158,11,0.5)] border border-orange-400 transition transform active:scale-95 animate-pulse">
ガメラ:火球・火炎
</button>
</div>
</footer>
</div>
<!-- JavaScript ロジック -->
<script>
// --- STREAMING_CHUNK:Initializing the main scene, camera, and renderer... ---
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
const colorDay = new THREE.Color(0xa5c5f0);
const colorStudio = new THREE.Color(0xe0e4eb);
const colorNight = new THREE.Color(0x0c0303);
scene.background = colorDay;
scene.fog = new THREE.FogExp2(0xa5c5f0, 0.012);
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(-12, 10, 24); // 対決構図が見やすい斜めの高所アングル
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxPolarAngle = Math.PI / 2 + 0.15;
controls.minDistance = 5;
controls.maxDistance = 60;
// --- STREAMING_CHUNK:Configuring the lighting systems... ---
// ライティング
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
scene.add(ambientLight);
const mainLight = new THREE.DirectionalLight(0xffffff, 1.8);
mainLight.position.set(20, 40, 10);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
mainLight.shadow.bias = -0.0005;
scene.add(mainLight);
const fillLight = new THREE.DirectionalLight(0xddf0ff, 0.8);
fillLight.position.set(-15, -10, -10);
scene.add(fillLight);
const glowLight = new THREE.PointLight(0xff0000, 2.5, 12);
glowLight.position.set(0, 3, 4);
scene.add(glowLight);
// ガメラの発光用ポイントライト
const gameraGlowLight = new THREE.PointLight(0xff5500, 0, 15);
scene.add(gameraGlowLight);
// 世代変数(デフォルト:平成 'heisei')
let currentGeneration = 'heisei';
// --- STREAMING_CHUNK:Loading materials for monsters... ---
// マテリアル定義(関数内で動的に切り替わりますが、基本定義)
let bodyMat, wingMembraneMat, eyeMat, goldHornMat, toothMat, skinMat, shellMat, plastronMat, tuskMat, clawMat;
function updateMaterials() {
if (currentGeneration === 'heisei') {
// 平成:G3 & ギャオス・ハイパー(暗く、重厚で金属的なシャープさ)
bodyMat = new THREE.MeshStandardMaterial({
color: 0x221d21, roughness: 0.45, metalness: 0.55, flatShading: true
});
wingMembraneMat = new THREE.MeshStandardMaterial({
color: 0x5a1217, roughness: 0.4, metalness: 0.3, side: THREE.DoubleSide, flatShading: true
});
eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0515 }); // 狂暴な赤い眼
goldHornMat = new THREE.MeshStandardMaterial({ color: 0x5a3e22, roughness: 0.25, metalness: 0.85 });
toothMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.2 });
skinMat = new THREE.MeshStandardMaterial({ color: 0x2b2e2a, roughness: 0.85, flatShading: true });
shellMat = new THREE.MeshStandardMaterial({ color: 0x1c1916, roughness: 0.9, flatShading: true });
plastronMat = new THREE.MeshStandardMaterial({
color: 0x473b30, emissive: 0xff3c00, emissiveIntensity: 0.25, roughness: 0.8, flatShading: true
});
tuskMat = new THREE.MeshStandardMaterial({ color: 0xeae5d8, roughness: 0.3 });
clawMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.3 });
} else {
// 昭和:1967年「大怪獣空中戦」(茶褐色〜オリーブ、レトロな着ぐるみのゴム感)
bodyMat = new THREE.MeshStandardMaterial({
color: 0x3d312d, roughness: 0.7, metalness: 0.1, flatShading: true // 昭和ギャオスの茶褐色スキン
});
wingMembraneMat = new THREE.MeshStandardMaterial({
color: 0x50282b, roughness: 0.6, metalness: 0.1, side: THREE.DoubleSide, flatShading: true // 紫がかった赤茶
});
eyeMat = new THREE.MeshBasicMaterial({ color: 0xffd000 }); // 昭和特有の光る黄色い眼
goldHornMat = new THREE.MeshStandardMaterial({ color: 0x403328, roughness: 0.6, metalness: 0.2 });
toothMat = new THREE.MeshStandardMaterial({ color: 0xf5f3ea, roughness: 0.4 });
skinMat = new THREE.MeshStandardMaterial({ color: 0x423830, roughness: 0.9, flatShading: true }); // 昭和ガメラの土気色の皮膚
shellMat = new THREE.MeshStandardMaterial({ color: 0x2b271d, roughness: 0.9, flatShading: true }); // ドーム状の平坦な緑褐色甲羅
plastronMat = new THREE.MeshStandardMaterial({
color: 0x7c694b, emissive: 0x332200, emissiveIntensity: 0.1, roughness: 0.9, flatShading: true // 黄土色の平滑な腹甲
});
tuskMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.4 }); // 大きな白い牙
clawMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.4 });
}
}
// --- STREAMING_CHUNK:Creating Single Gyaos model... ---
function createSingleGyaos() {
const gyaosInstance = new THREE.Group();
const isShowa = (currentGeneration === 'showa');
// 胴体
const torsoGeo = new THREE.CylinderGeometry(0.5, 0.15, 4.5, 5);
const torso = new THREE.Mesh(torsoGeo, bodyMat);
torso.rotation.x = Math.PI / 2.3;
torso.position.set(0, 0, -0.5);
torso.castShadow = true;
torso.receiveShadow = true;
gyaosInstance.add(torso);
// 蛇腹首
const neckGroup = new THREE.Group();
neckGroup.position.set(0, 0.7, 1.5);
const neckSegments = 4;
const neckParts = [];
for (let i = 0; i < neckSegments; i++) {
const size = 0.45 - (i * 0.05);
const neckSegGeo = new THREE.BoxGeometry(size * 1.2, size, 0.5);
const seg = new THREE.Mesh(neckSegGeo, bodyMat);
seg.position.set(0, i * 0.25, i * 0.35);
seg.rotation.x = -0.15;
seg.castShadow = true;
seg.receiveShadow = true;
neckGroup.add(seg);
neckParts.push(seg);
}
gyaosInstance.add(neckGroup);
// 頭部
const headGroup = new THREE.Group();
headGroup.position.set(0, neckSegments * 0.25 + 0.1, neckSegments * 0.35 + 0.2);
headGroup.rotation.x = 0.35;
// 上顎プレート
const headShape = new THREE.Shape();
if (isShowa) {
// 昭和:まな板のように平たく、幅が一定で後ろがそこまで深く裂けていない
headShape.moveTo(0, 1.8);
headShape.lineTo(0.6, 1.3);
headShape.lineTo(0.65, 0.4);
headShape.lineTo(0.7, -1.3); // 後ろがあまり広がらない
headShape.lineTo(0.3, -1.0);
headShape.lineTo(0, -0.6);
headShape.lineTo(-0.3, -1.0);
headShape.lineTo(-0.7, -1.3);
headShape.lineTo(-0.65, 0.4);
headShape.lineTo(-0.6, 1.3);
headShape.lineTo(0, 1.8);
} else {
// 平成:超鋭利で巨大なV字スリット型
headShape.moveTo(0, 2.0);
headShape.lineTo(0.5, 1.2);
headShape.lineTo(0.8, 0.4);
headShape.lineTo(1.2, -1.8);
headShape.lineTo(0.5, -1.2);
headShape.lineTo(0, -0.6);
headShape.lineTo(-0.5, -1.2);
headShape.lineTo(-1.2, -1.8);
headShape.lineTo(-0.8, 0.4);
headShape.lineTo(-0.5, 1.2);
headShape.lineTo(0, 2.0);
}
const extrudeSettings = {
steps: 1, depth: 0.15, bevelEnabled: true, bevelThickness: 0.04, bevelSize: 0.03, bevelSegments: 3
};
const headPlateGeo = new THREE.ExtrudeGeometry(headShape, extrudeSettings);
headPlateGeo.center();
headPlateGeo.rotateX(-Math.PI / 2);
headPlateGeo.rotateY(Math.PI);
const headPlate = new THREE.Mesh(headPlateGeo, bodyMat);
headPlate.castShadow = true;
headPlate.receiveShadow = true;
headGroup.add(headPlate);
// 下顎プレート
const lowerJawShape = new THREE.Shape();
lowerJawShape.moveTo(0, 1.95);
lowerJawShape.lineTo(0.48, 1.15);
lowerJawShape.lineTo(0.72, 0.35);
lowerJawShape.lineTo(0.85, -0.8);
lowerJawShape.lineTo(0, -0.5);
lowerJawShape.lineTo(-0.85, -0.8);
lowerJawShape.lineTo(-0.72, 0.35);
lowerJawShape.lineTo(-0.48, 1.15);
lowerJawShape.lineTo(0, 1.95);
const lowerJawExtSettings = {
steps: 1, depth: 0.12, bevelEnabled: true, bevelThickness: 0.03, bevelSize: 0.02, bevelSegments: 2
};
const lowerJawPlateGeo = new THREE.ExtrudeGeometry(lowerJawShape, lowerJawExtSettings);
lowerJawPlateGeo.center();
lowerJawPlateGeo.rotateX(-Math.PI / 2);
lowerJawPlateGeo.rotateY(Math.PI);
const lowerJawPlate = new THREE.Mesh(lowerJawPlateGeo, bodyMat);
lowerJawPlate.position.set(0, -0.18, 0.05);
lowerJawPlate.rotation.x = 0.12;
lowerJawPlate.castShadow = true;
lowerJawPlate.receiveShadow = true;
headGroup.add(lowerJawPlate);
// 口内(発光部)
const mouthInsideGeo = new THREE.BoxGeometry(1.0, 0.1, 2.4);
const mouthPos = mouthInsideGeo.attributes.position;
for (let i = 0; i < mouthPos.count; i++) {
let z = mouthPos.getZ(i);
let x = mouthPos.getX(i);
if (z > 0) mouthPos.setX(i, x * 0.4);
}
mouthInsideGeo.computeVertexNormals();
const mouthInsideMat = new THREE.MeshStandardMaterial({
color: 0x3a0305, emissive: isShowa ? 0xccaa00 : 0xff0000, emissiveIntensity: 1.6, roughness: 0.6
});
const mouthInside = new THREE.Mesh(mouthInsideGeo, mouthInsideMat);
mouthInside.position.set(0, -0.09, 0.5);
headGroup.add(mouthInside);
// 牙
const upperToothGeo = new THREE.ConeGeometry(0.04, 0.16, 4);
upperToothGeo.rotateX(Math.PI);
const upperToothPositions = [
{ x: 0.18, y: -0.09, z: 1.4 }, { x: -0.18, y: -0.09, z: 1.4 },
{ x: 0.35, y: -0.09, z: 0.9 }, { x: -0.35, y: -0.09, z: 0.9 }
];
upperToothPositions.forEach(pos => {
const tooth = new THREE.Mesh(upperToothGeo, toothMat);
tooth.position.set(pos.x, pos.y, pos.z);
headGroup.add(tooth);
});
// 目
const eyeWidth = isShowa ? 0.18 : 0.08;
const eyeLength = isShowa ? 0.3 : 0.7;
const eyeGeo = new THREE.BoxGeometry(eyeWidth, eyeWidth, eyeLength);
const leftEye = new THREE.Mesh(eyeGeo, eyeMat);
leftEye.position.set(0.65, 0.03, 0.7);
leftEye.rotation.y = 0.2; leftEye.rotation.z = isShowa ? 0 : -0.12;
const rightEye = leftEye.clone();
rightEye.position.x = -0.65;
rightEye.rotation.y = -0.2; rightEye.rotation.z = isShowa ? 0 : 0.12;
headGroup.add(leftEye, rightEye);
// 側頭ブレード
const hornGeo = new THREE.ConeGeometry(0.12, 1.8, 3);
hornGeo.scale(1, 0.1, 1);
const leftHorn = new THREE.Mesh(hornGeo, goldHornMat);
leftHorn.rotation.set(Math.PI / 2.2, 0, -Math.PI / 6);
leftHorn.position.set(0.9, 0.02, -1.0);
leftHorn.castShadow = true;
const rightHorn = leftHorn.clone();
rightHorn.position.x = -0.9; rightHorn.rotation.z = Math.PI / 6;
headGroup.add(leftHorn, rightHorn);
neckGroup.add(headGroup);
// --- STREAMING_CHUNK:Creating Ggyaos wings... ---
// 左主翼
const leftWingGroup = new THREE.Group();
leftWingGroup.position.set(0.5, 0.4, 0.2);
const leftWingBlade = new THREE.Group();
leftWingGroup.add(leftWingBlade);
const boneLength = isShowa ? 3.6 : 4.1;
const wingBone1Geo = new THREE.BoxGeometry(boneLength, 0.18, 0.3);
wingBone1Geo.translate(boneLength / 2, 0, 0);
const wingBone1 = new THREE.Mesh(wingBone1Geo, bodyMat);
wingBone1.rotation.y = 0.1;
wingBone1.castShadow = true;
leftWingBlade.add(wingBone1);
const wingBone2Geo = new THREE.BoxGeometry(3.5, 0.13, 0.22);
wingBone2Geo.translate(1.75, 0, 0);
const wingBone2 = new THREE.Mesh(wingBone2Geo, bodyMat);
wingBone2.position.set(boneLength - 0.2, 0, -0.4);
wingBone2.rotation.y = isShowa ? -0.15 : -0.26;
wingBone2.castShadow = true;
leftWingBlade.add(wingBone2);
const wingShape = new THREE.Shape();
if (isShowa) {
wingShape.moveTo(0, 0);
wingShape.lineTo(3.5, 0.3);
wingShape.lineTo(6.5, -0.4);
wingShape.lineTo(5.0, -2.2);
wingShape.lineTo(2.8, -1.6);
wingShape.lineTo(1.0, -2.1);
wingShape.lineTo(0, -0.6);
wingShape.lineTo(0, 0);
} else {
wingShape.moveTo(0, 0);
wingShape.lineTo(4.0, 0.4);
wingShape.lineTo(7.5, -0.5);
wingShape.lineTo(5.5, -2.8);
wingShape.lineTo(2.8, -1.8);
wingShape.lineTo(1.0, -2.5);
wingShape.lineTo(0, -0.6);
wingShape.lineTo(0, 0);
}
const wingMembraneGeo = new THREE.ShapeGeometry(wingShape);
const leftWingMembrane = new THREE.Mesh(wingMembraneGeo, wingMembraneMat);
leftWingMembrane.rotation.x = Math.PI / 2;
leftWingMembrane.castShadow = true;
leftWingMembrane.receiveShadow = true;
leftWingBlade.add(leftWingMembrane);
gyaosInstance.add(leftWingGroup);
// 右主翼
const rightWingGroup = new THREE.Group();
rightWingGroup.position.set(-0.5, 0.4, 0.2);
const rightWingBlade = new THREE.Group();
rightWingGroup.add(rightWingBlade);
const rightWingBone1 = new THREE.Mesh(wingBone1Geo, bodyMat);
rightWingBone1.rotation.y = -0.1;
rightWingBone1.scale.x = -1;
rightWingBone1.castShadow = true;
rightWingBlade.add(rightWingBone1);
const rightWingBone2 = new THREE.Mesh(wingBone2Geo, bodyMat);
rightWingBone2.position.set(-(boneLength - 0.2), 0, -0.4);
rightWingBone2.rotation.y = isShowa ? 0.15 : 0.26;
rightWingBone2.scale.x = -1;
rightWingBone2.castShadow = true;
rightWingBlade.add(rightWingBone2);
const rightWingMembrane = leftWingMembrane.clone();
rightWingMembrane.scale.x = -1;
rightWingBlade.add(rightWingMembrane);
gyaosInstance.add(rightWingGroup);
// 後肢・尾
const legL = new THREE.Group();
legL.position.set(0.4, -0.3, -2.0);
const thighGeo = new THREE.CylinderGeometry(0.2, 0.15, 1.5, 4);
const thigh = new THREE.Mesh(thighGeo, bodyMat);
thigh.rotation.x = Math.PI / 3;
thigh.castShadow = true;
legL.add(thigh);
gyaosInstance.add(legL);
const legR = legL.clone();
legR.position.x = -0.4;
gyaosInstance.add(legR);
const tailGroup = new THREE.Group();
tailGroup.position.set(0, -0.2, -2.4);
const tailBaseGeo = new THREE.BoxGeometry(0.4, 0.15, 1.8);
const tailBase = new THREE.Mesh(tailBaseGeo, bodyMat);
tailBase.position.set(0, 0, -0.6);
tailBase.rotation.x = -0.1;
tailBase.castShadow = true;
tailGroup.add(tailBase);
gyaosInstance.add(tailGroup);
gyaosInstance.scale.set(0.8, 0.8, 0.8);
return {
model: gyaosInstance,
leftWingGroup: leftWingGroup,
leftWingBlade: leftWingBlade,
rightWingGroup: rightWingGroup,
rightWingBlade: rightWingBlade,
neckParts: neckParts,
headGroup: headGroup,
lowerJawPlate: lowerJawPlate,
mouthInside: mouthInside,
animOffset: Math.random() * Math.PI * 2
};
}
// --- STREAMING_CHUNK:Creating Gamera model... ---
function createGamera() {
const gameraInstance = new THREE.Group();
const isShowa = (currentGeneration === 'showa');
// 1. 体幹
const bodyGeo = new THREE.SphereGeometry(1.8, 8, 8);
bodyGeo.scale(1.1, 1.3, 1.2);
const body = new THREE.Mesh(bodyGeo, skinMat);
body.position.set(0, 0, 0);
body.castShadow = true;
body.receiveShadow = true;
gameraInstance.add(body);
// 2. 腹甲
const plastronGroup = new THREE.Group();
for (let i = 0; i < 5; i++) {
const depth = isShowa ? 0.3 : 0.4;
const segmentGeo = new THREE.BoxGeometry(1.5 - i * 0.12, 0.3, depth);
const segment = new THREE.Mesh(segmentGeo, plastronMat);
segment.position.set(0, 0.7 - i * 0.35, 0.7 + i * 0.05);
segment.rotation.x = -0.12;
segment.castShadow = true;
plastronGroup.add(segment);
}
gameraInstance.add(plastronGroup);
// 3. 甲羅
const shellGroup = new THREE.Group();
shellGroup.position.set(0, 0.1, -0.6);
const shellBaseGeo = new THREE.SphereGeometry(1.9, 8, 8);
shellBaseGeo.scale(1.2, 1.4, 0.7);
const shellBase = new THREE.Mesh(shellBaseGeo, shellMat);
shellBase.castShadow = true;
shellGroup.add(shellBase);
// 甲羅の外縁
const rimGeo = new THREE.TorusGeometry(1.5, 0.15, 4, 8);
rimGeo.scale(1.15, 1.35, 0.5);
const rim = new THREE.Mesh(rimGeo, shellMat);
rim.position.set(0, 0, -0.2);
shellGroup.add(rim);
// 背骨の巨大トゲ
const spikeHeight = isShowa ? 0.3 : 0.75;
const spikeGeo = new THREE.ConeGeometry(0.2, spikeHeight, 4);
spikeGeo.rotateX(Math.PI / 2);
for (let i = 0; i < 6; i++) {
const spike = new THREE.Mesh(spikeGeo, shellMat);
const yPos = 1.2 - i * 0.48;
const zPos = -0.8 - (Math.sin(i * 0.5) * 0.22);
spike.position.set(0, yPos, zPos);
spike.rotation.x = -0.2 - i * 0.15;
spike.castShadow = true;
shellGroup.add(spike);
}
// 左右のサブトゲ
for (let i = 1; i < 4; i++) {
const leftSpike = new THREE.Mesh(spikeGeo, shellMat);
leftSpike.position.set(0.65, 0.8 - i * 0.45, -0.7);
leftSpike.rotation.set(-0.2, 0.3, -0.2);
const rightSpike = leftSpike.clone();
rightSpike.position.x = -0.65;
rightSpike.rotation.y = -0.3;
shellGroup.add(leftSpike, rightSpike);
}
gameraInstance.add(shellGroup);
// 手先足先
const clawGeo = new THREE.ConeGeometry(0.08, 0.35, 4);
clawGeo.rotateX(Math.PI / 2);
// 左腕
const armLGroup = new THREE.Group();
armLGroup.position.set(1.4, 0.3, 0.2);
const upperArmLGeo = new THREE.CylinderGeometry(0.4, 0.35, 1.2, 6);
const upperArmL = new THREE.Mesh(upperArmLGeo, skinMat);
upperArmL.rotation.z = -Math.PI / 6;
upperArmL.castShadow = true;
armLGroup.add(upperArmL);
const forearmLGeo = new THREE.CylinderGeometry(0.35, 0.25, 1.0, 6);
forearmLGeo.translate(0, -0.5, 0);
const forearmL = new THREE.Mesh(forearmLGeo, skinMat);
forearmL.position.set(0.3, -0.5, 0.1);
forearmL.rotation.z = -Math.PI / 12;
forearmL.castShadow = true;
armLGroup.add(forearmL);
for (let i = -1; i <= 1; i++) {
const claw = new THREE.Mesh(clawGeo, clawMat);
claw.position.set(0.3 + i * 0.12, -1.05, 0.25);
claw.rotation.y = i * 0.15;
claw.castShadow = true;
armLGroup.add(claw);
}
gameraInstance.add(armLGroup);
// 右腕
const armRGroup = new THREE.Group();
armRGroup.position.set(-1.4, 0.3, 0.2);
const upperArmR = new THREE.Mesh(upperArmLGeo, skinMat);
upperArmR.rotation.z = Math.PI / 6;
upperArmR.castShadow = true;
armRGroup.add(upperArmR);
const forearmR = new THREE.Mesh(forearmLGeo, skinMat);
forearmR.position.set(-0.3, -0.5, 0.1);
forearmR.rotation.z = Math.PI / 12;
forearmR.castShadow = true;
armRGroup.add(forearmR);
for (let i = -1; i <= 1; i++) {
const claw = new THREE.Mesh(clawGeo, clawMat);
claw.position.set(-0.3 + i * 0.12, -1.05, 0.25);
claw.rotation.y = i * 0.15;
claw.castShadow = true;
armRGroup.add(claw);
}
gameraInstance.add(armRGroup);
// 左脚
const legLGroup = new THREE.Group();
legLGroup.position.set(1.0, -0.8, -0.2);
const thighLGeo = new THREE.CylinderGeometry(0.5, 0.45, 1.2, 6);
const thighL = new THREE.Mesh(thighLGeo, skinMat);
thighL.castShadow = true;
legLGroup.add(thighL);
const footLGeo = new THREE.BoxGeometry(0.6, 0.4, 1.0);
const footL = new THREE.Mesh(footLGeo, skinMat);
footL.position.set(0, -0.6, 0.2);
footL.castShadow = true;
legLGroup.add(footL);
for (let i = -1; i <= 1; i++) {
const claw = new THREE.Mesh(clawGeo, clawMat);
claw.position.set(i * 0.15, -0.72, 0.7);
claw.castShadow = true;
legLGroup.add(claw);
}
gameraInstance.add(legLGroup);
// 右脚
const legRGroup = new THREE.Group();
legRGroup.position.set(-1.0, -0.8, -0.2);
const thighR = new THREE.Mesh(thighLGeo, skinMat);
thighR.castShadow = true;
legRGroup.add(thighR);
const footR = footL.clone();
legRGroup.add(footR);
for (let i = -1; i <= 1; i++) {
const claw = new THREE.Mesh(clawGeo, clawMat);
claw.position.set(i * 0.15, -0.72, 0.7);
claw.castShadow = true;
legRGroup.add(claw);
}
gameraInstance.add(legRGroup);
// 太い尻尾
const tailGroup = new THREE.Group();
tailGroup.position.set(0, -1.0, -1.2);
const tailGeo = new THREE.ConeGeometry(0.45, 2.0, 5);
tailGeo.rotateX(-Math.PI / 2.3);
const tail = new THREE.Mesh(tailGeo, skinMat);
tail.position.set(0, 0, -0.6);
tail.castShadow = true;
tailGroup.add(tail);
gameraInstance.add(tailGroup);
// 5. 頭部ユニット
const gameraHeadGroup = new THREE.Group();
gameraHeadGroup.position.set(0, 1.5, 0.6);
const neckGeo = new THREE.CylinderGeometry(0.5, 0.65, 0.9, 6);
neckGeo.rotateX(Math.PI / 6);
const neck = new THREE.Mesh(neckGeo, skinMat);
neck.position.set(0, -0.3, -0.1);
neck.castShadow = true;
gameraHeadGroup.add(neck);
// 上顎
const headMainGeo = new THREE.BoxGeometry(0.8, 0.5, 1.1);
const headMain = new THREE.Mesh(headMainGeo, skinMat);
headMain.position.set(0, 0.2, 0.3);
headMain.castShadow = true;
gameraHeadGroup.add(headMain);
const snoutGeo = new THREE.ConeGeometry(0.4, 0.6, 4);
snoutGeo.rotateX(Math.PI / 2);
const snout = new THREE.Mesh(snoutGeo, skinMat);
snout.position.set(0, 0.15, 0.85);
gameraHeadGroup.add(snout);
// 下顎
const gameraLowerJaw = new THREE.Group();
gameraLowerJaw.position.set(0, -0.1, 0.3);
const lowerJawGeo = new THREE.BoxGeometry(0.76, 0.2, 0.9);
const lowerJawMesh = new THREE.Mesh(lowerJawGeo, skinMat);
lowerJawMesh.position.set(0, -0.05, 0.25);
gameraLowerJaw.add(lowerJawMesh);
// 双牙
const tuskHeight = isShowa ? 0.7 : 0.6;
const tuskGeo = new THREE.ConeGeometry(0.12, tuskHeight, 4);
tuskGeo.rotateX(-Math.PI / 10);
const tuskL = new THREE.Mesh(tuskGeo, tuskMat);
tuskL.position.set(0.35, 0.18, 0.5);
tuskL.rotation.z = -0.15;
tuskL.castShadow = true;
const tuskR = tuskL.clone();
tuskR.position.x = -0.35;
tuskR.rotation.z = 0.15;
gameraLowerJaw.add(tuskL, tuskR);
gameraLowerJaw.rotation.x = 0.15;
gameraHeadGroup.add(gameraLowerJaw);
// 眼
if (isShowa) {
// 昭和特有の白目・黒目を持つ円形眼
const eyeOuterGeo = new THREE.SphereGeometry(0.13, 6, 6);
eyeOuterGeo.scale(1.0, 1.2, 0.5);
const eyeOuterL = new THREE.Mesh(eyeOuterGeo, new THREE.MeshBasicMaterial({ color: 0xeeeeaa }));
eyeOuterL.position.set(0.41, 0.25, 0.45);
eyeOuterL.rotation.y = 0.15;
const pupilGeo = new THREE.SphereGeometry(0.06, 5, 5);
const pupilL = new THREE.Mesh(pupilGeo, new THREE.MeshBasicMaterial({ color: 0x111111 }));
pupilL.position.set(0.42, 0.25, 0.49);
const eyeOuterR = eyeOuterL.clone();
eyeOuterR.position.x = -0.41;
eyeOuterR.rotation.y = -0.15;
const pupilR = pupilL.clone();
pupilR.position.x = -0.42;
gameraHeadGroup.add(eyeOuterL, pupilL, eyeOuterR, pupilR);
} else {
// 平成G3の細く鋭い眼
const gameraEyeMat = new THREE.MeshBasicMaterial({ color: 0xffd700 });
const eyeGeo = new THREE.BoxGeometry(0.08, 0.08, 0.2);
const eyeL = new THREE.Mesh(eyeGeo, gameraEyeMat);
eyeL.position.set(0.41, 0.25, 0.45);
eyeL.rotation.y = 0.15;
const eyeR = eyeL.clone();
eyeR.position.x = -0.41;
eyeR.rotation.y = -0.15;
gameraHeadGroup.add(eyeL, eyeR);
}
// 口内組織
const fireInsideGeo = new THREE.SphereGeometry(0.2, 5, 5);
const fireInsideMat = new THREE.MeshBasicMaterial({ color: 0xffaa00 });
const fireInside = new THREE.Mesh(fireInsideGeo, fireInsideMat);
fireInside.position.set(0, -0.05, 0.2);
gameraHeadGroup.add(fireInside);
gameraInstance.add(gameraHeadGroup);
gameraInstance.scale.set(2.4, 2.4, 2.4);
return {
model: gameraInstance,
headGroup: gameraHeadGroup,
lowerJaw: gameraLowerJaw,
plastron: plastronGroup,
fireInside: fireInside,
plastronMat: plastronMat,
armLGroup: armLGroup,
armRGroup: armRGroup
};
}
// --- STREAMING_CHUNK:Initializing flock and combat state... ---
let gyaosFlock = [];
let currentFlockSize = 9;
let baseSpread = 1.0;
let gamera = null;
// V字編隊の位置を計算
function getChevronPosition(index, spread) {
const isShowa = (currentGeneration === 'showa');
if (index === 0) return new THREE.Vector3(0, isShowa ? 5.5 : 3.0, -12);
const row = Math.floor((index + 1) / 2);
const isLeft = index % 2 === 1;
const xOffset = row * 3.8 * spread * (isLeft ? 1 : -1);
const yOffset = 3 + (Math.sin(index * 1.5) * 0.5);
const zOffset = -12 - (row * 3.5 * spread);
return new THREE.Vector3(xOffset, yOffset, zOffset);
}
// 世代変更やスライダー変更を反映して怪獣たちを再配置
function rebuildFlock() {
gyaosFlock.forEach(item => {
scene.remove(item.model);
});
gyaosFlock = [];
if (gamera) {
scene.remove(gamera.model);
}
updateMaterials();
const isShowa = (currentGeneration === 'showa');
if (isShowa) {
// 昭和モード: 大きな1匹だけを対峙させる
const gyaos = createSingleGyaos();
const pos = getChevronPosition(0, baseSpread);
gyaos.model.position.copy(pos);
gyaos.model.rotation.y = 0;
gyaos.model.scale.set(2.1, 2.1, 2.1); // 巨大化
scene.add(gyaos.model);
gyaosFlock.push(gyaos);
} else {
// 平成モード: スライダーに準拠した大群編成
for (let i = 0; i < currentFlockSize; i++) {
const gyaos = createSingleGyaos();
const pos = getChevronPosition(i, baseSpread);
gyaos.model.position.copy(pos);
gyaos.model.rotation.y = 0;
scene.add(gyaos.model);
gyaosFlock.push(gyaos);
}
}
gamera = createGamera();
gamera.model.position.set(0, 0.8, 14);
gamera.model.rotation.y = Math.PI; // 正面(ギャオス側)を向かせる
scene.add(gamera.model);
}
// --- STREAMING_CHUNK:Integrating background audio and audio controls... ---
// BGMオーディオ統合
const bgmUrl = "https://petitacode.web2050.jp/users/original/syunsuke/%E8%A3%82%E3%81%91%E3%82%8B%E5%A4%9C%E7%A9%BA.mp3";
const bgmAudio = new Audio(bgmUrl);
bgmAudio.loop = true;
bgmAudio.volume = 0.45; // 適切な音量バランス
let bgmPlaying = false;
const bgmBtn = document.getElementById('bgm-btn');
const bgmIcon = document.getElementById('bgm-icon');
const bgmText = document.getElementById('bgm-text');
function toggleBGM() {
if (bgmPlaying) {
bgmAudio.pause();
bgmPlaying = false;
bgmIcon.innerText = "🔇";
bgmText.innerText = "BGM OFF";
bgmBtn.classList.replace('bg-red-800', 'bg-red-950');
} else {
// 再生試行
bgmAudio.play().then(() => {
bgmPlaying = true;
bgmIcon.innerText = "🔊";
bgmText.innerText = "BGM ON";
bgmBtn.classList.replace('bg-red-950', 'bg-red-800');
}).catch(err => {
console.log("Audio play deferred due to browser interaction restrictions:", err);
});
}
}
bgmBtn.addEventListener('click', toggleBGM);
// 最初の画面インタラクション(クリック)時に、BGMを自動的に再生開始するトリガーを追加(快適な体験のため)
let initialInteractionDone = false;
window.addEventListener('mousedown', () => {
if (!initialInteractionDone) {
initialInteractionDone = true;
// 自動再生ポリシー対策:ONに初期設定して再生
bgmAudio.play().then(() => {
bgmPlaying = true;
bgmIcon.innerText = "🔊";
bgmText.innerText = "BGM ON";
bgmBtn.classList.replace('bg-red-950', 'bg-red-800');
}).catch(() => {});
}
});
// 大気粒子
const particleCount = 350;
const positions = new Float32Array(particleCount * 3);
const speeds = [];
for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 60;
positions[i * 3 + 1] = (Math.random() - 0.2) * 20;
positions[i * 3 + 2] = (Math.random() - 0.5) * 60;
speeds.push({
x: (Math.random() - 0.5) * 0.02,
y: 0.015 + Math.random() * 0.03,
z: (Math.random() - 0.5) * 0.02
});
}
const particlesGeo = new THREE.BufferGeometry();
particlesGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const particleMat = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.12,
transparent: true,
opacity: 0.4
});
const ambientParticles = new THREE.Points(particlesGeo, particleMat);
scene.add(ambientParticles);
const gridHelper = new THREE.GridHelper(100, 50, 0x7799bb, 0xccddee);
gridHelper.position.y = -3.5;
scene.add(gridHelper);
const shadowFloorGeo = new THREE.PlaneGeometry(200, 200);
const shadowFloorMat = new THREE.ShadowMaterial({ opacity: 0.15 });
const shadowFloor = new THREE.Mesh(shadowFloorGeo, shadowFloorMat);
shadowFloor.rotation.x = -Math.PI / 2;
shadowFloor.position.y = -3.52;
shadowFloor.receiveShadow = true;
scene.add(shadowFloor);
// --- STREAMING_CHUNK:Setting up combat effects... ---
// 光線エフェクト
let isBeaming = false;
let activeBeams = [];
let beamProgress = 0;
const maxBeamLength = 50;
let isGameraBeaming = false;
let gameraBeamMesh = null;
let gameraBeamProgress = 0;
// 干渉爆発パーティクル
let clashParticles = null;
const clashCount = 65;
const clashPositions = new Float32Array(clashCount * 3);
const clashVelocities = [];
const clashGeo = new THREE.BufferGeometry();
clashGeo.setAttribute('position', new THREE.BufferAttribute(clashPositions, 3));
const clashMat = new THREE.PointsMaterial({
color: 0xffaa00,
size: 0.45,
transparent: true,
opacity: 0,
blending: THREE.AdditiveBlending
});
clashParticles = new THREE.Points(clashGeo, clashMat);
scene.add(clashParticles);
for (let i = 0; i < clashCount; i++) {
clashVelocities.push({
x: (Math.random() - 0.5) * 12,
y: (Math.random() - 0.5) * 12,
z: (Math.random() - 0.5) * 12
});
}
function triggerClashExplosion() {
clashMat.opacity = 1.0;
const clashPosAttr = clashParticles.geometry.attributes.position;
for (let i = 0; i < clashCount; i++) {
clashPosAttr.setXYZ(i, (Math.random() - 0.5) * 2, 3 + (Math.random() - 0.5) * 2, 0);
}
clashPosAttr.needsUpdate = true;
}
// 1. ギャオス超音波メス射撃
function createAllUltrasonicBeams() {
if (isBeaming) return;
isBeaming = true;
const flash = document.getElementById('flash-overlay');
flash.style.backgroundColor = currentGeneration === 'showa' ? 'rgba(255, 221, 51, 0.4)' : 'rgba(239, 68, 68, 0.4)';
setTimeout(() => { flash.style.backgroundColor = 'rgba(0, 0, 0, 0)'; }, 150);
activeBeams = [];
const isShowa = (currentGeneration === 'showa');
const beamColorHex = isShowa ? 0xffdd33 : 0xff1e56;
gyaosFlock.forEach((gyaos, idx) => {
// 昭和の巨大ギャオスは発射するビームも1.8倍太くする
const scaleFactor = isShowa ? 1.8 : 1.0;
const beamGeo = new THREE.CylinderGeometry(0.10 * scaleFactor, 0.025 * scaleFactor, 1, 8);
beamGeo.rotateX(Math.PI / 2);
beamGeo.translate(0, 0, 0.5);
const beamMat = new THREE.MeshBasicMaterial({
color: beamColorHex,
transparent: true,
opacity: 0.95
});
const beamMesh = new THREE.Mesh(beamGeo, beamMat);
const lightPower = idx === 0 ? 8 : 2;
const beamLight = new THREE.PointLight(beamColorHex, lightPower, 15);
beamMesh.add(beamLight);
const muzzleWorldPos = new THREE.Vector3();
gyaos.mouthInside.getWorldPosition(muzzleWorldPos);
const forwardOffset = new THREE.Vector3(0, 0, 1.0);
forwardOffset.applyQuaternion(gyaos.headGroup.getWorldQuaternion(new THREE.Quaternion()));
muzzleWorldPos.add(forwardOffset);
beamMesh.position.copy(muzzleWorldPos);
const headDirection = new THREE.Vector3(0, -0.05, 1).normalize();
headDirection.applyQuaternion(gyaos.headGroup.getWorldQuaternion(new THREE.Quaternion()));
const targetPoint = new THREE.Vector3().copy(beamMesh.position).add(headDirection);
beamMesh.lookAt(targetPoint);
scene.add(beamMesh);
activeBeams.push({
mesh: beamMesh,
gyaos: gyaos,
dir: headDirection,
startPos: muzzleWorldPos.clone()
});
});
beamProgress = 0;
if (isGameraBeaming) triggerClashExplosion();
}
// 2. ガメラの火球発射
function createGameraFireball() {
if (isGameraBeaming) return;
isGameraBeaming = true;
const flash = document.getElementById('flash-overlay');
flash.style.backgroundColor = 'rgba(245, 158, 11, 0.3)';
setTimeout(() => { flash.style.backgroundColor = 'rgba(0, 0, 0, 0)'; }, 150);
const beamRadius = currentGeneration === 'showa' ? 0.55 : 0.35;
const beamColorHex = currentGeneration === 'showa' ? 0xff8800 : 0xff5500;
const beamGeo = new THREE.CylinderGeometry(beamRadius, 0.15, 1, 10);
beamGeo.rotateX(Math.PI / 2);
beamGeo.translate(0, 0, 0.5);
const beamMat = new THREE.MeshBasicMaterial({
color: beamColorHex,
transparent: true,
opacity: 0.98
});
gameraBeamMesh = new THREE.Mesh(beamGeo, beamMat);
const gameraLight = new THREE.PointLight(beamColorHex, 10, 20);
gameraBeamMesh.add(gameraLight);
const mouthWorldPos = new THREE.Vector3();
gamera.fireInside.getWorldPosition(mouthWorldPos);
gameraBeamMesh.position.copy(mouthWorldPos);
const targetDir = new THREE.Vector3(0, -0.05, 1).normalize();
targetDir.applyQuaternion(gamera.headGroup.getWorldQuaternion(new THREE.Quaternion()));
const targetPoint = new THREE.Vector3().copy(gameraBeamMesh.position).add(targetDir);
gameraBeamMesh.lookAt(targetPoint);
scene.add(gameraBeamMesh);
gameraBeamProgress = 0;
if (isBeaming) triggerClashExplosion();
}
// --- STREAMING_CHUNK:Binding UI controls and event listeners... ---
document.getElementById('flock-slider').addEventListener('input', (e) => {
currentFlockSize = parseInt(e.target.value);
document.getElementById('flock-val').innerText = currentFlockSize + ' 匹';
rebuildFlock();
});
document.getElementById('spread-slider').addEventListener('input', (e) => {
baseSpread = parseFloat(e.target.value);
document.getElementById('spread-val').innerText = baseSpread.toFixed(1) + 'x';
gyaosFlock.forEach((gyaos, idx) => {
const targetPos = getChevronPosition(idx, baseSpread);
gyaos.model.position.x = targetPos.x;
gyaos.model.position.z = targetPos.z;
});
});
const genHeiseiBtn = document.getElementById('gen-heisei-btn');
const genShowaBtn = document.getElementById('gen-showa-btn');
const flockControl = document.getElementById('flock-control-container');
const spreadControl = document.getElementById('spread-control-container');
function setGeneration(gen) {
currentGeneration = gen;
if (gen === 'heisei') {
genHeiseiBtn.classList.replace('bg-zinc-800', 'bg-red-600');
genHeiseiBtn.classList.replace('text-gray-300', 'text-white');
genHeiseiBtn.classList.remove('hover:bg-zinc-700');
genShowaBtn.classList.replace('bg-red-600', 'bg-zinc-800');
genShowaBtn.classList.replace('text-white', 'text-gray-300');
genShowaBtn.classList.add('hover:bg-zinc-700');
flockControl.classList.remove('hidden');
spreadControl.classList.remove('hidden');
document.getElementById('title-main').innerHTML = `GAMERA vs GYAOS-HYPER <span id="title-sub" class="text-[10px] text-red-400 font-sans block tracking-wider font-bold">ガメラ 対 ギャオス・ハイパー 大群シミュレーター</span>`;
document.getElementById('title-desc').innerText = 'ガメラ3 邪神〈イリス〉覚醒 (1999) より';
document.getElementById('beam-btn').innerText = "超音波メス 一斉掃射";
clashMat.color.setHex(0xffaa00);
} else {
genShowaBtn.classList.replace('bg-zinc-800', 'bg-red-600');
genShowaBtn.classList.replace('text-gray-300', 'text-white');
genShowaBtn.classList.remove('hover:bg-zinc-700');
genHeiseiBtn.classList.replace('bg-red-600', 'bg-zinc-800');
genHeiseiBtn.classList.replace('text-white', 'text-gray-300');
genHeiseiBtn.classList.add('hover:bg-zinc-700');
flockControl.classList.add('hidden');
spreadControl.classList.add('hidden');
document.getElementById('title-main').innerHTML = `GAMERA vs GYAOS - SHOWA <span id="title-sub" class="text-[10px] text-yellow-400 font-sans block tracking-wider font-bold">大怪獣空中戦 ガメラ対ギャオス シミュレーター</span>`;
document.getElementById('title-desc').innerText = '昭和大映特撮シリーズ (1967) より';
document.getElementById('beam-btn').innerText = "超音波メス 発射";
clashMat.color.setHex(0xffdd33);
}
rebuildFlock();
}
genHeiseiBtn.addEventListener('click', () => setGeneration('heisei'));
genShowaBtn.addEventListener('click', () => setGeneration('showa'));
const dayBtn = document.getElementById('env-day-btn');
const studioBtn = document.getElementById('env-studio-btn');
const nightBtn = document.getElementById('env-night-btn');
function setEnvironment(mode) {
[dayBtn, studioBtn, nightBtn].forEach(b => {
b.classList.replace('bg-red-600', 'bg-zinc-800');
b.classList.replace('text-white', 'text-gray-300');
b.classList.add('hover:bg-zinc-700');
});
if (mode === 'day') {
dayBtn.classList.replace('bg-zinc-800', 'bg-red-600');
dayBtn.classList.replace('text-gray-300', 'text-white');
dayBtn.classList.remove('hover:bg-zinc-700');
scene.background = colorDay;
scene.fog.color = colorDay;
scene.fog.density = 0.012;
ambientLight.color.setHex(0xffffff);
ambientLight.intensity = 1.2;
mainLight.color.setHex(0xffffff);
mainLight.intensity = 1.8;
mainLight.position.set(20, 40, 10);
fillLight.color.setHex(0xddf0ff);
fillLight.intensity = 0.8;
particleMat.color.setHex(0xffffff);
particleMat.opacity = 0.4;
particleMat.size = 0.12;
gridHelper.material.color.setHex(0x7799bb);
}
else if (mode === 'studio') {
studioBtn.classList.replace('bg-zinc-800', 'bg-red-600');
studioBtn.classList.replace('text-gray-300', 'text-white');
studioBtn.classList.remove('hover:bg-zinc-700');
scene.background = colorStudio;
scene.fog.color = colorStudio;
scene.fog.density = 0.015;
ambientLight.color.setHex(0xffffff);
ambientLight.intensity = 0.9;
mainLight.color.setHex(0xffffff);
mainLight.intensity = 2.0;
mainLight.position.set(5, 40, 5);
fillLight.color.setHex(0xaaaaaa);
fillLight.intensity = 0.5;
particleMat.color.setHex(0x888888);
particleMat.opacity = 0.2;
particleMat.size = 0.08;
gridHelper.material.color.setHex(0xaaaaaa);
}
else if (mode === 'night') {
nightBtn.classList.replace('bg-zinc-800', 'bg-red-600');
nightBtn.classList.replace('text-gray-300', 'text-white');
nightBtn.classList.remove('hover:bg-zinc-700');
scene.background = colorNight;
scene.fog.color = colorNight;
scene.fog.density = 0.02;
ambientLight.color.setHex(0x220c0c);
ambientLight.intensity = 1.6;
mainLight.color.setHex(0xaaccff);
mainLight.intensity = 1.2;
mainLight.position.set(10, 30, 10);
fillLight.color.setHex(0xff3300);
fillLight.intensity = 1.4;
particleMat.color.setHex(0xff4400);
particleMat.opacity = 0.8;
particleMat.size = 0.15;
gridHelper.material.color.setHex(0x550c0c);
}
}
dayBtn.addEventListener('click', () => setEnvironment('day'));
studioBtn.addEventListener('click', () => setEnvironment('studio'));
nightBtn.addEventListener('click', () => setEnvironment('night'));
document.getElementById('beam-btn').addEventListener('click', createAllUltrasonicBeams);
document.getElementById('gamera-btn').addEventListener('click', createGameraFireball);
window.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
createAllUltrasonicBeams();
}
if (e.code === 'Enter') {
e.preventDefault();
createGameraFireball();
}
});
// --- STREAMING_CHUNK:Animating characters and handling calculations... ---
const clock = new THREE.Clock();
let speedMultiplier = 1.0;
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
const time = clock.getElapsedTime();
// 1. ギャオスのアニメーション
gyaosFlock.forEach((gyaos, idx) => {
const individualTime = time + gyaos.animOffset;
const chevronPos = getChevronPosition(idx, baseSpread);
// 自律的なホバリング
const hoverY = chevronPos.y + Math.sin(individualTime * 2.2) * 0.4;
gyaos.model.position.y = THREE.MathUtils.lerp(gyaos.model.position.y, hoverY, 0.05);
gyaos.model.position.z = THREE.MathUtils.lerp(
gyaos.model.position.z,
chevronPos.z + Math.cos(individualTime * 1.5) * 0.15,
0.05
);
gyaos.model.rotation.x = Math.sin(individualTime * 0.9) * 0.03 - 0.05;
gyaos.model.rotation.z = Math.cos(individualTime * 0.5) * 0.05;
// 羽ばたき
const flapSpeed = individualTime * 4.5 * speedMultiplier;
const flapAngle = Math.sin(flapSpeed) * 0.35;
gyaos.leftWingGroup.rotation.z = flapAngle;
gyaos.leftWingBlade.rotation.y = -0.2 + Math.sin(flapSpeed - 0.4) * 0.08;
gyaos.rightWingGroup.rotation.z = -flapAngle;
gyaos.rightWingBlade.rotation.y = 0.2 - Math.sin(flapSpeed - 0.4) * 0.08;
// 首
gyaos.neckParts.forEach((part, sIdx) => {
part.rotation.z = Math.sin(individualTime * 1.3 + sIdx * 0.3) * 0.035;
part.rotation.y = Math.cos(individualTime * 0.9 + sIdx * 0.2) * 0.02;
});
gyaos.headGroup.rotation.y = Math.sin(individualTime * 1.4) * 0.05;
// 通常時の口
if (!isBeaming) {
gyaos.lowerJawPlate.rotation.x = THREE.MathUtils.lerp(
gyaos.lowerJawPlate.rotation.x,
0.10 + Math.sin(individualTime * 1.0) * 0.03,
0.05
);
}
});
// 2. ガメラのアニメーション(重厚な呼吸と巨獣感)
if (gamera) {
const pulseIntensity = currentGeneration === 'showa'
? 0.1 + Math.sin(time * 1.5) * 0.05
: 0.25 + Math.sin(time * 3.0) * 0.15;
gamera.plastronMat.emissiveIntensity = isGameraBeaming ? 3.0 : pulseIntensity;
gamera.model.position.y = 0.8 + Math.sin(time * 1.5) * 0.04;
if (gamera.armLGroup) gamera.armLGroup.rotation.z = -Math.PI / 6 + Math.sin(time * 1.5) * 0.025;
if (gamera.armRGroup) gamera.armRGroup.rotation.z = Math.PI / 6 - Math.sin(time * 1.5) * 0.025;
gamera.headGroup.rotation.y = Math.sin(time * 0.5) * 0.03;
gamera.headGroup.rotation.x = 0.12 + Math.sin(time * 0.7) * 0.02;
}
// --- STREAMING_CHUNK:Processing beam weapon updates and collisions... ---
// 3. 環境パーティクル
const particlePosAttr = ambientParticles.geometry.attributes.position;
for (let i = 0; i < particleCount; i++) {
let y = particlePosAttr.getY(i);
let x = particlePosAttr.getX(i);
let z = particlePosAttr.getZ(i);
y += speeds[i].y;
x += speeds[i].x;
z += speeds[i].z;
if (y > 20) {
y = -3.5;
x = (Math.random() - 0.5) * 60;
z = (Math.random() - 0.5) * 60;
}
particlePosAttr.setY(i, y);
particlePosAttr.setX(i, x);
particlePosAttr.setZ(i, z);
}
particlePosAttr.needsUpdate = true;
// 4. 超音波メス照射
const baseHeadZ = 4 * 0.35 + 0.2; // 首の接続点
let targetHeadZ = baseHeadZ;
if (isBeaming && activeBeams.length > 0) {
beamProgress += delta * 125.0;
if (beamProgress < maxBeamLength) {
activeBeams.forEach(beam => {
const currentMaxZ = isGameraBeaming ? Math.min(beamProgress, 14) : beamProgress;
beam.mesh.scale.set(1, 1, currentMaxZ);
const currentMuzzlePos = new THREE.Vector3();
beam.gyaos.mouthInside.getWorldPosition(currentMuzzlePos);
const forwardOffset = new THREE.Vector3(0, 0, 1.0);
forwardOffset.applyQuaternion(beam.gyaos.headGroup.getWorldQuaternion(new THREE.Quaternion()));
currentMuzzlePos.add(forwardOffset);
beam.mesh.position.copy(currentMuzzlePos);
beam.gyaos.lowerJawPlate.rotation.x = THREE.MathUtils.lerp(beam.gyaos.lowerJawPlate.rotation.x, 0.48, 0.25);
});
targetHeadZ = baseHeadZ - 0.4; // 反動
glowLight.intensity = 8;
} else {
activeBeams.forEach(beam => {
scene.remove(beam.mesh);
beam.mesh.geometry.dispose();
});
activeBeams = [];
isBeaming = false;
glowLight.intensity = 2.5;
}
} else if (!isBeaming) {
gyaosFlock.forEach(gyaos => {
gyaos.lowerJawPlate.rotation.x = THREE.MathUtils.lerp(gyaos.lowerJawPlate.rotation.x, 0.12, 0.1);
});
}
gyaosFlock.forEach(gyaos => {
gyaos.headGroup.position.z = THREE.MathUtils.lerp(gyaos.headGroup.position.z, targetHeadZ, 0.15);
});
// 5. ガメラ火球
if (gamera && isGameraBeaming && gameraBeamMesh) {
gameraBeamProgress += delta * 125.0;
if (gameraBeamProgress < maxBeamLength) {
const currentMaxZ = isBeaming ? Math.min(gameraBeamProgress, 14) : gameraBeamProgress;
gameraBeamMesh.scale.set(1, 1, currentMaxZ);
const mouthPos = new THREE.Vector3();
gamera.fireInside.getWorldPosition(mouthPos);
gameraBeamMesh.position.copy(mouthPos);
const targetDir = new THREE.Vector3(0, -0.05, 1).normalize();
targetDir.applyQuaternion(gamera.headGroup.getWorldQuaternion(new THREE.Quaternion()));
const targetPoint = new THREE.Vector3().copy(mouthPos).add(targetDir);
gameraBeamMesh.lookAt(targetPoint);
gamera.lowerJaw.rotation.x = THREE.MathUtils.lerp(gamera.lowerJaw.rotation.x, 0.65, 0.25);
gameraGlowLight.intensity = 15;
} else {
scene.remove(gameraBeamMesh);
gameraBeamMesh.geometry.dispose();
gameraBeamMesh = null;
isGameraBeaming = false;
gameraGlowLight.intensity = 0;
}
} else if (gamera) {
gamera.lowerJaw.rotation.x = THREE.MathUtils.lerp(gamera.lowerJaw.rotation.x, 0.15, 0.1);
}
// 6. 空中激突
if (isBeaming && isGameraBeaming) {
clashMat.opacity = THREE.MathUtils.lerp(clashMat.opacity, 1.0, 0.1);
const clashPosAttr = clashParticles.geometry.attributes.position;
for (let i = 0; i < clashCount; i++) {
let cx = clashPosAttr.getX(i);
let cy = clashPosAttr.getY(i);
let cz = clashPosAttr.getZ(i);
cx += clashVelocities[i].x * delta;
cy += clashVelocities[i].y * delta;
cz += clashVelocities[i].z * delta;
if (Math.abs(cx) > 8 || Math.abs(cy) > 8) {
cx = (Math.random() - 0.5) * 1.5;
cy = 3 + (Math.random() - 0.5) * 1.5;
cz = (Math.random() - 0.5) * 1.5;
}
clashPosAttr.setXYZ(i, cx, cy, cz);
}
clashPosAttr.needsUpdate = true;
} else {
clashMat.opacity = THREE.MathUtils.lerp(clashMat.opacity, 0, 0.2);
}
controls.update();
renderer.render(scene, camera);
}
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 初期ロード
setEnvironment('day');
rebuildFlock();
animate();
</script>
</body>
</html>
大迫力のバトルシーン