LLM-test/test/code/3/glm-4.7.html

1248 lines
54 KiB
HTML
Raw Normal View History

<!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>星际文明探索者 | Interstellar Explorer</title>
<style>
/* --- 基础样式 --- */
:root {
--primary-color: #00f3ff;
--secondary-color: #ff0055;
--bg-glass: rgba(12, 12, 28, 0.75);
--border-glass: 1px solid rgba(255, 255, 255, 0.15);
--text-glow: 0 0 10px rgba(0, 243, 255, 0.5);
--font-main: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
body {
margin: 0;
overflow: hidden;
background-color: #050510;
font-family: var(--font-main);
color: white;
user-select: none;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
outline: none;
}
/* --- UI 通用组件 --- */
.ui-layer {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 10;
}
.hud-panel {
background: var(--bg-glass);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: var(--border-glass);
border-radius: 8px;
padding: 15px;
pointer-events: auto;
transition: all 0.3s ease;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
}
.btn {
background: rgba(0, 243, 255, 0.1);
border: 1px solid var(--primary-color);
color: var(--primary-color);
padding: 8px 16px;
cursor: pointer;
font-family: var(--font-main);
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s;
font-weight: bold;
font-size: 14px;
}
.btn:hover {
background: var(--primary-color);
color: #000;
box-shadow: 0 0 15px var(--primary-color);
}
.btn-danger {
border-color: var(--secondary-color);
color: var(--secondary-color);
background: rgba(255, 0, 85, 0.1);
}
.btn-danger:hover {
background: var(--secondary-color);
color: white;
box-shadow: 0 0 15px var(--secondary-color);
}
/* --- HUD 布局 --- */
#hud-top-left {
position: absolute;
top: 20px;
left: 20px;
width: 240px;
}
#hud-top-right {
position: absolute;
top: 20px;
right: 20px;
text-align: right;
pointer-events: none;
}
#radar-container {
position: relative;
pointer-events: auto;
display: inline-block;
}
#hud-center-msg {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-size: 24px;
text-shadow: var(--text-glow);
opacity: 0;
transition: opacity 0.5s;
pointer-events: none;
}
#hud-bottom-center {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 20px;
align-items: center;
}
/* --- 状态条 --- */
.stat-row { margin-bottom: 8px; }
.stat-label {
font-size: 11px;
color: #aaa;
display: flex;
justify-content: space-between;
text-transform: uppercase;
letter-spacing: 1px;
}
.progress-bar {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
overflow: hidden;
margin-top: 4px;
}
.progress-fill { height: 100%; width: 100%; transition: width 0.3s ease-out; }
.hp-fill { background: var(--secondary-color); box-shadow: 0 0 8px var(--secondary-color); }
.shield-fill { background: var(--primary-color); box-shadow: 0 0 8px var(--primary-color); }
.xp-fill { background: #ffcc00; }
/* --- 模态框 --- */
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
backdrop-filter: blur(5px);
}
.modal-overlay.active {
opacity: 1;
pointer-events: auto;
}
.modal-content {
width: 600px;
max-width: 90%;
background: rgba(16, 20, 35, 0.95);
border: 1px solid var(--primary-color);
padding: 30px;
border-radius: 12px;
box-shadow: 0 0 40px rgba(0, 243, 255, 0.15);
position: relative;
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.modal-overlay.active .modal-content { transform: scale(1); }
h2 { margin-top: 0; color: var(--primary-color); text-transform: uppercase; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 10px; letter-spacing: 2px; font-size: 24px;}
.upgrade-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 15px;
margin: 25px 0;
}
.upgrade-card {
background: rgba(255,255,255,0.03);
padding: 15px 10px;
border-radius: 8px;
text-align: center;
cursor: pointer;
border: 1px solid transparent;
transition: all 0.2s;
}
.upgrade-card:hover {
border-color: var(--primary-color);
background: rgba(0, 243, 255, 0.05);
transform: translateY(-2px);
}
.upgrade-icon { font-size: 32px; display: block; margin-bottom: 10px; }
.upgrade-name { font-size: 14px; font-weight: bold; color: #eee; }
.upgrade-level { font-size: 12px; color: #666; margin-top: 5px; }
.upgrade-cost { font-size: 12px; color: #ffcc00; margin-top: 5px; display: block;}
/* --- 扫描面板细节 --- */
.scan-details {
display: grid;
grid-template-columns: auto 1fr;
gap: 8px 20px;
margin: 20px 0;
font-size: 14px;
background: rgba(0,0,0,0.3);
padding: 15px;
border-radius: 6px;
}
.scan-label { color: #aaa; }
.scan-value { color: #fff; text-align: right; font-family: 'Courier New', monospace; }
/* --- 交互提示 --- */
.interaction-key {
display: inline-block;
border: 1px solid rgba(255,255,255,0.4);
border-radius: 4px;
padding: 2px 6px;
font-size: 12px;
background: rgba(255,255,255,0.1);
margin-right: 5px;
font-family: monospace;
min-width: 20px;
text-align: center;
}
/* --- 准星 --- */
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 5;
display: flex;
align-items: center;
justify-content: center;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.6);
transition: all 0.2s;
}
#crosshair::before { width: 2px; height: 100%; }
#crosshair::after { height: 2px; width: 100%; }
#crosshair.hovering::before, #crosshair.hovering::after { background: var(--primary-color); height: 120%; width: 120%; }
#crosshair-hover-text {
position: absolute;
top: -25px;
color: var(--primary-color);
font-size: 12px;
opacity: 0;
transition: opacity 0.2s;
text-shadow: 0 0 5px black;
}
#crosshair.hovering #crosshair-hover-text { opacity: 1; }
/* --- 加载遮罩 --- */
#loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
z-index: 9999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: opacity 1s ease-out;
}
.loader-title { font-size: 32px; letter-spacing: 8px; font-weight: 300; margin-bottom: 20px; text-shadow: 0 0 20px var(--primary-color); }
.loader-bar { width: 200px; height: 2px; background: #333; position: relative; overflow: hidden; }
.loader-progress {
position: absolute; left: 0; top: 0; height: 100%; width: 0%;
background: var(--primary-color);
box-shadow: 0 0 10px var(--primary-color);
animation: load 1.5s ease-in-out forwards;
}
@keyframes load { 100% { width: 100%; } }
</style>
<!-- 引入外部库 (CDN) -->
<!-- Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- 修复:使用经典的 cannon.js (0.6.2) 确保兼容全局变量 CANNON -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>
<!-- GSAP (动画) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
</head>
<body>
<!-- 加载界面 -->
<div id="loader">
<div class="loader-title">INTERSTELLAR</div>
<div style="font-size: 12px; color: #666; margin-bottom: 20px;">初始化物理引擎...</div>
<div class="loader-bar"><div class="loader-progress"></div></div>
</div>
<!-- 3D 画布 -->
<div id="canvas-container"></div>
<!-- 准星 -->
<div id="crosshair"><div id="crosshair-hover-text">按 F 扫描</div></div>
<!-- UI 层 -->
<div class="ui-layer" id="ui-layer">
<!-- 左上角:状态 -->
<div id="hud-top-left" class="hud-panel">
<div class="stat-row">
<div class="stat-label"><span>结构完整性 (HP)</span> <span id="hp-val">100%</span></div>
<div class="progress-bar"><div id="hp-bar" class="progress-fill hp-fill"></div></div>
</div>
<div class="stat-row">
<div class="stat-label"><span>能量护盾</span> <span id="shield-val">100%</span></div>
<div class="progress-bar"><div id="shield-bar" class="progress-fill shield-fill"></div></div>
</div>
<div class="stat-row" style="margin-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); padding-top:10px;">
<div class="stat-label"><span>资源储备</span></div>
<div style="font-size: 24px; color: #ffcc00; text-shadow: 0 0 10px rgba(255, 204, 0, 0.4);">
<span id="res-count">0</span> <span style="font-size:12px; color:#888;">UNIT</span>
</div>
</div>
</div>
<!-- 右上角:雷达 -->
<div id="hud-top-right">
<div id="radar-container" class="hud-panel" style="padding: 5px; display:inline-block;">
<div id="radar-display" style="width: 120px; height: 120px; border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; position: relative; background: rgba(0,0,0,0.5); overflow: hidden;">
<!-- 中心玩家 -->
<div style="position: absolute; top: 50%; left: 50%; width: 6px; height: 6px; background: var(--primary-color); border-radius: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 5px var(--primary-color);"></div>
<!-- 动态雷达点将通过 JS 插入 -->
</div>
</div>
<div style="margin-top: 10px; font-size: 12px; color: #888; font-family: monospace;">POS: <span id="coords">0, 0</span></div>
</div>
<!-- 中央消息 -->
<div id="hud-center-msg">
<div id="msg-title" style="font-size: 32px; font-weight: bold; margin-bottom: 10px;">警告</div>
<div id="msg-desc">侦测到敌对信号</div>
</div>
<!-- 底部中央:控制 -->
<div id="hud-bottom-center" class="hud-panel" style="padding: 10px 25px;">
<button class="btn" onclick="Game.openUpgradeMenu()">升级系统 [U]</button>
<div style="font-size: 11px; color: #666; margin-left: 10px;">
W/A/S/D 移动 • 鼠标 瞄准 • 左键 射击 • F 交互
</div>
</div>
</div>
<!-- 扫描结果模态框 -->
<div id="scan-modal" class="modal-overlay">
<div class="modal-content">
<h2>行星分析报告</h2>
<div class="scan-details">
<span class="scan-label">行星代号:</span>
<span class="scan-value" id="scan-name">Kepler-186f</span>
<span class="scan-label">大气成分:</span>
<span class="scan-value" id="scan-atmo">氮气 78%, 氧气 21%</span>
<span class="scan-label">表面温度:</span>
<span class="scan-value" id="scan-temp">-12°C</span>
<span class="scan-label">宜居指数:</span>
<span class="scan-value" style="color: #0f0;" id="scan-habit">Class A</span>
<span class="scan-label">资源潜力:</span>
<span class="scan-value" style="color: #ffcc00;" id="scan-res">高 (晶体矿)</span>
</div>
<div style="text-align: right; margin-top: 25px;">
<button class="btn btn-danger" onclick="Game.closeScanModal()">忽略</button>
<button class="btn" onclick="Game.collectPlanetResources()">采集样本 [花费时间]</button>
</div>
</div>
</div>
<!-- 升级菜单模态框 -->
<div id="upgrade-modal" class="modal-overlay">
<div class="modal-content">
<h2>飞船改装中心</h2>
<div style="text-align: center; margin-bottom: 20px;">
<span style="font-size: 14px; color: #888;">当前资源储备: </span>
<span id="upgrade-res" style="color: #ffcc00; font-size: 18px; font-weight: bold;">0</span>
</div>
<div class="upgrade-grid">
<div class="upgrade-card" onclick="Game.upgradeShip('engine')">
<span class="upgrade-icon">🚀</span>
<div class="upgrade-name">引擎核心</div>
<div class="upgrade-level">Lv <span id="lvl-engine">1</span></div>
<span class="upgrade-cost">100 UNIT</span>
</div>
<div class="upgrade-card" onclick="Game.upgradeShip('shield')">
<span class="upgrade-icon">🛡️</span>
<div class="upgrade-name">偏导护盾</div>
<div class="upgrade-level">Lv <span id="lvl-shield">1</span></div>
<span class="upgrade-cost">80 UNIT</span>
</div>
<div class="upgrade-card" onclick="Game.upgradeShip('weapon')">
<span class="upgrade-icon"></span>
<div class="upgrade-name">相位激光</div>
<div class="upgrade-level">Lv <span id="lvl-weapon">1</span></div>
<span class="upgrade-cost">120 UNIT</span>
</div>
</div>
<div style="text-align: center;">
<button class="btn btn-danger" onclick="Game.closeUpgradeMenu()">返回航行</button>
</div>
</div>
</div>
<!-- 游戏结束/开始界面 -->
<div id="start-screen" class="modal-overlay active" style="background: rgba(0,0,0,0.85);">
<div class="modal-content" style="text-align: center; max-width: 500px; border: 1px solid #fff;">
<h1 style="font-size: 40px; margin-bottom: 5px; color: #fff;">星际文明探索者</h1>
<div style="width: 50px; height: 2px; background: var(--primary-color); margin: 15px auto;"></div>
<p style="color: #aaa; line-height: 1.6; font-size: 14px;">
驾驶飞船探索未知宇宙,采集稀有资源,升级战舰。<br>
驾驶物理引擎飞船,体验太空漂移。
</p>
<div style="text-align: left; background: rgba(255,255,255,0.05); padding: 15px; border-radius: 6px; margin: 25px 0; font-size: 13px; display: inline-block;">
<div style="margin-bottom: 5px;"><span class="interaction-key">W</span><span class="interaction-key">A</span><span class="interaction-key">S</span><span class="interaction-key">D</span> 移动引擎 (具有惯性)</div>
<div style="margin-bottom: 5px;"><span class="interaction-key">鼠标</span> 旋转与瞄准</div>
<div style="margin-bottom: 5px;"><span class="interaction-key">左键</span> 射击</div>
<div style="margin-bottom: 5px;"><span class="interaction-key">F</span> 扫描星球 / 采集</div>
<div><span class="interaction-key">U</span> 升级系统</div>
</div>
<br>
<button class="btn" style="font-size: 18px; padding: 12px 50px; width: 100%;" onclick="Game.start()">启动引擎</button>
</div>
</div>
<script>
// --- 1. 音频管理器 ---
class SoundManager {
constructor() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.masterGain = this.ctx.createGain();
this.masterGain.gain.value = 0.3;
this.masterGain.connect(this.ctx.destination);
}
playShoot() {
if(this.ctx.state === 'suspended') this.ctx.resume();
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.3, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.ctx.currentTime + 0.15);
}
playExplosion() {
if(this.ctx.state === 'suspended') this.ctx.resume();
const bufferSize = this.ctx.sampleRate * 0.4;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.ctx.createBufferSource();
noise.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 800;
filter.frequency.linearRampToValueAtTime(100, this.ctx.currentTime + 0.4);
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0.5, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4);
noise.connect(filter);
filter.connect(gain);
gain.connect(this.masterGain);
noise.start();
}
playThrust() {
if(this.ctx.state === 'suspended') this.ctx.resume();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle'; // 更柔和的引擎声
osc.frequency.setValueAtTime(60, this.ctx.currentTime);
osc.frequency.linearRampToValueAtTime(40, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(0.05, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.1);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.ctx.currentTime + 0.1);
}
playCollect() {
if(this.ctx.state === 'suspended') this.ctx.resume();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(1200, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(2000, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.2);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.ctx.currentTime + 0.2);
}
}
// --- 2. 全局配置与状态 ---
const Config = {
colors: { ship: 0xffffff, engine: 0x00f3ff, laser: 0xff0055, enemy: 0xff3333, resource: 0xffcc00 },
worldSize: 2000,
};
const State = {
resources: 0,
hp: 100,
maxHp: 100,
shield: 100,
maxShield: 100,
levels: { engine: 1, shield: 1, weapon: 1 },
isPlaying: false,
isPaused: false,
scannedPlanet: null,
upgradeCosts: { engine: 100, shield: 80, weapon: 120 }
};
// --- 3. 游戏引擎核心 ---
class GameEngine {
constructor() {
// 检查 CANNON 是否加载
if (typeof CANNON === 'undefined') {
console.error("CANNON 物理库未能正确加载,请检查网络连接。");
alert("错误:物理引擎库加载失败,请刷新页面。");
return;
}
// Three.js 初始化
this.scene = new THREE.Scene();
this.scene.fog = new THREE.FogExp2(0x050510, 0.0008);
this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000);
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
document.getElementById('canvas-container').appendChild(this.renderer.domElement);
// Cannon.js 物理世界
this.world = new CANNON.World();
this.world.gravity.set(0, 0, 0);
this.physicsMaterial = new CANNON.Material('slippery');
const contactMaterial = new CANNON.ContactMaterial(this.physicsMaterial, this.physicsMaterial, {
friction: 0.1,
restitution: 0.3
});
this.world.addContactMaterial(contactMaterial);
// 灯光
this.scene.add(new THREE.AmbientLight(0x404040, 1.2));
const sunLight = new THREE.DirectionalLight(0xffffff, 1.2);
sunLight.position.set(500, 300, 500);
sunLight.castShadow = true;
this.scene.add(sunLight);
// 实体与队列
this.entities = [];
this.particles = []; // 简单的视觉粒子
this.radarDots = [];
// 输入
this.input = { w: false, a: false, s: false, d: false, mouseX: 0, mouseY: 0, mouseDown: false };
// 工具
this.clock = new THREE.Clock();
this.raycaster = new THREE.Raycaster();
this.plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); // 用于鼠标拾取的平面
this.initListeners();
this.createEnvironment();
// 绑定动画循环
this.animate = this.animate.bind(this);
requestAnimationFrame(this.animate);
// 移除加载遮罩
setTimeout(() => {
document.getElementById('loader').style.opacity = 0;
setTimeout(() => document.getElementById('loader').style.display = 'none', 1000);
}, 1500);
}
initListeners() {
window.addEventListener('resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
});
document.addEventListener('keydown', e => {
if(e.key.toLowerCase() === 'w') this.input.w = true;
if(e.key.toLowerCase() === 'a') this.input.a = true;
if(e.key.toLowerCase() === 's') this.input.s = true;
if(e.key.toLowerCase() === 'd') this.input.d = true;
});
document.addEventListener('keyup', e => {
if(e.key.toLowerCase() === 'w') this.input.w = false;
if(e.key.toLowerCase() === 'a') this.input.a = false;
if(e.key.toLowerCase() === 's') this.input.s = false;
if(e.key.toLowerCase() === 'd') this.input.d = false;
});
document.addEventListener('mousemove', e => {
this.input.mouseX = (e.clientX / window.innerWidth) * 2 - 1;
this.input.mouseY = -(e.clientY / window.innerHeight) * 2 + 1;
});
document.addEventListener('mousedown', () => {
this.input.mouseDown = true;
if(State.isPlaying && !State.isPaused && this.player) this.shoot(this.player, true);
});
document.addEventListener('mouseup', () => this.input.mouseDown = false);
}
createEnvironment() {
// 星空
const starGeo = new THREE.BufferGeometry();
const starCount = 3000;
const posArray = new Float32Array(starCount * 3);
for(let i=0; i<starCount*3; i++) posArray[i] = (Math.random()-0.5)*4000;
starGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
this.scene.add(new THREE.Points(starGeo, new THREE.PointsMaterial({size: 1.5, color: 0xffffff, transparent: true})));
// 随机生成星球
for(let i=0; i<6; i++) {
const pos = new THREE.Vector3((Math.random()-0.5)*1500, 0, (Math.random()-0.5)*1500);
if(pos.length() < 400) pos.setLength(600);
this.createPlanet(pos);
}
}
createPlanet(pos) {
const r = 80 + Math.random() * 100;
const geo = new THREE.SphereGeometry(r, 32, 32);
const color = new THREE.Color().setHSL(Math.random(), 0.8, 0.4);
const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: color, roughness: 0.9 }));
mesh.position.copy(pos);
this.scene.add(mesh);
// 大气
const atmo = new THREE.Mesh(new THREE.SphereGeometry(r*1.2, 32, 32), new THREE.MeshBasicMaterial({color: color, transparent: true, opacity: 0.15, side: THREE.BackSide}));
mesh.add(atmo);
// 物理体
const body = new CANNON.Body({ mass: 0, shape: new CANNON.Sphere(r) });
body.position.copy(pos);
this.world.addBody(body);
this.entities.push({
type: 'planet',
mesh: mesh,
body: body,
radius: r,
data: {
name: `P-${Math.floor(Math.random()*9000)+1000}`,
temp: Math.floor(Math.random()*200-100) + "°C",
habit: Math.random() > 0.7 ? "S级 (宜居)" : "C级 (恶劣)",
resVal: Math.floor(Math.random()*300) + 100
}
});
}
createPlayer() {
const group = new THREE.Group();
// 机身
const body = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 4), new THREE.MeshStandardMaterial({ color: Config.colors.ship }));
group.add(body);
// 引擎
const engine = new THREE.Mesh(new THREE.ConeGeometry(0.5, 1, 8), new THREE.MeshBasicMaterial({ color: Config.colors.engine }));
engine.rotation.x = Math.PI/2;
engine.position.set(0, 0, 2.5);
group.add(engine);
this.scene.add(group);
const physBody = new CANNON.Body({
mass: 10,
shape: new CANNON.Box(new CANNON.Vec3(1, 0.5, 2)),
linearDamping: 0.95,
angularDamping: 0.95
});
this.world.addBody(physBody);
this.player = {
type: 'player',
mesh: group,
body: physBody,
engine: this,
lastShot: 0,
throttle: 0
};
// 碰撞事件监听 (受伤)
physBody.addEventListener('collide', (e) => {
const normal = new CANNON.Vec3();
e.contact.ni.negate(normal); // 碰撞方向
const relativeVelocity = e.contact.getImpactVelocityAlongNormal();
if(Math.abs(relativeVelocity) > 5) {
Game.takeDamage(Math.floor(Math.abs(relativeVelocity) * 2));
this.createExplosion(physBody.position, 0.5);
}
});
}
spawnEnemy() {
if(this.entities.filter(e => e.type === 'enemy').length > 5) return;
const pos = this.player.mesh.position.clone().add(new THREE.Vector3((Math.random()-0.5)*300, 0, (Math.random()-0.5)*300));
const mesh = new THREE.Mesh(new THREE.ConeGeometry(3, 8, 4), new THREE.MeshStandardMaterial({ color: Config.colors.enemy }));
mesh.rotation.x = Math.PI/2;
mesh.position.copy(pos);
this.scene.add(mesh);
const body = new CANNON.Body({ mass: 5, linearDamping: 0.5 });
// 使用球体作为碰撞体
body.addShape(new CANNON.Sphere(3));
body.position.copy(pos);
this.world.addBody(body);
const enemy = { type: 'enemy', mesh: mesh, body: body, hp: 30, lastShot: 0, engine: this };
this.entities.push(enemy);
}
shoot(source, isPlayer) {
const now = Date.now();
const cd = isPlayer ? 200 - State.levels.weapon * 15 : 2000;
if (now - source.lastShot < cd) return;
source.lastShot = now;
new SoundManager().playShoot();
const direction = new THREE.Vector3();
if(isPlayer) {
direction.set(0, 0, 1).applyQuaternion(source.mesh.quaternion);
} else {
direction.subVectors(this.player.mesh.position, source.mesh.position).normalize();
}
const mesh = new THREE.Mesh(new THREE.SphereGeometry(0.5), new THREE.MeshBasicMaterial({ color: Config.colors.laser }));
mesh.position.copy(source.mesh.position).add(direction.clone().multiplyScalar(3));
this.scene.add(mesh);
const body = new CANNON.Body({ mass: 0.5, shape: new CANNON.Sphere(0.5), linearDamping: 0 });
body.position.copy(mesh.position);
body.velocity.copy(direction.multiplyScalar(isPlayer ? 300 : 100));
// 子弹碰撞逻辑:这里使用一个简单的 owner 标记
body.isBullet = true;
body.owner = isPlayer ? 'player' : 'enemy';
// 为子弹绑定碰撞事件
body.addEventListener('collide', (e) => {
// 【关键修复】使用 setTimeout 延迟移除,避免物理引擎崩溃
setTimeout(() => this.removeEntity(mesh, body), 0);
this.createExplosion(body.position, 0.3);
if (isPlayer) {
// 玩家子弹击中某物
const enemy = this.entities.find(ent => ent.body === e.body && ent.type === 'enemy');
if (enemy) {
enemy.hp -= (10 + State.levels.weapon * 5);
if (enemy.hp <= 0) {
Game.addResource(20);
// 延迟移除敌人
setTimeout(() => {
this.removeEntity(enemy.mesh, enemy.body);
this.entities = this.entities.filter(ent => ent !== enemy);
}, 0);
this.createExplosion(enemy.body.position, 2);
new SoundManager().playExplosion();
}
}
} else {
// 敌人子弹击中玩家
Game.takeDamage(10 + State.levels.weapon * 2);
}
});
this.world.addBody(body);
// 管理子弹生命周期 (临时实体)
const bullet = { type: 'bullet', mesh: mesh, body: body, life: 2000 };
this.entities.push(bullet);
}
createExplosion(pos, scale = 1) {
new SoundManager().playExplosion();
const particleCount = 8 * scale;
const geo = new THREE.BoxGeometry(scale, scale, scale);
const mat = new THREE.MeshBasicMaterial({ color: 0xffaa00 });
for(let i=0; i<particleCount; i++) {
const mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(pos);
mesh.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
this.scene.add(mesh);
this.particles.push({
mesh: mesh,
vel: new THREE.Vector3((Math.random()-0.5)*10, (Math.random()-0.5)*10, (Math.random()-0.5)*10).multiplyScalar(scale),
life: 1.0
});
}
}
createResource(pos) {
const mesh = new THREE.Mesh(new THREE.OctahedronGeometry(2), new THREE.MeshStandardMaterial({
color: Config.colors.resource, emissive: Config.colors.resource, emissiveIntensity: 0.5
}));
mesh.position.copy(pos);
this.scene.add(mesh);
const body = new CANNON.Body({ mass: 1, shape: new CANNON.Sphere(2), isTrigger: true }); // Trigger: 不产生物理碰撞反弹
body.position.copy(pos);
// 【关键修复】资源碰撞监听
body.addEventListener('collide', (e) => {
if (e.body === this.player.body) {
Game.addResource(50);
// 延迟移除
setTimeout(() => {
this.removeEntity(mesh, body);
this.entities = this.entities.filter(ent => ent !== res);
}, 0);
new SoundManager().playCollect();
this.createExplosion(pos, 0.5);
}
});
this.world.addBody(body);
const res = { type: 'resource', mesh: mesh, body: body };
this.entities.push(res);
}
removeEntity(mesh, body) {
if(mesh && this.scene.children.includes(mesh)) this.scene.remove(mesh);
if(body && body.world === this.world) this.world.removeBody(body);
}
update(delta) {
if (!State.isPlaying || State.isPaused) return;
// 1. 物理步进
this.world.step(1/60, delta, 3);
// 2. 玩家控制
if(this.player) {
const speed = 30 * (1 + State.levels.engine * 0.1);
const rotSpeed = 2.5;
// 移动力
if(this.input.w) {
this.player.body.applyLocalForce(new CANNON.Vec3(0, 0, -speed), this.player.body.position);
if (Math.random() > 0.8) new SoundManager().playThrust();
}
if(this.input.s) this.player.body.applyLocalForce(new CANNON.Vec3(0, 0, speed), this.player.body.position);
if(this.input.a) this.player.body.angularVelocity.y += rotSpeed * delta;
if(this.input.d) this.player.body.angularVelocity.y -= rotSpeed * delta;
// 同步位置
this.player.mesh.position.copy(this.player.body.position);
this.player.mesh.quaternion.copy(this.player.body.quaternion);
// 鼠标朝向 (绕Y轴)
// 计算鼠标在3D空间的方向
this.raycaster.setFromCamera({x: this.input.mouseX, y: this.input.mouseY}, this.camera);
const target = new THREE.Vector3();
this.raycaster.ray.intersectPlane(this.plane, target);
if(target) {
const lookDir = new THREE.Vector3().subVectors(target, this.player.mesh.position).normalize();
// 我们只关心水平面旋转
lookDir.y = 0;
lookDir.normalize();
// 使用 quaternion slerp 平滑旋转
const targetQuat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0,0,1), lookDir);
this.player.mesh.quaternion.slerp(targetQuat, delta * 5);
this.player.body.quaternion.copy(this.player.mesh.quaternion);
}
// 相机跟随
const camOffset = new THREE.Vector3(0, 30, -40).applyQuaternion(this.player.mesh.quaternion).add(this.player.mesh.position);
this.camera.position.lerp(camOffset, delta * 2);
this.camera.lookAt(this.player.mesh.position);
// 更新坐标显示
document.getElementById('coords').innerText = `${Math.floor(this.player.mesh.position.x)}, ${Math.floor(this.player.mesh.position.z)}`;
}
// 3. 实体更新
for (let i = this.entities.length - 1; i >= 0; i--) {
const ent = this.entities[i];
// 【关键修复】如果物理体已被移除world === null则从实体列表中删除避免后续同步错误
if (ent.body.world === null) {
this.entities.splice(i, 1);
continue;
}
if(ent.type === 'bullet') {
ent.life -= delta * 1000;
if(ent.life <= 0) {
this.removeEntity(ent.mesh, ent.body);
this.entities.splice(i, 1);
continue;
}
}
if(ent.type === 'enemy') {
// AI 简单的追踪
const dir = new THREE.Vector3().subVectors(this.player.mesh.position, ent.mesh.position).normalize();
ent.body.velocity.copy(dir.multiplyScalar(15));
ent.mesh.position.copy(ent.body.position);
ent.mesh.lookAt(this.player.mesh.position);
// 射击
const dist = ent.mesh.position.distanceTo(this.player.mesh.position);
if (dist < 200 && Math.random() < 0.01) this.shoot(ent, false);
}
if(ent.type === 'resource' || ent.type === 'asteroid') {
ent.mesh.position.copy(ent.body.position);
ent.mesh.rotation.x += delta;
ent.mesh.rotation.y += delta;
}
}
// 4. 粒子更新
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i];
p.life -= delta;
p.mesh.position.add(p.vel.clone().multiplyScalar(delta));
p.mesh.scale.setScalar(p.life);
p.mesh.material.opacity = p.life;
if(p.life <= 0) {
this.scene.remove(p.mesh);
this.particles.splice(i, 1);
}
}
// 5. 鼠标悬停检测 (UI提示)
this.raycaster.setFromCamera({x: this.input.mouseX, y: this.input.mouseY}, this.camera);
const intersects = this.raycaster.intersectObjects(this.entities.filter(e => e.type === 'planet').map(e => e.mesh));
const crosshair = document.getElementById('crosshair');
if(intersects.length > 0 && intersects[0].distance < 500) {
crosshair.classList.add('hovering');
// 记录当前悬停的星球用于F键交互
this.hoveredPlanet = this.entities.find(e => e.mesh === intersects[0].object);
} else {
crosshair.classList.remove('hovering');
this.hoveredPlanet = null;
}
// 6. 雷达更新
this.updateRadar();
// 7. 生成逻辑 (简单的随机)
if(Math.random() < 0.005) this.spawnAsteroid();
if(Math.random() < 0.002) this.spawnEnemy(); // 敌人生成率 - 现在这个方法名是对的了
}
spawnAsteroid() {
const pos = this.player.mesh.position.clone().add(new THREE.Vector3((Math.random()-0.5)*600, (Math.random()-0.5)*200, (Math.random()-0.5)*600));
const r = 2 + Math.random()*3;
const geo = new THREE.DodecahedronGeometry(r, 0);
const mat = new THREE.MeshStandardMaterial({ color: 0x888888, flatShading: true });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(pos);
this.scene.add(mesh);
const body = new CANNON.Body({ mass: r*2, shape: new CANNON.Sphere(r), linearDamping: 0.1 });
body.position.copy(pos);
body.velocity.set(Math.random(), Math.random(), Math.random());
// 碰撞
body.addEventListener('collide', (e) => {
if(e.body === this.player.body) {
Game.takeDamage(10);
// 【关键修复】延迟移除
setTimeout(() => {
this.removeEntity(mesh, body);
this.entities = this.entities.filter(ent => ent !== ast);
}, 0);
this.createExplosion(body.position, 1);
}
// 子弹碰撞
const bullet = this.entities.find(ent => ent.body === e.body && ent.type === 'bullet');
if(bullet) {
// 【关键修复】延迟移除
setTimeout(() => {
this.removeEntity(mesh, body);
this.entities = this.entities.filter(ent => ent !== ast);
}, 0);
// 掉落资源几率
if(Math.random() > 0.5) this.createResource(body.position);
}
});
this.world.addBody(body);
const ast = { type: 'asteroid', mesh: mesh, body: body };
this.entities.push(ast);
}
updateRadar() {
const radar = document.getElementById('radar-display');
// 清理旧的点
this.radarDots.forEach(d => d.remove());
this.radarDots = [];
if(!this.player) return;
this.entities.forEach(ent => {
if(ent.type === 'player') return;
// 计算相对位置
const dx = ent.mesh.position.x - this.player.mesh.position.x;
const dz = ent.mesh.position.z - this.player.mesh.position.z;
const dist = Math.sqrt(dx*dx + dz*dz);
if(dist < 400) { // 雷达范围
const dot = document.createElement('div');
dot.style.position = 'absolute';
// 映射到雷达像素: 半径60px
const rx = (dx / 400) * 60;
const ry = (dz / 400) * 60;
dot.style.left = (60 + rx) + 'px';
dot.style.top = (60 + ry) + 'px';
dot.style.width = '4px';
dot.style.height = '4px';
dot.style.borderRadius = '50%';
if(ent.type === 'enemy') dot.style.background = '#ff0055';
else if(ent.type === 'planet') dot.style.background = '#00f3ff';
else if(ent.type === 'resource') dot.style.background = '#ffcc00';
radar.appendChild(dot);
this.radarDots.push(dot);
}
});
}
animate() {
requestAnimationFrame(this.animate);
const delta = this.clock.getDelta();
this.update(delta);
this.renderer.render(this.scene, this.camera);
}
}
// --- 4. 游戏逻辑控制器 ---
const Game = {
engine: null,
init: function() {
this.engine = new GameEngine();
this.updateUI();
},
start: function() {
document.getElementById('start-screen').classList.remove('active');
State.isPlaying = true;
this.engine.createPlayer();
new SoundManager().playThrust(); // 启动音效
},
takeDamage: function(amount) {
// 护盾抵扣
if(State.shield > 0) {
State.shield -= amount;
if(State.shield < 0) {
const rem = Math.abs(State.shield);
State.shield = 0;
State.hp -= rem;
}
} else {
State.hp -= amount;
}
this.updateUI();
if(State.hp <= 0) {
alert("飞船损毁! 任务失败。");
location.reload();
}
},
addResource: function(amount) {
State.resources += amount;
this.updateUI();
this.showFloatingText(`+${amount} RESOURCE`, '#ffcc00');
},
updateUI: function() {
document.getElementById('hp-val').innerText = Math.floor(State.hp) + '%';
document.getElementById('hp-bar').style.width = (State.hp / State.maxHp * 100) + '%';
document.getElementById('shield-val').innerText = Math.floor(State.shield) + '%';
document.getElementById('shield-bar').style.width = (State.shield / State.maxShield * 100) + '%';
document.getElementById('res-count').innerText = State.resources;
// 更新升级菜单中的资源
document.getElementById('upgrade-res').innerText = State.resources;
// 更新等级显示
document.getElementById('lvl-engine').innerText = State.levels.engine;
document.getElementById('lvl-shield').innerText = State.levels.shield;
document.getElementById('lvl-weapon').innerText = State.levels.weapon;
},
showFloatingText: function(text, color) {
const el = document.createElement('div');
el.innerText = text;
el.style.position = 'absolute';
el.style.left = '50%';
el.style.top = '40%';
el.style.transform = 'translate(-50%, -50%)';
el.style.color = color;
el.style.fontSize = '20px';
el.style.fontWeight = 'bold';
el.style.textShadow = '0 0 5px black';
el.style.pointerEvents = 'none';
el.style.transition = 'all 1s';
document.body.appendChild(el);
// 动画
setTimeout(() => {
el.style.top = '30%';
el.style.opacity = 0;
}, 50);
setTimeout(() => el.remove(), 1000);
},
tryInteract: function() {
if(this.engine.hoveredPlanet && this.engine.hoveredPlanet.type === 'planet') {
State.isPaused = true;
State.scannedPlanet = this.engine.hoveredPlanet;
// 填充数据
const p = State.scannedPlanet.data;
document.getElementById('scan-name').innerText = p.name;
document.getElementById('scan-atmo').innerText = "N2 78%, O2 21%";
document.getElementById('scan-temp').innerText = p.temp;
document.getElementById('scan-habit').innerText = p.habit;
document.getElementById('scan-res').innerText = "价值: " + p.resVal;
document.getElementById('scan-modal').classList.add('active');
}
},
closeScanModal: function() {
document.getElementById('scan-modal').classList.remove('active');
State.isPaused = false;
State.scannedPlanet = null;
},
collectPlanetResources: function() {
if(State.scannedPlanet) {
const val = State.scannedPlanet.data.resVal;
this.addResource(val);
this.closeScanModal();
this.showFloatingText("采集成功!", "#00f3ff");
new SoundManager().playCollect();
}
},
openUpgradeMenu: function() {
State.isPaused = true;
document.getElementById('upgrade-modal').classList.add('active');
},
closeUpgradeMenu: function() {
document.getElementById('upgrade-modal').classList.remove('active');
State.isPaused = false;
},
upgradeShip: function(type) {
const cost = State.upgradeCosts[type] * State.levels[type];
if(State.resources >= cost) {
State.resources -= cost;
State.levels[type]++;
// 应用即时效果
if(type === 'shield') {
State.maxShield += 50;
State.shield = State.maxShield;
}
this.updateUI();
new SoundManager().playCollect(); // 使用收集音效作为确认音
} else {
const btn = event.currentTarget;
btn.style.borderColor = 'red';
setTimeout(() => btn.style.borderColor = 'transparent', 500);
}
}
};
// 启动初始化
window.onload = () => Game.init();
</script>
</body>
</html>