LLM-test/test/code/3/mimo-v2-flash.html

1083 lines
46 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>星际文明探索者 | Star Civilization Explorer</title>
<!-- CDN 引入依赖库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.min.js"></script>
<style>
/* --- 核心样式变量 --- */
:root {
--bg-deep: #05070a;
--primary-glow: #00f3ff;
--secondary-glow: #bd00ff;
--glass-bg: rgba(10, 15, 30, 0.65);
--glass-border: rgba(255, 255, 255, 0.1);
--text-main: #e0e6ed;
--font-stack: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
body {
background-color: var(--bg-deep);
color: var(--text-main);
font-family: var(--font-stack);
overflow: hidden;
height: 100vh;
width: 100vw;
}
/* --- 画布层 --- */
#game-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
/* --- UI 层 --- */
#ui-layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: none; /* 让点击穿透到 Canvas (用于飞船控制) */
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 250px 1fr 250px;
padding: 20px;
gap: 20px;
}
/* 通用玻璃拟态面板 */
.glass-panel {
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 12px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
pointer-events: auto;
position: relative;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.glass-panel::before {
content: '';
position: absolute;
top: 0; left: 0; width: 100%; height: 2px;
background: linear-gradient(90deg, transparent, var(--primary-glow), transparent);
opacity: 0.5;
}
.glass-panel:hover {
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
transform: translateY(-2px);
}
/* --- HUD 布局 --- */
.hud-top-left { grid-row: 1; grid-column: 1; display: flex; flex-direction: column; gap: 10px; }
.hud-center { grid-row: 1 / 3; grid-column: 2; position: relative; display: flex; justify-content: center; pointer-events: none; }
.hud-bottom-left { grid-row: 3; grid-column: 1; }
.hud-top-right { grid-row: 1; grid-column: 3; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
.hud-bottom-right { grid-row: 3; grid-column: 3; }
.hud-overlay { grid-row: 1 / 4; grid-column: 1 / 4; display: flex; justify-content: center; align-items: center; z-index: 100; }
/* 指标模块 */
.stat-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; font-size: 14px; letter-spacing: 1px; text-transform: uppercase; }
.stat-label { opacity: 0.7; }
.stat-value { color: var(--primary-glow); font-weight: bold; text-shadow: 0 0 5px var(--primary-glow); }
.progress-bar { width: 100%; height: 4px; background: rgba(255,255,255,0.1); margin-top: 4px; border-radius: 2px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--primary-glow); width: 0%; transition: width 0.3s; box-shadow: 0 0 8px var(--primary-glow); }
/* --- 按钮样式 --- */
.btn {
background: rgba(0, 243, 255, 0.1);
border: 1px solid var(--primary-glow);
color: var(--primary-glow);
padding: 10px 20px;
font-family: inherit;
font-weight: bold;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 2px;
transition: all 0.2s;
font-size: 12px;
width: 100%;
margin-bottom: 8px;
}
.btn:hover { background: var(--primary-glow); color: #000; box-shadow: 0 0 15px var(--primary-glow); }
.btn:disabled { opacity: 0.3; cursor: not-allowed; border-color: #555; color: #555; background: transparent; box-shadow: none; }
.btn-upgrade { border-color: var(--secondary-glow); color: var(--secondary-glow); background: rgba(189, 0, 255, 0.1); }
.btn-upgrade:hover { background: var(--secondary-glow); color: #fff; box-shadow: 0 0 15px var(--secondary-glow); }
/* --- 启动/菜单屏幕 --- */
#start-screen {
background: rgba(5, 7, 10, 0.95);
z-index: 200;
flex-direction: column;
text-align: center;
gap: 30px;
}
.title {
font-size: 4rem;
font-weight: 900;
background: linear-gradient(135deg, #fff, var(--primary-glow), var(--secondary-glow));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(0, 243, 255, 0.3);
letter-spacing: 4px;
}
.subtitle { font-size: 1.2rem; opacity: 0.8; color: var(--primary-glow); }
.controls-hint {
margin-top: 20px;
font-size: 0.9rem;
color: #889;
line-height: 1.6;
}
.key {
display: inline-block;
padding: 2px 6px;
border: 1px solid #555;
border-radius: 4px;
background: rgba(255,255,255,0.1);
font-family: monospace;
color: #fff;
}
/* --- 通知系统 --- */
#notifications {
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
pointer-events: none;
width: 400px;
}
.notify-msg {
background: rgba(0, 0, 0, 0.7);
border: 1px solid var(--primary-glow);
color: #fff;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
opacity: 0;
transform: translateY(20px);
text-align: center;
}
/* --- 战斗提示 --- */
#combat-alert {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 3rem;
color: #ff3333;
text-shadow: 0 0 20px #ff0000;
font-weight: bold;
opacity: 0;
pointer-events: none;
letter-spacing: 5px;
border: 4px solid #ff3333;
padding: 20px 40px;
background: rgba(50, 0, 0, 0.5);
display: none;
}
/* --- 移动端适配 --- */
@media (max-width: 768px) {
.title { font-size: 2.5rem; }
#ui-layer { display: flex; flex-direction: column; padding: 10px; }
.glass-panel { margin-bottom: 5px; padding: 5px; }
.hud-center { display: none; } /* 隐藏中心瞄准镜,节省空间 */
.controls-hint { font-size: 0.8rem; }
}
</style>
</head>
<body>
<!-- 3D Canvas -->
<canvas id="game-canvas"></canvas>
<!-- UI Layer -->
<div id="ui-layer">
<!-- Left Stats -->
<div class="hud-top-left">
<div class="glass-panel">
<div class="stat-row">
<span class="stat-label">Shields</span>
<span class="stat-value" id="val-shield">100%</span>
</div>
<div class="progress-bar"><div class="progress-fill" id="bar-shield" style="width: 100%"></div></div>
</div>
<div class="glass-panel">
<div class="stat-row">
<span class="stat-label">Hull</span>
<span class="stat-value" id="val-hull" style="color: #ff5555">100%</span>
</div>
<div class="progress-bar"><div class="progress-fill" id="bar-hull" style="width: 100%; background: #ff5555; box-shadow: 0 0 8px #ff5555;"></div></div>
</div>
</div>
<!-- Center (Notifications & Alert) -->
<div class="hud-center">
<div id="notifications"></div>
<div id="combat-alert">WARNING: HOSTILES</div>
</div>
<!-- Right Resources & Upgrades -->
<div class="hud-top-right">
<div class="glass-panel" style="width: 100%">
<div class="stat-row">
<span class="stat-label">Resources</span>
<span class="stat-value" id="val-res" style="color: #ffd700">0</span>
</div>
<div class="stat-row" style="font-size: 10px; opacity: 0.5;">
<span>Crystals</span>
<span id="val-crystals">0</span>
</div>
</div>
<div class="glass-panel" style="width: 100%; margin-top: 10px;">
<div style="padding: 10px; text-align: right; font-size: 12px; color: var(--primary-glow); letter-spacing: 2px; border-bottom: 1px solid rgba(255,255,255,0.1); margin-bottom: 5px;">ENGINEERING</div>
<button class="btn btn-upgrade" id="btn-up-engine" onclick="game.upgrade('engine')">Engines (100)</button>
<button class="btn btn-upgrade" id="btn-up-shield" onclick="game.upgrade('shield')">Shields (150)</button>
<button class="btn btn-upgrade" id="btn-up-laser" onclick="game.upgrade('laser')">Lasers (200)</button>
</div>
</div>
<!-- Bottom Status -->
<div class="hud-bottom-left glass-panel">
<div class="stat-row">
<span class="stat-label">Speed</span>
<span class="stat-value" id="val-speed">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Status</span>
<span class="stat-value" id="val-status" style="color: #aaa;">Drifting</span>
</div>
</div>
<!-- Map Controls (Simple) -->
<div class="hud-bottom-right glass-panel">
<div class="stat-row" style="justify-content: flex-end; gap: 15px;">
<span style="font-size: 10px; opacity: 0.5;">ZOOM: SCROLL</span>
<span style="font-size: 10px; opacity: 0.5;">AIM: MOUSE</span>
</div>
</div>
</div>
<!-- Start Screen Overlay -->
<div id="start-screen" class="glass-panel hud-overlay">
<div class="title">STAR EXPLORER</div>
<div class="subtitle">INTERSTELLAR CIVILIZATION LOG</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn" onclick="initGame()" style="width: 200px; font-size: 1.2rem; padding: 15px;">Initialize</button>
</div>
<div class="controls-hint">
<p><span class="key">W</span> <span class="key">A</span> <span class="key">S</span> <span class="key">D</span><span class="key">方向键</span> : 推进 / 移动</p>
<p><span class="key">MOUSE</span> : 瞄准</p>
<p><span class="key">LEFT CLICK</span> : 发射激光</p>
<p><span class="key">SCROLL</span> : 调整视角</p>
<p style="margin-top: 10px; color: #fff;">靠近星球进行扫描,收集资源。避开红色敌对目标,或摧毁它们。</p>
</div>
</div>
<script>
/*
* ------------------------------------------------------------------
* AUDIO SYSTEM (Procedural Synthesis via Web Audio API)
* ------------------------------------------------------------------
*/
const AudioSys = {
ctx: null,
init: function() {
if (!this.ctx) {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.playAmbient();
}
},
playTone: function(freq, type, duration, vol = 0.1) {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
gain.gain.setValueAtTime(vol, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + duration);
},
playLaser: function() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(800, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, this.ctx.currentTime + 0.15);
gain.gain.setValueAtTime(0.05, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.2);
},
playExplosion: function() {
this.playTone(100, 'square', 0.4, 0.15);
this.playTone(50, 'sawtooth', 0.5, 0.2);
},
playCollect: function() {
this.playTone(1200, 'sine', 0.1, 0.1);
setTimeout(() => this.playTone(1800, 'sine', 0.15, 0.1), 80);
},
playAlert: function() {
this.playTone(400, 'square', 0.3, 0.1);
setTimeout(() => this.playTone(350, 'square', 0.3, 0.1), 300);
},
playAmbient: function() {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.value = 40;
gain.gain.value = 0.02;
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
}
};
/*
* ------------------------------------------------------------------
* GAME LOGIC & STATE
* ------------------------------------------------------------------
*/
const Game = {
state: 'start',
score: 0,
crystals: 0,
hull: 100,
maxHull: 100,
shield: 100,
maxShield: 100,
resources: 0,
scrap: 0,
speedLevel: 1,
shieldLevel: 1,
laserLevel: 1,
enemies: [],
projectiles: [],
particles: [],
planets: [],
inCombat: false,
upgrade: function(type) {
let cost = 0;
if (type === 'engine') {
cost = 100;
if (this.resources >= cost) {
this.resources -= cost;
this.speedLevel++;
this.notify("Engines Upgraded!", "cyan");
AudioSys.playTone(600, 'triangle', 0.2);
} else { this.notify("Insufficient Resources", "red"); }
} else if (type === 'shield') {
cost = 150;
if (this.resources >= cost) {
this.resources -= cost;
this.maxShield += 20;
this.shield = this.maxShield;
this.shieldLevel++;
this.notify("Shields Reinforced!", "magenta");
AudioSys.playTone(800, 'triangle', 0.2);
} else { this.notify("Insufficient Resources", "red"); }
} else if (type === 'laser') {
cost = 200;
if (this.resources >= cost) {
this.resources -= cost;
this.laserLevel++;
this.notify("Weapons Upgraded!", "orange");
AudioSys.playTone(1000, 'triangle', 0.2);
} else { this.notify("Insufficient Resources", "red"); }
}
this.updateUI();
},
notify: function(msg, color = "white") {
const container = document.getElementById('notifications');
const el = document.createElement('div');
el.className = 'notify-msg';
el.innerText = msg;
el.style.borderColor = color;
el.style.color = color;
container.appendChild(el);
gsap.to(el, { opacity: 1, y: 0, duration: 0.5, ease: 'back.out' });
gsap.to(el, { opacity: 0, y: -20, duration: 0.5, delay: 2, onComplete: () => el.remove() });
},
start: function() {
this.state = 'playing';
this.hull = 100;
this.shield = 100;
this.resources = 0;
this.crystals = 0;
this.enemies = [];
this.projectiles = [];
this.planets = [];
this.inCombat = false;
Renderer.camera.position.set(0, 20, 30);
Renderer.camera.lookAt(0, 0, 0);
Renderer.scene.children = Renderer.scene.children.filter(c => c.name === 'StarSystem' || c.name === 'AmbientLight' || c.name === 'SunLight');
World.generate();
document.getElementById('start-screen').style.display = 'none';
this.updateUI();
AudioSys.init();
AudioSys.playTone(440, 'sine', 0.5);
},
gameOver: function() {
this.state = 'gameover';
this.notify("CRITICAL FAILURE", "red");
AudioSys.playExplosion();
setTimeout(() => {
document.getElementById('start-screen').style.display = 'flex';
document.querySelector('#start-screen .title').innerText = "SIGNAL LOST";
document.querySelector('#start-screen .subtitle').innerText = `Final Score: ${Math.floor(this.score)}`;
document.querySelector('#start-screen button').innerText = "Reboot System";
}, 2000);
},
updateUI: function() {
document.getElementById('val-shield').innerText = Math.floor(this.shield) + '%';
document.getElementById('bar-shield').style.width = (this.shield / this.maxShield * 100) + '%';
document.getElementById('val-hull').innerText = Math.floor(this.hull) + '%';
document.getElementById('bar-hull').style.width = (this.hull / this.maxHull * 100) + '%';
document.getElementById('val-res').innerText = Math.floor(this.resources);
document.getElementById('val-crystals').innerText = this.crystals;
document.getElementById('btn-up-engine').disabled = this.resources < 100;
document.getElementById('btn-up-shield').disabled = this.resources < 150;
document.getElementById('btn-up-laser').disabled = this.resources < 200;
if (Physics.velocity) {
const speed = Physics.velocity.length();
document.getElementById('val-speed').innerText = speed.toFixed(1);
}
},
updateCombatState: function(hasHostiles) {
if (hasHostiles && !this.inCombat) {
this.inCombat = true;
const el = document.getElementById('combat-alert');
el.style.display = 'block';
gsap.to(el, { opacity: 1, duration: 0.5, yoyo: true, repeat: 3 });
AudioSys.playAlert();
} else if (!hasHostiles && this.inCombat) {
this.inCombat = false;
const el = document.getElementById('combat-alert');
gsap.to(el, { opacity: 0, duration: 0.5, onComplete: () => { el.style.display = 'none'; } });
}
},
updateStatus: function(text) {
const el = document.getElementById('val-status');
if (el) el.innerText = text;
}
};
/*
* ------------------------------------------------------------------
* PHYSICS
* ------------------------------------------------------------------
*/
const Physics = {
velocity: new THREE.Vector3(),
rotation: 0,
thrust: 0,
drag: 0.98,
rotateSpeed: 0.03,
update: function(dt) {
if (Game.state !== 'playing') return;
this.velocity.multiplyScalar(this.drag);
if (this.thrust > 0) {
const accel = 0.5 + (Game.speedLevel * 0.1);
const vector = new THREE.Vector3(0, 0, -1).applyAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation);
this.velocity.add(vector.multiplyScalar(this.thrust * accel * dt));
Renderer.spawnThrusterParticles();
if (Math.random() > 0.8) AudioSys.playTone(100 + Math.random()*50, 'sawtooth', 0.05, 0.02);
}
if (this.movingLeft) this.rotation += this.rotateSpeed;
if (this.movingRight) this.rotation -= this.rotateSpeed;
if (Renderer.playerMesh) {
Renderer.playerMesh.rotation.y = this.rotation;
Renderer.playerMesh.position.add(this.velocity.clone().multiplyScalar(1));
}
if (Renderer.camera && Renderer.playerMesh) {
Renderer.camera.position.x += (Renderer.playerMesh.position.x - Renderer.camera.position.x) * 0.05;
Renderer.camera.position.z += (Renderer.playerMesh.position.z - Renderer.camera.position.z) * 0.05;
Renderer.camera.position.y += (20 + Math.abs(this.velocity.length()) - Renderer.camera.position.y) * 0.05;
Renderer.camera.lookAt(Renderer.playerMesh.position);
}
if (Game.enemies) Game.enemies.forEach(e => e.update(dt));
if (Game.projectiles) Game.projectiles.forEach(p => p.update(dt));
if (Game.particles) Game.particles.forEach(p => p.update(dt));
Game.enemies = Game.enemies.filter(e => e.active);
Game.projectiles = Game.projectiles.filter(p => p.active);
Game.particles = Game.particles.filter(p => p.active);
if (World) World.spawnLogic();
Game.updateUI();
}
};
/*
* ------------------------------------------------------------------
* RENDERER (Three.js Setup)
* ------------------------------------------------------------------
*/
const Renderer = {
scene: null,
camera: null,
renderer: null,
playerMesh: null,
raycaster: new THREE.Raycaster(),
mouse: new THREE.Vector2(),
init: function() {
const canvas = document.getElementById('game-canvas');
this.scene = new THREE.Scene();
this.scene.fog = new THREE.FogExp2(0x05070a, 0.002);
this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
this.camera.position.set(0, 20, 30);
this.renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: false });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
ambientLight.name = 'AmbientLight';
this.scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xffffff, 1);
sunLight.position.set(100, 100, 50);
sunLight.name = 'SunLight';
this.scene.add(sunLight);
this.createStarField();
this.createPlayerShip();
window.addEventListener('resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
});
window.addEventListener('keydown', (e) => {
if (Game.state !== 'playing') return;
switch(e.key.toLowerCase()) {
case 'w': case 'arrowup': Physics.thrust = 1; Game.updateStatus("Accelerating"); break;
case 's': case 'arrowdown': Physics.thrust = -0.5; Game.updateStatus("Retro Thrusters"); break;
case 'a': case 'arrowleft': Physics.movingLeft = true; break;
case 'd': case 'arrowright': Physics.movingRight = true; break;
}
});
window.addEventListener('keyup', (e) => {
if (Game.state !== 'playing') return;
switch(e.key.toLowerCase()) {
case 'w': case 'arrowup': case 's': case 'arrowdown':
Physics.thrust = 0; Game.updateStatus("Drifting"); break;
case 'a': case 'arrowleft': Physics.movingLeft = false; break;
case 'd': case 'arrowright': Physics.movingRight = false; break;
}
});
window.addEventListener('mousemove', (e) => {
this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
});
window.addEventListener('mousedown', (e) => {
if (Game.state !== 'playing') return;
if (e.button === 0) this.fireWeapon();
});
window.addEventListener('wheel', (e) => {
if (Game.state !== 'playing') return;
const delta = e.deltaY * 0.05;
this.camera.position.z += delta;
this.camera.position.y += delta * 0.5;
});
},
createPlayerShip: function() {
const geometry = new THREE.ConeGeometry(1, 3, 8);
const material = new THREE.MeshPhongMaterial({
color: 0x222222,
emissive: 0x00f3ff,
emissiveIntensity: 0.2,
flatShading: true
});
this.playerMesh = new THREE.Mesh(geometry, material);
this.playerMesh.rotation.x = Math.PI / 2;
this.scene.add(this.playerMesh);
this.aimPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
},
fireWeapon: function() {
if (!this.playerMesh) return;
this.raycaster.setFromCamera(this.mouse, this.camera);
const target = new THREE.Vector3();
this.raycaster.ray.intersectPlane(this.aimPlane, target);
if (!target) return;
const start = this.playerMesh.position.clone();
const dir = new THREE.Vector3().subVectors(target, start).normalize();
dir.x += (Math.random() - 0.5) * (0.1 - (Game.laserLevel * 0.01));
dir.z += (Math.random() - 0.5) * (0.1 - (Game.laserLevel * 0.01));
const p = {
mesh: null,
active: true,
velocity: dir.multiplyScalar(2 + (Game.laserLevel * 0.2)),
life: 100,
isPlayer: true,
update: function(dt) {
if (!this.active) return;
this.mesh.position.add(this.velocity);
this.life--;
if (this.life <= 0) {
this.active = false;
this.cleanup();
return;
}
for (let e of Game.enemies) {
if (!e.active) continue;
if (this.mesh.position.distanceTo(e.mesh.position) < 3) {
this.active = false;
this.cleanup();
e.takeDamage(20 + (Game.laserLevel * 5));
Game.resources += 5;
Game.score += 10;
Renderer.spawnExplosion(this.mesh.position, 0x00f3ff, 10);
AudioSys.playTone(600 + Math.random()*200, 'square', 0.1, 0.05);
break;
}
}
for (let pl of Game.planets) {
if (this.mesh.position.distanceTo(pl.mesh.position) < pl.radius + 1) {
this.active = false;
this.cleanup();
Renderer.spawnExplosion(this.mesh.position, 0xffaa00, 5);
break;
}
}
},
cleanup: function() {
if (this.mesh) {
this.mesh.visible = false;
Renderer.scene.remove(this.mesh);
}
}
};
const geo = new THREE.BoxGeometry(0.2, 0.2, 2);
const mat = new THREE.MeshBasicMaterial({ color: 0x00f3ff });
p.mesh = new THREE.Mesh(geo, mat);
p.mesh.position.copy(start);
p.mesh.lookAt(target);
Renderer.scene.add(p.mesh);
Game.projectiles.push(p);
AudioSys.playLaser();
Physics.velocity.add(new THREE.Vector3(0,0,1).applyAxisAngle(new THREE.Vector3(0,1,0), Physics.rotation)).multiplyScalar(-0.1);
},
spawnThrusterParticles: function() {
if (Math.random() > 0.5) return;
const p = {
mesh: null,
active: true,
life: 20 + Math.random() * 10,
velocity: new THREE.Vector3((Math.random()-0.5)*0.1, 0, 1).applyAxisAngle(new THREE.Vector3(0,1,0), Physics.rotation),
update: function(dt) {
this.mesh.position.add(this.velocity);
this.life--;
this.mesh.material.opacity = this.life / 30;
if (this.life <= 0) {
this.active = false;
this.mesh.visible = false;
Renderer.scene.remove(this.mesh);
}
}
};
const geo = new THREE.PlaneGeometry(0.5, 0.5);
const mat = new THREE.MeshBasicMaterial({ color: 0xff5500, transparent: true, opacity: 0.8, side: THREE.DoubleSide });
p.mesh = new THREE.Mesh(geo, mat);
p.mesh.position.copy(Renderer.playerMesh.position);
p.mesh.position.x -= Math.sin(Physics.rotation) * 0.5;
p.mesh.position.z -= Math.cos(Physics.rotation) * 0.5;
p.mesh.position.add(new THREE.Vector3((Math.random()-0.5)*0.5, 0, (Math.random()-0.5)*0.5));
p.mesh.lookAt(Renderer.camera.position);
Renderer.scene.add(p.mesh);
Game.particles.push(p);
},
spawnExplosion: function(pos, color, count) {
if (count > 5) AudioSys.playExplosion();
for(let i=0; i<count; i++) {
const p = {
mesh: null,
active: true,
life: 30 + Math.random()*20,
velocity: new THREE.Vector3((Math.random()-0.5), (Math.random()-0.5), (Math.random()-0.5)).normalize().multiplyScalar(0.3),
update: function(dt) {
this.mesh.position.add(this.velocity);
this.life--;
if (this.life < 10) this.mesh.scale.multiplyScalar(0.8);
this.mesh.material.opacity = this.life / 50;
if (this.life <= 0) {
this.active = false;
this.mesh.visible = false;
Renderer.scene.remove(this.mesh);
}
}
};
const geo = new THREE.TetrahedronGeometry(0.3);
const mat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 1 });
p.mesh = new THREE.Mesh(geo, mat);
p.mesh.position.copy(pos);
Renderer.scene.add(p.mesh);
Game.particles.push(p);
}
},
createStarField: function() {
const geometry = new THREE.BufferGeometry();
const count = 1500;
const positions = new Float32Array(count * 3);
for(let i=0; i<count*3; i++) {
positions[i] = (Math.random() - 0.5) * 1000;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({ size: 1.5, color: 0xffffff, transparent: true, opacity: 0.8 });
const stars = new THREE.Points(geometry, material);
stars.name = 'StarSystem';
this.scene.add(stars);
},
render: function() {
requestAnimationFrame(this.render.bind(this));
if (Game.state === 'playing') {
if (Physics.thrust !== 0 || Physics.movingLeft || Physics.movingRight) {
Physics.update(1);
}
}
const stars = this.scene.getObjectByName('StarSystem');
if (stars) {
stars.rotation.y += 0.0002;
stars.rotation.x = Math.sin(Date.now() * 0.0001) * 0.05;
}
if (Renderer.playerMesh && Renderer.camera && Game.state === 'playing') {
let shake = 0;
if (Game.hull < 30) shake = (Math.random()-0.5) * 0.5;
Renderer.camera.position.x += (Renderer.playerMesh.position.x + shake - Renderer.camera.position.x) * 0.1;
Renderer.camera.position.z += (Renderer.playerMesh.position.z + shake - Renderer.camera.position.z) * 0.1;
Renderer.camera.position.y += ((20 + Math.abs(Physics.velocity.length()*2)) - Renderer.camera.position.y) * 0.05;
}
this.renderer.render(this.scene, this.camera);
}
};
/*
* ------------------------------------------------------------------
* WORLD GENERATION
* ------------------------------------------------------------------
*/
const World = {
spawnTimer: 0,
generate: function() {
this.spawnCluster(new THREE.Vector3(0, 0, -50));
this.spawnCluster(new THREE.Vector3(100, 0, -150));
this.spawnCluster(new THREE.Vector3(-80, 0, -200));
},
spawnCluster: function(center) {
for (let i = 0; i < 3; i++) {
const radius = 5 + Math.random() * 10;
const pos = center.clone().add(new THREE.Vector3((Math.random()-0.5)*60, (Math.random()-0.5)*10, (Math.random()-0.5)*60));
this.createPlanet(pos, radius);
}
},
createPlanet: function(pos, radius) {
const geometry = new THREE.IcosahedronGeometry(radius, 1);
const color = Math.random() > 0.7 ? 0x00f3ff : (Math.random() > 0.5 ? 0xbd00ff : 0xffaa00);
const material = new THREE.MeshPhongMaterial({
color: color,
emissive: color,
emissiveIntensity: 0.1,
flatShading: true,
shininess: 30
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(pos);
if (Math.random() > 0.6) {
const ringGeo = new THREE.RingGeometry(radius * 1.4, radius * 1.8, 32);
const ringMat = new THREE.MeshBasicMaterial({ color: color, side: THREE.DoubleSide, transparent: true, opacity: 0.3 });
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.rotation.x = Math.PI / 2;
mesh.add(ring);
}
Renderer.scene.add(mesh);
const planetObj = {
mesh: mesh,
radius: radius,
type: Math.random() > 0.3 ? 'resource' : 'empty',
active: true,
collected: false,
angle: Math.random() * Math.PI * 2,
speed: (Math.random() - 0.5) * 0.005,
radiusOrbit: Math.random() * 10
};
Game.planets.push(planetObj);
},
spawnLogic: function() {
this.spawnTimer++;
if (this.spawnTimer > 300) {
this.spawnTimer = 0;
const dist = Renderer.playerMesh.position.length();
if (dist > 50) {
this.spawnEnemy();
}
}
},
spawnEnemy: function() {
if (Game.enemies.length > 6) return;
const geo = new THREE.OctahedronGeometry(1.5, 0);
const mat = new THREE.MeshPhongMaterial({ color: 0xff3333, emissive: 0xff0000, emissiveIntensity: 0.5 });
const mesh = new THREE.Mesh(geo, mat);
const angle = Math.random() * Math.PI * 2;
const dist = 20 + Math.random() * 20;
const pos = Renderer.playerMesh.position.clone().add(new THREE.Vector3(Math.cos(angle)*dist, (Math.random()-0.5)*5, Math.sin(angle)*dist));
mesh.position.copy(pos);
Renderer.scene.add(mesh);
const enemy = {
mesh: mesh,
active: true,
hp: 50,
type: 'drone',
fireTimer: 0,
update: function(dt) {
if (!this.active) return;
const dir = new THREE.Vector3().subVectors(Renderer.playerMesh.position, this.mesh.position).normalize();
this.mesh.position.add(dir.multiplyScalar(0.1));
this.mesh.lookAt(Renderer.playerMesh.position);
this.mesh.rotation.x += 0.05;
this.fireTimer++;
if (this.fireTimer > 180) {
this.fireTimer = 0;
this.shoot();
}
const d = this.mesh.position.distanceTo(Renderer.playerMesh.position);
if (d < 2) {
Game.shield -= 5;
Game.notify("IMPACT WARNING", "red");
this.takeDamage(100);
Renderer.spawnExplosion(this.mesh.position, 0xff3333, 20);
}
},
shoot: function() {
const p = {
mesh: null,
active: true,
life: 100,
velocity: new THREE.Vector3().subVectors(Renderer.playerMesh.position, this.mesh.position).normalize().multiplyScalar(0.4),
update: function(dt) {
this.mesh.position.add(this.velocity);
this.life--;
if (this.life <= 0) {
this.active = false;
this.mesh.visible = false;
Renderer.scene.remove(this.mesh);
}
if (this.mesh.position.distanceTo(Renderer.playerMesh.position) < 1.5) {
Game.shield -= 10;
Game.updateStatus("UNDER FIRE");
this.active = false;
this.mesh.visible = false;
Renderer.scene.remove(this.mesh);
Renderer.spawnExplosion(this.mesh.position, 0xffaa00, 5);
AudioSys.playTone(150, 'square', 0.2);
}
}
};
const geo = new THREE.BoxGeometry(0.3, 0.3, 1);
const mat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
p.mesh = new THREE.Mesh(geo, mat);
p.mesh.position.copy(this.mesh.position);
p.mesh.lookAt(Renderer.playerMesh.position);
Renderer.scene.add(p.mesh);
Game.projectiles.push(p);
AudioSys.playTone(200, 'sawtooth', 0.1, 0.05);
},
takeDamage: function(amount) {
this.hp -= amount;
if (this.hp <= 0) {
this.active = false;
this.mesh.visible = false;
Renderer.scene.remove(this.mesh);
Game.score += 50;
Game.resources += 20;
Game.crystals += (Math.random() > 0.8 ? 1 : 0);
Game.updateUI();
if (Math.random() > 0.8) {
Game.shield = Math.min(Game.maxShield, Game.shield + 10);
Game.notify("Shield Bonus", "cyan");
}
}
}
};
Game.enemies.push(enemy);
}
};
// Planet Collision & Collection Logic
setInterval(() => {
if (Game.state !== 'playing' || !Renderer.playerMesh) return;
let nearPlanet = false;
const playerPos = Renderer.playerMesh.position;
Game.planets.forEach(p => {
if (!p.active) return;
p.mesh.rotation.y += 0.005;
const d = playerPos.distanceTo(p.mesh.position);
if (d < p.radius + 5) {
nearPlanet = true;
if (!p.collected) {
p.collected = true;
p.mesh.material.emissiveIntensity = 0;
const gain = Math.floor(p.radius * 2);
Game.resources += gain;
Game.notify(`Scanned: +${gain}`, "cyan");
AudioSys.playCollect();
Renderer.spawnExplosion(p.mesh.position, 0x00f3ff, 15);
}
}
});
if (nearPlanet && Game.state === 'playing') Game.updateStatus("Scanning...");
else if (Game.state === 'playing' && Physics.thrust === 0) Game.updateStatus("Drifting");
// Check Health
if (Game.shield <= 0) {
Game.shield = 0;
Game.hull -= 0.2;
}
if (Game.hull <= 0) {
Game.hull = 0;
Game.gameOver();
}
// Combat Alert
const hasHostiles = Game.enemies.length > 0;
Game.updateCombatState(hasHostiles);
}, 100);
// Init
function initGame() {
if (!Renderer.renderer) {
Renderer.init();
Game.start();
Renderer.render();
} else {
Game.start();
}
}
console.log("%c SYSTEM READY ", "background: #00f3ff; color: #000; font-size: 20px; font-weight: bold;");
</script>
</body>
</html>