2314 lines
80 KiB
HTML
2314 lines
80 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>星际文明探索者 - Star Civilization Explorer</title>
|
||
|
||
<!-- 引入 Three.js -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||
|
||
<!-- 引入 GSAP 动画库 -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||
|
||
<!-- 引入 Howler.js 音频库 -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.4/howler.min.js"></script>
|
||
|
||
<!-- 引入字体 -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:root {
|
||
--primary-color: #4CC9F0;
|
||
--secondary-color: #7B2CBF;
|
||
--accent-color: #F72585;
|
||
--warning-color: #FFD60A;
|
||
--bg-color: #050510;
|
||
--glass-bg: rgba(255, 255, 255, 0.05);
|
||
--glass-border: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
body {
|
||
overflow: hidden;
|
||
background: var(--bg-color);
|
||
font-family: 'Orbitron', sans-serif;
|
||
color: white;
|
||
}
|
||
|
||
/* 游戏画布 */
|
||
#game-canvas {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
}
|
||
|
||
/* 游戏UI层 */
|
||
#game-ui {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 10;
|
||
}
|
||
|
||
/* 所有UI元素 */
|
||
.ui-element {
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* 磨砂玻璃面板 */
|
||
.glass-panel {
|
||
background: var(--glass-bg);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
}
|
||
|
||
/* 状态栏 - 左上角 */
|
||
#status-bar {
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.status-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.status-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.status-bar-container {
|
||
width: 200px;
|
||
height: 8px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.status-bar {
|
||
height: 100%;
|
||
border-radius: 4px;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.shield-bar {
|
||
background: linear-gradient(90deg, #4CC9F0, #00d4ff);
|
||
box-shadow: 0 0 10px #4CC9F0;
|
||
}
|
||
|
||
.health-bar {
|
||
background: linear-gradient(90deg, #F72585, #ff006e);
|
||
box-shadow: 0 0 10px #F72585;
|
||
}
|
||
|
||
.energy-bar {
|
||
background: linear-gradient(90deg, #FFD60A, #ffaa00);
|
||
box-shadow: 0 0 10px #FFD60A;
|
||
}
|
||
|
||
/* 资源面板 - 右上角 */
|
||
#resource-panel {
|
||
position: absolute;
|
||
top: 20px;
|
||
right: 20px;
|
||
text-align: right;
|
||
}
|
||
|
||
.resource-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-bottom: 8px;
|
||
font-family: 'Share Tech Mono', monospace;
|
||
}
|
||
|
||
.resource-icon {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.resource-count {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: var(--warning-color);
|
||
text-shadow: 0 0 10px rgba(255, 214, 10, 0.5);
|
||
}
|
||
|
||
/* 准星 - 屏幕中央 */
|
||
#crosshair {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 40px;
|
||
height: 40px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.crosshair-ring {
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 2px solid rgba(76, 201, 240, 0.6);
|
||
border-radius: 50%;
|
||
position: relative;
|
||
animation: crosshairPulse 2s infinite;
|
||
}
|
||
|
||
.crosshair-ring::before,
|
||
.crosshair-ring::after {
|
||
content: '';
|
||
position: absolute;
|
||
background: var(--primary-color);
|
||
}
|
||
|
||
.crosshair-ring::before {
|
||
width: 4px;
|
||
height: 4px;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.crosshair-line {
|
||
position: absolute;
|
||
background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
|
||
height: 1px;
|
||
}
|
||
|
||
.crosshair-line.horizontal {
|
||
width: 60px;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
|
||
.crosshair-line.vertical {
|
||
width: 1px;
|
||
height: 60px;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
|
||
@keyframes crosshairPulse {
|
||
0%, 100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.7; transform: scale(1.1); }
|
||
}
|
||
|
||
/* 交互提示 - 中下方 */
|
||
#interaction-prompt {
|
||
position: absolute;
|
||
bottom: 120px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
text-align: center;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
#interaction-prompt.visible {
|
||
opacity: 1;
|
||
}
|
||
|
||
.prompt-text {
|
||
font-size: 16px;
|
||
color: var(--primary-color);
|
||
text-shadow: 0 0 20px rgba(76, 201, 240, 0.8);
|
||
animation: promptGlow 1s infinite alternate;
|
||
}
|
||
|
||
@keyframes promptGlow {
|
||
from { text-shadow: 0 0 20px rgba(76, 201, 240, 0.8); }
|
||
to { text-shadow: 0 0 30px rgba(76, 201, 240, 1), 0 0 40px rgba(76, 201, 240, 0.5); }
|
||
}
|
||
|
||
/* 消息日志 - 左下角 */
|
||
#message-log {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
left: 20px;
|
||
max-width: 400px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.log-message {
|
||
padding: 10px 15px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-family: 'Share Tech Mono', monospace;
|
||
border-left: 3px solid var(--primary-color);
|
||
animation: logSlideIn 0.3s ease, logFadeOut 0.5s ease 4s forwards;
|
||
}
|
||
|
||
.log-message.success {
|
||
border-left-color: #00ff88;
|
||
}
|
||
|
||
.log-message.warning {
|
||
border-left-color: var(--warning-color);
|
||
}
|
||
|
||
.log-message.danger {
|
||
border-left-color: var(--accent-color);
|
||
}
|
||
|
||
@keyframes logSlideIn {
|
||
from { transform: translateX(-20px); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
|
||
@keyframes logFadeOut {
|
||
to { opacity: 0; transform: translateX(-10px); }
|
||
}
|
||
|
||
/* 小地图 - 右下角 */
|
||
#minimap {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
width: 180px;
|
||
height: 180px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#minimap-canvas {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(5, 5, 16, 0.8);
|
||
}
|
||
|
||
/* 升级面板 - 居中 */
|
||
#upgrade-panel {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%) scale(0.9);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: all 0.3s ease;
|
||
z-index: 100;
|
||
}
|
||
|
||
#upgrade-panel.visible {
|
||
transform: translate(-50%, -50%) scale(1);
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.upgrade-title {
|
||
font-size: 28px;
|
||
font-weight: 900;
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.upgrade-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
}
|
||
|
||
.upgrade-card {
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.upgrade-card:hover {
|
||
background: rgba(255, 255, 255, 0.08);
|
||
border-color: var(--primary-color);
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 10px 30px rgba(76, 201, 240, 0.2);
|
||
}
|
||
|
||
.upgrade-icon {
|
||
font-size: 40px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.upgrade-name {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.upgrade-level {
|
||
font-size: 12px;
|
||
color: var(--primary-color);
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.upgrade-cost {
|
||
font-size: 12px;
|
||
color: var(--warning-color);
|
||
}
|
||
|
||
.upgrade-btn {
|
||
margin-top: 20px;
|
||
padding: 12px 30px;
|
||
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
||
border: none;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-family: 'Orbitron', sans-serif;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.upgrade-btn:hover {
|
||
transform: scale(1.05);
|
||
box-shadow: 0 0 20px rgba(76, 201, 240, 0.5);
|
||
}
|
||
|
||
/* 暂停菜单 */
|
||
#pause-menu {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
text-align: center;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: all 0.3s ease;
|
||
z-index: 100;
|
||
}
|
||
|
||
#pause-menu.visible {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.pause-title {
|
||
font-size: 48px;
|
||
font-weight: 900;
|
||
margin-bottom: 40px;
|
||
text-shadow: 0 0 30px var(--primary-color);
|
||
}
|
||
|
||
.pause-btn {
|
||
display: block;
|
||
width: 250px;
|
||
margin: 15px auto;
|
||
padding: 15px 30px;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-family: 'Orbitron', sans-serif;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.pause-btn:hover {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-color: var(--primary-color);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
/* 控制说明 */
|
||
#controls-info {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.5);
|
||
text-align: center;
|
||
}
|
||
|
||
.key {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4px;
|
||
margin: 0 4px;
|
||
font-family: 'Share Tech Mono', monospace;
|
||
}
|
||
|
||
/* 开始界面 */
|
||
#start-screen {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(180deg, #050510 0%, #0a0a20 50%, #050510 100%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
transition: opacity 0.5s ease;
|
||
}
|
||
|
||
#start-screen.hidden {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.game-title {
|
||
font-size: 64px;
|
||
font-weight: 900;
|
||
margin-bottom: 20px;
|
||
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color));
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
animation: titleGlow 3s infinite alternate;
|
||
text-align: center;
|
||
}
|
||
|
||
.game-subtitle {
|
||
font-size: 18px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
margin-bottom: 50px;
|
||
}
|
||
|
||
@keyframes titleGlow {
|
||
from { filter: drop-shadow(0 0 20px rgba(76, 201, 240, 0.5)); }
|
||
to { filter: drop-shadow(0 0 40px rgba(123, 44, 191, 0.8)); }
|
||
}
|
||
|
||
.start-btn {
|
||
padding: 20px 60px;
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
||
border: none;
|
||
border-radius: 50px;
|
||
color: white;
|
||
font-family: 'Orbitron', sans-serif;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
margin: 10px;
|
||
}
|
||
|
||
.start-btn:hover {
|
||
transform: scale(1.1);
|
||
box-shadow: 0 0 40px rgba(76, 201, 240, 0.6);
|
||
}
|
||
|
||
.controls-list {
|
||
margin-top: 50px;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 15px;
|
||
text-align: left;
|
||
}
|
||
|
||
.control-item {
|
||
font-size: 14px;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
}
|
||
|
||
.control-item .key {
|
||
background: rgba(76, 201, 240, 0.2);
|
||
color: var(--primary-color);
|
||
border: 1px solid rgba(76, 201, 240, 0.3);
|
||
}
|
||
|
||
/* 警告提示 */
|
||
#warning-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: radial-gradient(ellipse at center, transparent 0%, rgba(247, 37, 133, 0) 100%);
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
z-index: 50;
|
||
}
|
||
|
||
#warning-overlay.active {
|
||
opacity: 1;
|
||
animation: warningPulse 0.5s infinite;
|
||
}
|
||
|
||
@keyframes warningPulse {
|
||
0%, 100% { background: radial-gradient(ellipse at center, transparent 0%, rgba(247, 37, 133, 0.1) 100%); }
|
||
50% { background: radial-gradient(ellipse at center, transparent 0%, rgba(247, 37, 133, 0.3) 100%); }
|
||
}
|
||
|
||
/* 战斗统计 */
|
||
#combat-stats {
|
||
position: absolute;
|
||
top: 100px;
|
||
right: 20px;
|
||
font-family: 'Share Tech Mono', monospace;
|
||
text-align: right;
|
||
}
|
||
|
||
.combat-stat {
|
||
font-size: 14px;
|
||
margin-bottom: 5px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.combat-stat-value {
|
||
color: var(--accent-color);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* 经验值显示 */
|
||
#exp-display {
|
||
position: absolute;
|
||
bottom: 60px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
text-align: center;
|
||
}
|
||
|
||
.exp-bar-container {
|
||
width: 300px;
|
||
height: 6px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.exp-bar {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, var(--secondary-color), var(--accent-color));
|
||
border-radius: 3px;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.exp-text {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.game-title {
|
||
font-size: 36px;
|
||
}
|
||
|
||
#status-bar {
|
||
top: 10px;
|
||
left: 10px;
|
||
}
|
||
|
||
.status-bar-container {
|
||
width: 120px;
|
||
}
|
||
|
||
#resource-panel {
|
||
top: 10px;
|
||
right: 10px;
|
||
}
|
||
|
||
.controls-list {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 游戏画布 -->
|
||
<canvas id="game-canvas"></canvas>
|
||
|
||
<!-- 游戏UI层 -->
|
||
<div id="game-ui">
|
||
<!-- 开始界面 -->
|
||
<div id="start-screen">
|
||
<h1 class="game-title">星际文明探索者</h1>
|
||
<p class="game-subtitle">Star Civilization Explorer</p>
|
||
<button class="start-btn" id="start-btn">开始探索</button>
|
||
<div class="controls-list">
|
||
<div class="control-item"><span class="key">W</span><span class="key">A</span><span class="key">S</span><span class="key">D</span> 移动飞船</div>
|
||
<div class="control-item"><span class="key">鼠标</span> 瞄准方向</div>
|
||
<div class="control-item"><span class="key">左键</span> 发射激光</div>
|
||
<div class="control-item"><span class="key">E</span> 采集资源</div>
|
||
<div class="control-item"><span class="key">空格</span> 释放技能</div>
|
||
<div class="control-item"><span class="key">U</span> 升级面板</div>
|
||
<div class="control-item"><span class="key">ESC</span> 暂停游戏</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 状态栏 -->
|
||
<div id="status-bar" class="ui-element" style="display: none;">
|
||
<div class="status-item">
|
||
<div class="status-icon">🛡️</div>
|
||
<div class="status-bar-container">
|
||
<div class="status-bar shield-bar" id="shield-bar" style="width: 100%;"></div>
|
||
</div>
|
||
<span id="shield-text">100%</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<div class="status-icon">❤️</div>
|
||
<div class="status-bar-container">
|
||
<div class="status-bar health-bar" id="health-bar" style="width: 100%;"></div>
|
||
</div>
|
||
<span id="health-text">100%</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<div class="status-icon">⚡</div>
|
||
<div class="status-bar-container">
|
||
<div class="status-bar energy-bar" id="energy-bar" style="width: 100%;"></div>
|
||
</div>
|
||
<span id="energy-text">100%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 资源面板 -->
|
||
<div id="resource-panel" class="ui-element" style="display: none;">
|
||
<div class="resource-item">
|
||
<span class="resource-count" id="crystal-count">0</span>
|
||
<span class="resource-icon">💎</span>
|
||
</div>
|
||
<div class="resource-item">
|
||
<span class="resource-count" id="ore-count">0</span>
|
||
<span class="resource-icon">🪨</span>
|
||
</div>
|
||
<div class="resource-item">
|
||
<span class="resource-count" id="relic-count">0</span>
|
||
<span class="resource-icon">🔮</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 准星 -->
|
||
<div id="crosshair" style="display: none;">
|
||
<div class="crosshair-ring"></div>
|
||
<div class="crosshair-line horizontal"></div>
|
||
<div class="crosshair-line vertical"></div>
|
||
</div>
|
||
|
||
<!-- 交互提示 -->
|
||
<div id="interaction-prompt" class="ui-element">
|
||
<span class="prompt-text" id="prompt-text">按 E 采集资源</span>
|
||
</div>
|
||
|
||
<!-- 消息日志 -->
|
||
<div id="message-log"></div>
|
||
|
||
<!-- 战斗统计 -->
|
||
<div id="combat-stats" class="ui-element" style="display: none;">
|
||
<div class="combat-stat">击毁: <span class="combat-stat-value" id="kill-count">0</span></div>
|
||
<div class="combat-stat">伤害: <span class="combat-stat-value" id="damage-count">0</span></div>
|
||
</div>
|
||
|
||
<!-- 经验值显示 -->
|
||
<div id="exp-display" class="ui-element" style="display: none;">
|
||
<div class="exp-bar-container">
|
||
<div class="exp-bar" id="exp-bar" style="width: 0%;"></div>
|
||
</div>
|
||
<span class="exp-text">等级 <span id="level-text">1</span></span>
|
||
</div>
|
||
|
||
<!-- 升级面板 -->
|
||
<div id="upgrade-panel" class="glass-panel ui-element">
|
||
<h2 class="upgrade-title">🚀 飞船升级</h2>
|
||
<div class="upgrade-grid">
|
||
<div class="upgrade-card" data-upgrade="engine">
|
||
<div class="upgrade-icon">🔥</div>
|
||
<div class="upgrade-name">推进系统</div>
|
||
<div class="upgrade-level" id="engine-level">等级 1</div>
|
||
<div class="upgrade-cost" id="engine-cost">消耗: 10 晶体</div>
|
||
</div>
|
||
<div class="upgrade-card" data-upgrade="shield">
|
||
<div class="upgrade-icon">🛡️</div>
|
||
<div class="upgrade-name">能量护盾</div>
|
||
<div class="upgrade-level" id="shield-level">等级 1</div>
|
||
<div class="upgrade-cost" id="shield-cost">消耗: 10 晶体</div>
|
||
</div>
|
||
<div class="upgrade-card" data-upgrade="weapon">
|
||
<div class="upgrade-icon">🔫</div>
|
||
<div class="upgrade-name">激光武器</div>
|
||
<div class="upgrade-level" id="weapon-level">等级 1</div>
|
||
<div class="upgrade-cost" id="weapon-cost">消耗: 10 晶体</div>
|
||
</div>
|
||
<div class="upgrade-card" data-upgrade="scanner">
|
||
<div class="upgrade-icon">📡</div>
|
||
<div class="upgrade-name">扫描系统</div>
|
||
<div class="upgrade-level" id="scanner-level">等级 1</div>
|
||
<div class="upgrade-cost" id="scanner-cost">消耗: 10 晶体</div>
|
||
</div>
|
||
<div class="upgrade-card" data-upgrade="cargo">
|
||
<div class="upgrade-icon">📦</div>
|
||
<div class="upgrade-name">货舱容量</div>
|
||
<div class="upgrade-level" id="cargo-level">等级 1</div>
|
||
<div class="upgrade-cost" id="cargo-cost">消耗: 10 晶体</div>
|
||
</div>
|
||
<div class="upgrade-card" data-upgrade="skill">
|
||
<div class="upgrade-icon">⚡</div>
|
||
<div class="upgrade-name">特殊技能</div>
|
||
<div class="upgrade-level" id="skill-level">等级 1</div>
|
||
<div class="upgrade-cost" id="skill-cost">消耗: 10 晶体</div>
|
||
</div>
|
||
</div>
|
||
<button class="upgrade-btn" id="close-upgrade">关闭</button>
|
||
</div>
|
||
|
||
<!-- 暂停菜单 -->
|
||
<div id="pause-menu" class="ui-element">
|
||
<h2 class="pause-title">⏸️ 暂停</h2>
|
||
<button class="pause-btn" id="resume-btn">继续游戏</button>
|
||
<button class="pause-btn" id="restart-btn">重新开始</button>
|
||
<button class="pause-btn" id="quit-btn">退出游戏</button>
|
||
</div>
|
||
|
||
<!-- 小地图 -->
|
||
<div id="minimap" class="ui-element" style="display: none;">
|
||
<canvas id="minimap-canvas"></canvas>
|
||
</div>
|
||
|
||
<!-- 控制说明 -->
|
||
<div id="controls-info" class="ui-element" style="display: none;">
|
||
<span class="key">WASD</span> 移动 |
|
||
<span class="key">鼠标</span> 瞄准 |
|
||
<span class="key">左键</span> 射击 |
|
||
<span class="key">E</span> 采集 |
|
||
<span class="key">U</span> 升级 |
|
||
<span class="key">ESC</span> 暂停
|
||
</div>
|
||
|
||
<!-- 警告遮罩 -->
|
||
<div id="warning-overlay"></div>
|
||
</div>
|
||
|
||
<script>
|
||
// ==================== 游戏主配置 ====================
|
||
const CONFIG = {
|
||
// 宇宙参数
|
||
UNIVERSE_SIZE: 5000,
|
||
STAR_COUNT: 3000,
|
||
PLANET_COUNT: 50,
|
||
ASTEROID_COUNT: 200,
|
||
ENEMY_COUNT: 30,
|
||
|
||
// 飞船参数
|
||
SHIP_SPEED: 50,
|
||
SHIP_ACCELERATION: 100,
|
||
SHIP_FRICTION: 0.98,
|
||
SHIP_TILT_FACTOR: 0.05,
|
||
|
||
// 战斗参数
|
||
LASER_SPEED: 200,
|
||
LASER_DAMAGE: 25,
|
||
LASER_COOLDOWN: 0.15,
|
||
SKILL_COOLDOWN: 30,
|
||
|
||
// 资源参数
|
||
RESOURCE_MAX_CARGO: 100,
|
||
HARVEST_SPEED: 10,
|
||
|
||
// 升级参数
|
||
BASE_UPGRADE_COST: 10,
|
||
UPGRADE_COST_MULTIPLIER: 1.5,
|
||
};
|
||
|
||
// ==================== 游戏状态 ====================
|
||
const gameState = {
|
||
isPlaying: false,
|
||
isPaused: false,
|
||
isUpgrading: false,
|
||
lastTime: 0,
|
||
|
||
// 玩家状态
|
||
player: {
|
||
health: 100,
|
||
maxHealth: 100,
|
||
shield: 100,
|
||
maxShield: 100,
|
||
energy: 100,
|
||
maxEnergy: 100,
|
||
exp: 0,
|
||
maxExp: 100,
|
||
level: 1,
|
||
|
||
// 资源
|
||
crystals: 0,
|
||
ores: 0,
|
||
relics: 0,
|
||
|
||
// 升级等级
|
||
upgrades: {
|
||
engine: 1,
|
||
shield: 1,
|
||
weapon: 1,
|
||
scanner: 1,
|
||
cargo: 1,
|
||
skill: 1
|
||
}
|
||
},
|
||
|
||
// 战斗统计
|
||
combat: {
|
||
kills: 0,
|
||
totalDamage: 0,
|
||
laserCooldown: 0,
|
||
skillCooldown: 0,
|
||
skillActive: false
|
||
},
|
||
|
||
// 交互状态
|
||
interaction: {
|
||
target: null,
|
||
type: null,
|
||
progress: 0
|
||
}
|
||
};
|
||
|
||
// ==================== Three.js 全局变量 ====================
|
||
let scene, camera, renderer;
|
||
let playerShip, playerLight;
|
||
let stars, starField;
|
||
let planets = [];
|
||
let asteroids = [];
|
||
let enemies = [];
|
||
let lasers = [];
|
||
let particles = [];
|
||
let spaceDust = [];
|
||
|
||
// ==================== 输入状态 ====================
|
||
const input = {
|
||
keys: {},
|
||
mouse: { x: 0, y: 0, down: false },
|
||
mouseWorld: new THREE.Vector3()
|
||
};
|
||
|
||
// ==================== 初始化 Three.js ====================
|
||
function initThree() {
|
||
const canvas = document.getElementById('game-canvas');
|
||
|
||
// 创建场景
|
||
scene = new THREE.Scene();
|
||
scene.fog = new THREE.FogExp2(0x050510, 0.0008);
|
||
|
||
// 创建相机
|
||
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
|
||
camera.position.set(0, 50, 100);
|
||
|
||
// 创建渲染器
|
||
renderer = new THREE.WebGLRenderer({
|
||
canvas,
|
||
antialias: true,
|
||
alpha: true
|
||
});
|
||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||
renderer.setClearColor(0x050510);
|
||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||
renderer.toneMappingExposure = 1.2;
|
||
|
||
// 添加光照
|
||
const ambientLight = new THREE.AmbientLight(0x404060, 0.5);
|
||
scene.add(ambientLight);
|
||
|
||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||
directionalLight.position.set(100, 100, 50);
|
||
scene.add(directionalLight);
|
||
|
||
// 创建星空背景
|
||
createStarField();
|
||
|
||
// 创建空间尘埃
|
||
createSpaceDust();
|
||
|
||
// 创建玩家飞船
|
||
createPlayerShip();
|
||
|
||
// 创建宇宙天体
|
||
createPlanets();
|
||
createAsteroids();
|
||
createEnemies();
|
||
|
||
// 事件监听
|
||
window.addEventListener('resize', onWindowResize);
|
||
|
||
// 开始渲染循环
|
||
animate();
|
||
}
|
||
|
||
// ==================== 星空背景 ====================
|
||
function createStarField() {
|
||
const geometry = new THREE.BufferGeometry();
|
||
const positions = [];
|
||
const colors = [];
|
||
const sizes = [];
|
||
|
||
for (let i = 0; i < CONFIG.STAR_COUNT; i++) {
|
||
const x = (Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE * 2;
|
||
const y = (Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE * 2;
|
||
const z = (Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE * 2;
|
||
positions.push(x, y, z);
|
||
|
||
// 星星颜色(蓝白色为主,带有彩色点缀)
|
||
const colorChoice = Math.random();
|
||
let color;
|
||
if (colorChoice < 0.6) {
|
||
color = new THREE.Color(0xffffff);
|
||
} else if (colorChoice < 0.8) {
|
||
color = new THREE.Color(0x4CC9F0);
|
||
} else if (colorChoice < 0.95) {
|
||
color = new THREE.Color(0xFFD60A);
|
||
} else {
|
||
color = new THREE.Color(0xF72585);
|
||
}
|
||
colors.push(color.r, color.g, color.b);
|
||
|
||
sizes.push(Math.random() * 2 + 0.5);
|
||
}
|
||
|
||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
||
geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
|
||
|
||
const material = new THREE.PointsMaterial({
|
||
size: 2,
|
||
vertexColors: true,
|
||
transparent: true,
|
||
opacity: 0.8,
|
||
sizeAttenuation: true
|
||
});
|
||
|
||
stars = new THREE.Points(geometry, material);
|
||
scene.add(stars);
|
||
}
|
||
|
||
// ==================== 空间尘埃 ====================
|
||
function createSpaceDust() {
|
||
const geometry = new THREE.BufferGeometry();
|
||
const positions = [];
|
||
|
||
for (let i = 0; i < 1000; i++) {
|
||
const x = (Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE * 2;
|
||
const y = (Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE * 2;
|
||
const z = (Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE * 2;
|
||
positions.push(x, y, z);
|
||
}
|
||
|
||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||
|
||
const material = new THREE.PointsMaterial({
|
||
size: 1,
|
||
color: 0x4CC9F0,
|
||
transparent: true,
|
||
opacity: 0.3,
|
||
sizeAttenuation: true
|
||
});
|
||
|
||
spaceDust = new THREE.Points(geometry, material);
|
||
scene.add(spaceDust);
|
||
}
|
||
|
||
// ==================== 玩家飞船 ====================
|
||
function createPlayerShip() {
|
||
const group = new THREE.Group();
|
||
|
||
// 飞船主体
|
||
const bodyGeometry = new THREE.ConeGeometry(2, 8, 8);
|
||
const bodyMaterial = new THREE.MeshStandardMaterial({
|
||
color: 0x4CC9F0,
|
||
metalness: 0.8,
|
||
roughness: 0.2,
|
||
emissive: 0x4CC9F0,
|
||
emissiveIntensity: 0.2
|
||
});
|
||
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
||
body.rotation.x = Math.PI / 2;
|
||
group.add(body);
|
||
|
||
// 飞船机翼
|
||
const wingGeometry = new THREE.BoxGeometry(8, 0.2, 3);
|
||
const wingMaterial = new THREE.MeshStandardMaterial({
|
||
color: 0x7B2CBF,
|
||
metalness: 0.6,
|
||
roughness: 0.3
|
||
});
|
||
const wing = new THREE.Mesh(wingGeometry, wingMaterial);
|
||
wing.position.z = 1;
|
||
group.add(wing);
|
||
|
||
// 引擎发光部分
|
||
const engineGeometry = new THREE.CylinderGeometry(0.8, 1.2, 2, 8);
|
||
const engineMaterial = new THREE.MeshStandardMaterial({
|
||
color: 0xF72585,
|
||
emissive: 0xF72585,
|
||
emissiveIntensity: 1
|
||
});
|
||
const engine = new THREE.Mesh(engineGeometry, engineMaterial);
|
||
engine.rotation.x = Math.PI / 2;
|
||
engine.position.z = 4;
|
||
group.add(engine);
|
||
|
||
// 引擎光源
|
||
playerLight = new THREE.PointLight(0xF72585, 2, 30);
|
||
playerLight.position.z = 5;
|
||
group.add(playerLight);
|
||
|
||
// 飞船初始位置
|
||
group.position.set(0, 0, 0);
|
||
|
||
// 添加物理属性
|
||
group.userData = {
|
||
velocity: new THREE.Vector3(),
|
||
acceleration: new THREE.Vector3()
|
||
};
|
||
|
||
playerShip = group;
|
||
scene.add(playerShip);
|
||
}
|
||
|
||
// ==================== 行星创建 ====================
|
||
function createPlanets() {
|
||
for (let i = 0; i < CONFIG.PLANET_COUNT; i++) {
|
||
const planet = createRandomPlanet();
|
||
planets.push(planet);
|
||
scene.add(planet);
|
||
}
|
||
}
|
||
|
||
function createRandomPlanet() {
|
||
const group = new THREE.Group();
|
||
|
||
// 随机大小和颜色
|
||
const radius = Math.random() * 20 + 10;
|
||
const colors = [0x4CC9F0, 0x7B2CBF, 0xF72585, 0xFFD60A, 0x00ff88, 0xff6b6b, 0xc44dff];
|
||
const baseColor = colors[Math.floor(Math.random() * colors.length)];
|
||
|
||
// 行星主体
|
||
const geometry = new THREE.SphereGeometry(radius, 32, 32);
|
||
const material = new THREE.MeshStandardMaterial({
|
||
color: baseColor,
|
||
metalness: 0.3,
|
||
roughness: 0.7,
|
||
emissive: baseColor,
|
||
emissiveIntensity: 0.1
|
||
});
|
||
const planet = new THREE.Mesh(geometry, material);
|
||
group.add(planet);
|
||
|
||
// 大气层光环
|
||
const atmosphereGeometry = new THREE.SphereGeometry(radius * 1.2, 32, 32);
|
||
const atmosphereMaterial = new THREE.MeshBasicMaterial({
|
||
color: baseColor,
|
||
transparent: true,
|
||
opacity: 0.15,
|
||
side: THREE.BackSide
|
||
});
|
||
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
|
||
group.add(atmosphere);
|
||
|
||
// 随机位置
|
||
group.position.set(
|
||
(Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE,
|
||
(Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE,
|
||
(Math.random() - 0.5) * CONFIG.UNIVERSE_SIZE
|
||
);
|
||
|
||
// 随机自转
|
||
group.userData = {
|
||
type: 'planet',
|
||
radius: radius,
|
||
color: baseColor,
|
||
rotationSpeed: (Math.random() - 0.5) * 0.01,
|
||
hasResources: Math.random() > 0.3,
|
||
resourceType: ['crystal', 'ore', 'relic'][Math.floor(Math.random() * 3)],
|
||
resourceAmount: Math.floor(Math.random() * 50) + 20,
|
||
name: `行星-${Math.floor(Math.random() * 9999)}`,
|
||
scanned: false
|
||
};
|
||
|
||
return group;
|
||
}
|
||
|
||
// ==================== 小行星带 ====================
|
||
function createAsteroids() {
|
||
for (let i = 0; i < CONFIG.ASTEROID_COUNT; i++) {
|
||
const asteroid = createRandomAsteroid();
|
||
asteroids.push(asteroid);
|
||
scene.add(asteroid);
|
||
}
|
||
}
|
||
|
||
function createRandomAsteroid() {
|
||
const geometry = new THREE.DodecahedronGeometry(Math.random() * 3 + 1, 0);
|
||
const material = new THREE.MeshStandardMaterial({
|
||
color: 0x666666,
|
||
metalness: 0.5,
|
||
roughness: 0.8
|
||
});
|
||
const asteroid = new THREE.Mesh(geometry, material);
|
||
|
||
// 在特定区域生成小行星
|
||
const angle = Math.random() * Math.PI * 2;
|
||
const distance = Math.random() * 500 + 200;
|
||
asteroid.position.set(
|
||
Math.cos(angle) * distance,
|
||
(Math.random() - 0.5) * 100,
|
||
Math.sin(angle) * distance
|
||
);
|
||
|
||
asteroid.userData = {
|
||
type: 'asteroid',
|
||
health: 50,
|
||
rotationSpeed: (Math.random() - 0.5) * 0.02
|
||
};
|
||
|
||
return asteroid;
|
||
}
|
||
|
||
// ==================== 敌人创建 ====================
|
||
function createEnemies() {
|
||
for (let i = 0; i < CONFIG.ENEMY_COUNT; i++) {
|
||
const enemy = createRandomEnemy();
|
||
enemies.push(enemy);
|
||
scene.add(enemy);
|
||
}
|
||
}
|
||
|
||
function createRandomEnemy() {
|
||
const group = new THREE.Group();
|
||
|
||
// 敌机主体
|
||
const geometry = new THREE.OctahedronGeometry(3, 0);
|
||
const material = new THREE.MeshStandardMaterial({
|
||
color: 0xF72585,
|
||
metalness: 0.7,
|
||
roughness: 0.3,
|
||
emissive: 0xF72585,
|
||
emissiveIntensity: 0.3
|
||
});
|
||
const body = new THREE.Mesh(geometry, material);
|
||
group.add(body);
|
||
|
||
// 敌机光源
|
||
const light = new THREE.PointLight(0xF72585, 1, 20);
|
||
light.position.y = 2;
|
||
group.add(light);
|
||
|
||
// 随机位置(在玩家周围)
|
||
const angle = Math.random() * Math.PI * 2;
|
||
const distance = Math.random() * 500 + 300;
|
||
group.position.set(
|
||
Math.cos(angle) * distance,
|
||
(Math.random() - 0.5) * 200,
|
||
Math.sin(angle) * distance
|
||
);
|
||
|
||
group.userData = {
|
||
type: 'enemy',
|
||
health: 100,
|
||
maxHealth: 100,
|
||
speed: 20 + Math.random() * 20,
|
||
rotationSpeed: 0.02,
|
||
lastShot: 0,
|
||
shootCooldown: 2 + Math.random() * 2
|
||
};
|
||
|
||
return group;
|
||
}
|
||
|
||
// ==================== 激光系统 ====================
|
||
function createLaser(from, to, isEnemy = false) {
|
||
const start = from.clone();
|
||
const end = to.clone();
|
||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||
const distance = start.distanceTo(end);
|
||
|
||
const geometry = new THREE.CylinderGeometry(0.2, 0.2, distance, 8);
|
||
const material = new THREE.MeshBasicMaterial({
|
||
color: isEnemy ? 0xFF0000 : 0x4CC9F0,
|
||
transparent: true,
|
||
opacity: 0.8
|
||
});
|
||
|
||
const laser = new THREE.Mesh(geometry, material);
|
||
laser.position.copy(start).add(direction.multiplyScalar(distance / 2));
|
||
laser.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||
|
||
laser.userData = {
|
||
start: start,
|
||
end: end,
|
||
direction: direction,
|
||
speed: CONFIG.LASER_SPEED,
|
||
damage: isEnemy ? 10 : CONFIG.LASER_DAMAGE * gameState.player.upgrades.weapon,
|
||
isEnemy: isEnemy,
|
||
createdAt: gameState.lastTime
|
||
};
|
||
|
||
lasers.push(laser);
|
||
scene.add(laser);
|
||
|
||
// 创建激光粒子尾迹
|
||
createLaserTrail(start, isEnemy);
|
||
|
||
return laser;
|
||
}
|
||
|
||
function createLaserTrail(position, isEnemy) {
|
||
for (let i = 0; i < 5; i++) {
|
||
const particle = createParticle(
|
||
position.clone().add(new THREE.Vector3(
|
||
(Math.random() - 0.5) * 2,
|
||
(Math.random() - 0.5) * 2,
|
||
(Math.random() - 0.5) * 2
|
||
)),
|
||
isEnemy ? 0xFF0000 : 0x4CC9F0,
|
||
0.5
|
||
);
|
||
particle.userData.velocity = new THREE.Vector3(
|
||
(Math.random() - 0.5) * 10,
|
||
(Math.random() - 0.5) * 10,
|
||
(Math.random() - 0.5) * 10
|
||
);
|
||
}
|
||
}
|
||
|
||
// ==================== 粒子系统 ====================
|
||
function createParticle(position, color, life = 1) {
|
||
const geometry = new THREE.SphereGeometry(0.3, 4, 4);
|
||
const material = new THREE.MeshBasicMaterial({
|
||
color: color,
|
||
transparent: true,
|
||
opacity: 1
|
||
});
|
||
|
||
const particle = new THREE.Mesh(geometry, material);
|
||
particle.position.copy(position);
|
||
particle.userData = {
|
||
life: life,
|
||
maxLife: life,
|
||
velocity: new THREE.Vector3(
|
||
(Math.random() - 0.5) * 5,
|
||
(Math.random() - 0.5) * 5,
|
||
(Math.random() - 0.5) * 5
|
||
)
|
||
};
|
||
|
||
particles.push(particle);
|
||
scene.add(particle);
|
||
|
||
return particle;
|
||
}
|
||
|
||
function createExplosion(position, color = 0xF72585, count = 20) {
|
||
for (let i = 0; i < count; i++) {
|
||
const particle = createParticle(position.clone(), color, 1.5);
|
||
const angle = Math.random() * Math.PI * 2;
|
||
const speed = Math.random() * 30 + 10;
|
||
particle.userData.velocity = new THREE.Vector3(
|
||
Math.cos(angle) * speed,
|
||
(Math.random() - 0.5) * speed,
|
||
Math.sin(angle) * speed
|
||
);
|
||
}
|
||
|
||
// 创建冲击波
|
||
createShockwave(position);
|
||
}
|
||
|
||
function createShockwave(position) {
|
||
const geometry = new THREE.RingGeometry(0.5, 1, 32);
|
||
const material = new THREE.MeshBasicMaterial({
|
||
color: 0x4CC9F0,
|
||
transparent: true,
|
||
opacity: 0.8,
|
||
side: THREE.DoubleSide
|
||
});
|
||
|
||
const shockwave = new THREE.Mesh(geometry, material);
|
||
shockwave.position.copy(position);
|
||
shockwave.lookAt(camera.position);
|
||
|
||
shockwave.userData = {
|
||
type: 'shockwave',
|
||
life: 0.5,
|
||
maxLife: 0.5
|
||
};
|
||
|
||
particles.push(shockwave);
|
||
scene.add(shockwave);
|
||
}
|
||
|
||
// ==================== 引擎尾焰 ====================
|
||
function createEngineTrail() {
|
||
const trailPos = playerShip.position.clone();
|
||
trailPos.z += 6;
|
||
trailPos.add(new THREE.Vector3(
|
||
(Math.random() - 0.5) * 2,
|
||
(Math.random() - 0.5) * 2,
|
||
0
|
||
));
|
||
|
||
const particle = createParticle(trailPos, 0xF72585, 0.3);
|
||
particle.userData.velocity = new THREE.Vector3(0, 0, -20);
|
||
}
|
||
|
||
// ==================== UI 更新 ====================
|
||
function updateUI() {
|
||
// 状态栏
|
||
const shieldPercent = (gameState.player.shield / gameState.player.maxShield) * 100;
|
||
const healthPercent = (gameState.player.health / gameState.player.maxHealth) * 100;
|
||
const energyPercent = (gameState.player.energy / gameState.player.maxEnergy) * 100;
|
||
|
||
document.getElementById('shield-bar').style.width = shieldPercent + '%';
|
||
document.getElementById('health-bar').style.width = healthPercent + '%';
|
||
document.getElementById('energy-bar').style.width = energyPercent + '%';
|
||
|
||
document.getElementById('shield-text').textContent = Math.round(shieldPercent) + '%';
|
||
document.getElementById('health-text').textContent = Math.round(healthPercent) + '%';
|
||
document.getElementById('energy-text').textContent = Math.round(energyPercent) + '%';
|
||
|
||
// 资源
|
||
document.getElementById('crystal-count').textContent = gameState.player.crystals;
|
||
document.getElementById('ore-count').textContent = gameState.player.ores;
|
||
document.getElementById('relic-count').textContent = gameState.player.relics;
|
||
|
||
// 经验
|
||
const expPercent = (gameState.player.exp / gameState.player.maxExp) * 100;
|
||
document.getElementById('exp-bar').style.width = expPercent + '%';
|
||
document.getElementById('level-text').textContent = gameState.player.level;
|
||
|
||
// 战斗统计
|
||
document.getElementById('kill-count').textContent = gameState.combat.kills;
|
||
document.getElementById('damage-count').textContent = Math.floor(gameState.combat.totalDamage);
|
||
|
||
// 警告
|
||
const warningOverlay = document.getElementById('warning-overlay');
|
||
if (healthPercent < 30) {
|
||
warningOverlay.classList.add('active');
|
||
} else {
|
||
warningOverlay.classList.remove('active');
|
||
}
|
||
|
||
// 技能冷却
|
||
if (gameState.combat.skillCooldown > 0) {
|
||
document.getElementById('energy-text').textContent = `CD: ${Math.ceil(gameState.combat.skillCooldown)}s`;
|
||
}
|
||
}
|
||
|
||
// ==================== 消息系统 ====================
|
||
function showMessage(text, type = 'info') {
|
||
const log = document.getElementById('message-log');
|
||
const message = document.createElement('div');
|
||
message.className = `log-message ${type}`;
|
||
message.textContent = text;
|
||
log.appendChild(message);
|
||
|
||
setTimeout(() => {
|
||
message.remove();
|
||
}, 4500);
|
||
}
|
||
|
||
// ==================== 交互系统 ====================
|
||
function checkInteractions(deltaTime) {
|
||
let nearestTarget = null;
|
||
let nearestDistance = Infinity;
|
||
let interactionType = null;
|
||
|
||
// 检查行星
|
||
for (const planet of planets) {
|
||
if (!planet.userData.hasResources) continue;
|
||
|
||
const distance = playerShip.position.distanceTo(planet.position);
|
||
if (distance < planet.userData.radius + 50 && distance < nearestDistance) {
|
||
nearestTarget = planet;
|
||
nearestDistance = distance;
|
||
interactionType = 'harvest';
|
||
}
|
||
}
|
||
|
||
// 更新交互状态
|
||
gameState.interaction.target = nearestTarget;
|
||
gameState.interaction.type = interactionType;
|
||
|
||
const promptEl = document.getElementById('interaction-prompt');
|
||
const promptText = document.getElementById('prompt-text');
|
||
|
||
if (nearestTarget && interactionType === 'harvest') {
|
||
const resourceName = {
|
||
'crystal': '高能晶体',
|
||
'ore': '稀有矿物',
|
||
'relic': '古老遗迹碎片'
|
||
};
|
||
promptText.textContent = `按 E 采集 ${resourceName[nearestTarget.userData.resourceType]}`;
|
||
promptEl.classList.add('visible');
|
||
} else {
|
||
promptEl.classList.remove('visible');
|
||
}
|
||
}
|
||
|
||
function performInteraction() {
|
||
const { target, type } = gameState.interaction;
|
||
if (!target || type !== 'harvest') return;
|
||
|
||
const data = target.userData;
|
||
if (!data.hasResources) return;
|
||
|
||
// 采集资源
|
||
const amount = Math.floor(Math.random() * 5) + 3;
|
||
const resourceType = data.resourceType;
|
||
|
||
if (resourceType === 'crystal') {
|
||
gameState.player.crystals += amount;
|
||
} else if (resourceType === 'ore') {
|
||
gameState.player.ores += amount;
|
||
} else {
|
||
gameState.player.relics += amount;
|
||
}
|
||
|
||
data.resourceAmount -= amount;
|
||
if (data.resourceAmount <= 0) {
|
||
data.hasResources = false;
|
||
// 改变星球外观表示已采集
|
||
target.children[0].material.emissiveIntensity = 0;
|
||
}
|
||
|
||
showMessage(`获得: ${resourceType} x${amount}`, 'success');
|
||
|
||
// 采集特效
|
||
createExplosion(playerShip.position.clone().add(new THREE.Vector3(0, 0, -10)), 0xFFD60A, 10);
|
||
|
||
// 获得经验
|
||
gainExp(amount * 2);
|
||
|
||
updateUI();
|
||
}
|
||
|
||
// ==================== 经验系统 ====================
|
||
function gainExp(amount) {
|
||
gameState.player.exp += amount;
|
||
|
||
if (gameState.player.exp >= gameState.player.maxExp) {
|
||
levelUp();
|
||
}
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function levelUp() {
|
||
gameState.player.level++;
|
||
gameState.player.exp = gameState.player.exp - gameState.player.maxExp;
|
||
gameState.player.maxExp = Math.floor(gameState.player.maxExp * 1.5);
|
||
|
||
// 属性提升
|
||
gameState.player.maxHealth += 20;
|
||
gameState.player.maxShield += 20;
|
||
gameState.player.health = gameState.player.maxHealth;
|
||
gameState.player.shield = gameState.player.maxShield;
|
||
|
||
showMessage(`等级提升!当前等级: ${gameState.player.level}`, 'success');
|
||
|
||
// 升级特效
|
||
createExplosion(playerShip.position, 0x4CC9F0, 50);
|
||
}
|
||
|
||
// ==================== 战斗系统 ====================
|
||
function updateCombat(deltaTime) {
|
||
// 激光冷却
|
||
if (gameState.combat.laserCooldown > 0) {
|
||
gameState.combat.laserCooldown -= deltaTime;
|
||
}
|
||
|
||
// 技能冷却
|
||
if (gameState.combat.skillCooldown > 0) {
|
||
gameState.combat.skillCooldown -= deltaTime;
|
||
}
|
||
|
||
// 能量恢复
|
||
if (gameState.player.energy < gameState.player.maxEnergy) {
|
||
gameState.player.energy += deltaTime * 5;
|
||
if (gameState.player.energy > gameState.player.maxEnergy) {
|
||
gameState.player.energy = gameState.player.maxEnergy;
|
||
}
|
||
}
|
||
|
||
// 护盾恢复
|
||
if (gameState.player.shield < gameState.player.maxShield && gameState.player.energy > 10) {
|
||
gameState.player.shield += deltaTime * 2;
|
||
}
|
||
|
||
// 更新激光
|
||
for (let i = lasers.length - 1; i >= 0; i--) {
|
||
const laser = lasers[i];
|
||
const data = laser.userData;
|
||
|
||
// 激光移动
|
||
const moveDistance = data.speed * deltaTime;
|
||
laser.position.add(data.direction.clone().multiplyScalar(moveDistance));
|
||
|
||
// 激光生命周期
|
||
if (gameState.lastTime - data.createdAt > 3) {
|
||
removeLaser(i);
|
||
continue;
|
||
}
|
||
|
||
// 碰撞检测
|
||
if (!data.isEnemy) {
|
||
// 检测敌机
|
||
for (let j = enemies.length - 1; j >= 0; j--) {
|
||
const enemy = enemies[j];
|
||
if (laser.position.distanceTo(enemy.position) < 5) {
|
||
damageEnemy(enemy, data.damage);
|
||
removeLaser(i);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 检测小行星
|
||
for (let j = asteroids.length - 1; j >= 0; j--) {
|
||
const asteroid = asteroids[j];
|
||
if (laser.position.distanceTo(asteroid.position) < 3) {
|
||
damageAsteroid(asteroid, data.damage);
|
||
removeLaser(i);
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// 检测玩家
|
||
if (laser.position.distanceTo(playerShip.position) < 3) {
|
||
damagePlayer(data.damage);
|
||
removeLaser(i);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新敌人AI
|
||
updateEnemyAI(deltaTime);
|
||
}
|
||
|
||
function updateEnemyAI(deltaTime) {
|
||
const now = gameState.lastTime;
|
||
|
||
for (const enemy of enemies) {
|
||
const data = enemy.userData;
|
||
|
||
// 追踪玩家
|
||
const toPlayer = new THREE.Vector3().subVectors(playerShip.position, enemy.position);
|
||
const distance = toPlayer.length();
|
||
|
||
if (distance > 100) {
|
||
toPlayer.normalize();
|
||
enemy.position.add(toPlayer.multiplyScalar(data.speed * deltaTime));
|
||
}
|
||
|
||
// 转向玩家
|
||
enemy.lookAt(playerShip.position);
|
||
|
||
// 射击
|
||
if (distance < 300 && now - data.lastShot > data.shootCooldown) {
|
||
data.lastShot = now;
|
||
const direction = toPlayer.normalize();
|
||
const target = enemy.position.clone().add(direction.multiplyScalar(100));
|
||
createLaser(enemy.position, target, true);
|
||
}
|
||
|
||
// 自转动画
|
||
enemy.rotation.x += data.rotationSpeed;
|
||
enemy.rotation.y += data.rotationSpeed;
|
||
}
|
||
}
|
||
|
||
function damageEnemy(enemy, damage) {
|
||
enemy.userData.health -= damage;
|
||
createExplosion(enemy.position, 0xF72585, 10);
|
||
|
||
gameState.combat.totalDamage += damage;
|
||
|
||
if (enemy.userData.health <= 0) {
|
||
destroyEnemy(enemy);
|
||
}
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function destroyEnemy(enemy) {
|
||
createExplosion(enemy.position, 0xF72585, 30);
|
||
|
||
// 掉落资源
|
||
const dropType = Math.random();
|
||
if (dropType < 0.6) {
|
||
gameState.player.crystals += Math.floor(Math.random() * 10) + 5;
|
||
showMessage('获得: 高能晶体 x' + (Math.floor(Math.random() * 10) + 5), 'success');
|
||
} else if (dropType < 0.9) {
|
||
gameState.player.ores += Math.floor(Math.random() * 5) + 3;
|
||
showMessage('获得: 稀有矿物 x' + (Math.floor(Math.random() * 5) + 3), 'success');
|
||
} else {
|
||
gameState.player.relics += 1;
|
||
showMessage('获得: 古老遗迹碎片 x1', 'success');
|
||
}
|
||
|
||
// 经验奖励
|
||
gainExp(50);
|
||
|
||
// 移除敌人
|
||
const index = enemies.indexOf(enemy);
|
||
if (index > -1) {
|
||
enemies.splice(index, 1);
|
||
}
|
||
scene.remove(enemy);
|
||
|
||
gameState.combat.kills++;
|
||
updateUI();
|
||
}
|
||
|
||
function damageAsteroid(asteroid, damage) {
|
||
asteroid.userData.health -= damage;
|
||
createExplosion(asteroid.position, 0x888888, 5);
|
||
|
||
if (asteroid.userData.health <= 0) {
|
||
destroyAsteroid(asteroid);
|
||
}
|
||
}
|
||
|
||
function destroyAsteroid(asteroid) {
|
||
createExplosion(asteroid.position, 0x666666, 15);
|
||
|
||
// 获得少量矿石
|
||
gameState.player.ores += Math.floor(Math.random() * 3) + 1;
|
||
|
||
const index = asteroids.indexOf(asteroid);
|
||
if (index > -1) {
|
||
asteroids.splice(index, 1);
|
||
}
|
||
scene.remove(asteroid);
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function damagePlayer(damage) {
|
||
// 先扣除护盾
|
||
if (gameState.player.shield > 0) {
|
||
gameState.player.shield -= damage;
|
||
if (gameState.player.shield < 0) {
|
||
gameState.player.health += gameState.player.shield;
|
||
gameState.player.shield = 0;
|
||
}
|
||
} else {
|
||
gameState.player.health -= damage;
|
||
}
|
||
|
||
createExplosion(playerShip.position.clone().add(new THREE.Vector3(0, 0, -5)), 0xFF0000, 10);
|
||
|
||
// 屏幕震动效果
|
||
gsap.to(camera.position, {
|
||
x: camera.position.x + (Math.random() - 0.5) * 2,
|
||
y: camera.position.y + (Math.random() - 0.5) * 2,
|
||
duration: 0.05,
|
||
onComplete: () => {
|
||
gsap.to(camera.position, {
|
||
x: playerShip.position.x,
|
||
y: playerShip.position.y + 50,
|
||
duration: 0.2
|
||
});
|
||
}
|
||
});
|
||
|
||
if (gameState.player.health <= 0) {
|
||
gameOver();
|
||
}
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function removeLaser(index) {
|
||
const laser = lasers[index];
|
||
scene.remove(laser);
|
||
lasers.splice(index, 1);
|
||
}
|
||
|
||
// ==================== 飞船控制 ====================
|
||
function updateShip(deltaTime) {
|
||
const data = playerShip.userData;
|
||
const moveSpeed = CONFIG.SHIP_SPEED * gameState.player.upgrades.engine * 0.5;
|
||
|
||
// 计算移动方向
|
||
const moveDirection = new THREE.Vector3();
|
||
|
||
if (input.keys['KeyW'] || input.keys['ArrowUp']) {
|
||
moveDirection.z -= 1;
|
||
}
|
||
if (input.keys['KeyS'] || input.keys['ArrowDown']) {
|
||
moveDirection.z += 1;
|
||
}
|
||
if (input.keys['KeyA'] || input.keys['ArrowLeft']) {
|
||
moveDirection.x -= 1;
|
||
}
|
||
if (input.keys['KeyD'] || input.keys['ArrowRight']) {
|
||
moveDirection.x += 1;
|
||
}
|
||
|
||
if (input.keys['Space']) {
|
||
moveDirection.y += 1;
|
||
}
|
||
if (input.keys['ShiftLeft'] || input.keys['ShiftRight']) {
|
||
moveDirection.y -= 1;
|
||
}
|
||
|
||
// 应用加速度
|
||
if (moveDirection.length() > 0) {
|
||
moveDirection.normalize();
|
||
data.velocity.add(moveDirection.multiplyScalar(moveSpeed * deltaTime));
|
||
|
||
// 创建引擎尾焰
|
||
if (Math.random() > 0.5) {
|
||
createEngineTrail();
|
||
}
|
||
}
|
||
|
||
// 应用摩擦力
|
||
data.velocity.multiplyScalar(CONFIG.SHIP_FRICTION);
|
||
|
||
// 更新位置
|
||
playerShip.position.add(data.velocity.clone().multiplyScalar(deltaTime));
|
||
|
||
// 飞船倾斜效果
|
||
playerShip.rotation.z = -data.velocity.x * CONFIG.SHIP_TILT_FACTOR;
|
||
playerShip.rotation.x = data.velocity.z * CONFIG.SHIP_TILT_FACTOR * 0.5;
|
||
|
||
// 根据鼠标位置旋转飞船
|
||
const targetRotation = Math.atan2(
|
||
input.mouseWorld.x - playerShip.position.x,
|
||
input.mouseWorld.z - playerShip.position.z
|
||
);
|
||
playerShip.rotation.y = targetRotation + Math.PI / 2;
|
||
|
||
// 限制边界
|
||
const limit = CONFIG.UNIVERSE_SIZE * 0.8;
|
||
playerShip.position.x = Math.max(-limit, Math.min(limit, playerShip.position.x));
|
||
playerShip.position.y = Math.max(-limit, Math.min(limit, playerShip.position.y));
|
||
playerShip.position.z = Math.max(-limit, Math.min(limit, playerShip.position.z));
|
||
|
||
// 相机跟随
|
||
const targetCameraPos = new THREE.Vector3(
|
||
playerShip.position.x,
|
||
playerShip.position.y + 50,
|
||
playerShip.position.z + 80
|
||
);
|
||
camera.position.lerp(targetCameraPos, 0.05);
|
||
camera.lookAt(playerShip.position);
|
||
}
|
||
|
||
// ==================== 射击控制 ====================
|
||
function shoot() {
|
||
if (gameState.combat.laserCooldown > 0) return;
|
||
|
||
gameState.combat.laserCooldown = CONFIG.LASER_COOLDOWN;
|
||
|
||
const direction = new THREE.Vector3(0, 0, -1);
|
||
direction.applyQuaternion(playerShip.quaternion);
|
||
|
||
const start = playerShip.position.clone();
|
||
const end = start.clone().add(direction.multiplyScalar(200));
|
||
|
||
createLaser(start, end, false);
|
||
|
||
// 后坐力
|
||
gsap.to(playerShip.position, {
|
||
z: playerShip.position.z + 0.5,
|
||
duration: 0.05,
|
||
yoyo: true,
|
||
repeat: 1
|
||
});
|
||
}
|
||
|
||
// ==================== 技能系统 ====================
|
||
function activateSkill() {
|
||
if (gameState.combat.skillCooldown > 0) {
|
||
showMessage('技能冷却中...', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (gameState.player.energy < 50) {
|
||
showMessage('能量不足!', 'warning');
|
||
return;
|
||
}
|
||
|
||
gameState.player.energy -= 50;
|
||
gameState.combat.skillCooldown = CONFIG.SKILL_COOLDOWN;
|
||
gameState.combat.skillActive = true;
|
||
|
||
// 全屏攻击特效
|
||
createExplosion(playerShip.position, 0x4CC9F0, 100);
|
||
|
||
// 伤害范围内所有敌人
|
||
for (let i = enemies.length - 1; i >= 0; i--) {
|
||
const enemy = enemies[i];
|
||
if (playerShip.position.distanceTo(enemy.position) < 200) {
|
||
damageEnemy(enemy, 100);
|
||
}
|
||
}
|
||
|
||
showMessage('技能激活: 超新星爆发!', 'success');
|
||
updateUI();
|
||
}
|
||
|
||
// ==================== 粒子更新 ====================
|
||
function updateParticles(deltaTime) {
|
||
for (let i = particles.length - 1; i >= 0; i--) {
|
||
const particle = particles[i];
|
||
const data = particle.userData;
|
||
|
||
// 更新生命周期
|
||
data.life -= deltaTime;
|
||
|
||
if (data.life <= 0) {
|
||
scene.remove(particle);
|
||
particles.splice(i, 1);
|
||
continue;
|
||
}
|
||
|
||
// 更新位置
|
||
if (data.velocity) {
|
||
particle.position.add(data.velocity.clone().multiplyScalar(deltaTime));
|
||
data.velocity.multiplyScalar(0.95);
|
||
}
|
||
|
||
// 更新透明度
|
||
if (particle.material) {
|
||
particle.material.opacity = data.life / data.maxLife;
|
||
}
|
||
|
||
// 冲击波放大
|
||
if (data.type === 'shockwave') {
|
||
const scale = 1 + (1 - data.life / data.maxLife) * 10;
|
||
particle.scale.set(scale, scale, scale);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==================== 天体更新 ====================
|
||
function updateCelestialBodies(deltaTime) {
|
||
// 更新行星
|
||
for (const planet of planets) {
|
||
planet.rotation.y += planet.userData.rotationSpeed;
|
||
}
|
||
|
||
// 更新小行星
|
||
for (const asteroid of asteroids) {
|
||
asteroid.rotation.x += asteroid.userData.rotationSpeed;
|
||
asteroid.rotation.y += asteroid.userData.rotationSpeed;
|
||
}
|
||
|
||
// 星空视差滚动
|
||
if (stars) {
|
||
stars.position.x = playerShip.position.x * 0.1;
|
||
stars.position.y = playerShip.position.y * 0.1;
|
||
stars.position.z = playerShip.position.z * 0.1;
|
||
}
|
||
}
|
||
|
||
// ==================== 小地图更新 ====================
|
||
function updateMinimap() {
|
||
const canvas = document.getElementById('minimap-canvas');
|
||
const ctx = canvas.getContext('2d');
|
||
const size = 180;
|
||
const scale = size / CONFIG.UNIVERSE_SIZE;
|
||
|
||
canvas.width = size;
|
||
canvas.height = size;
|
||
|
||
// 清空
|
||
ctx.fillStyle = 'rgba(5, 5, 16, 0.9)';
|
||
ctx.beginPath();
|
||
ctx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// 绘制边框
|
||
ctx.strokeStyle = 'rgba(76, 201, 240, 0.5)';
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
ctx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
|
||
// 绘制玩家
|
||
const playerX = size / 2 + playerShip.position.x * scale;
|
||
const playerY = size / 2 + playerShip.position.z * scale;
|
||
|
||
ctx.fillStyle = '#4CC9F0';
|
||
ctx.beginPath();
|
||
ctx.arc(playerX, playerY, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// 绘制敌人
|
||
ctx.fillStyle = '#F72585';
|
||
for (const enemy of enemies) {
|
||
const x = size / 2 + enemy.position.x * scale;
|
||
const y = size / 2 + enemy.position.z * scale;
|
||
ctx.beginPath();
|
||
ctx.arc(x, y, 2, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
}
|
||
|
||
// 绘制行星
|
||
ctx.fillStyle = '#7B2CBF';
|
||
for (const planet of planets) {
|
||
const x = size / 2 + planet.position.x * scale;
|
||
const y = size / 2 + planet.position.z * scale;
|
||
ctx.beginPath();
|
||
ctx.arc(x, y, 3, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
}
|
||
}
|
||
|
||
// ==================== 游戏结束 ====================
|
||
function gameOver() {
|
||
gameState.isPlaying = false;
|
||
|
||
showMessage('飞船已损毁!', 'danger');
|
||
|
||
setTimeout(() => {
|
||
document.getElementById('pause-menu').classList.add('visible');
|
||
document.querySelector('.pause-title').textContent = '💀 游戏结束';
|
||
document.getElementById('resume-btn').style.display = 'none';
|
||
}, 1000);
|
||
}
|
||
|
||
// ==================== 游戏循环 ====================
|
||
function animate(currentTime = 0) {
|
||
requestAnimationFrame(animate);
|
||
|
||
const deltaTime = Math.min((currentTime - gameState.lastTime) / 1000, 0.1);
|
||
gameState.lastTime = currentTime;
|
||
|
||
if (!gameState.isPlaying || gameState.isPaused || gameState.isUpgrading) {
|
||
renderer.render(scene, camera);
|
||
return;
|
||
}
|
||
|
||
// 更新飞船
|
||
updateShip(deltaTime);
|
||
|
||
// 更新战斗
|
||
updateCombat(deltaTime);
|
||
|
||
// 检查交互
|
||
checkInteractions(deltaTime);
|
||
|
||
// 更新粒子
|
||
updateParticles(deltaTime);
|
||
|
||
// 更新天体
|
||
updateCelestialBodies(deltaTime);
|
||
|
||
// 更新小地图
|
||
updateMinimap();
|
||
|
||
// 渲染场景
|
||
renderer.render(scene, camera);
|
||
}
|
||
|
||
// ==================== 升级系统 ====================
|
||
function updateUpgradePanel() {
|
||
const upgrades = gameState.player.upgrades;
|
||
|
||
// 更新每个升级卡片
|
||
for (const [key, level] of Object.entries(upgrades)) {
|
||
const cost = Math.floor(CONFIG.BASE_UPGRADE_COST * Math.pow(CONFIG.UPGRADE_COST_MULTIPLIER, level - 1));
|
||
document.getElementById(`${key}-level`).textContent = `等级 ${level}`;
|
||
document.getElementById(`${key}-cost`).textContent = `消耗: ${cost} 晶体`;
|
||
}
|
||
}
|
||
|
||
function purchaseUpgrade(type) {
|
||
const level = gameState.player.upgrades[type];
|
||
const cost = Math.floor(CONFIG.BASE_UPGRADE_COST * Math.pow(CONFIG.UPGRADE_COST_MULTIPLIER, level - 1));
|
||
|
||
if (gameState.player.crystals < cost) {
|
||
showMessage('晶体不足!', 'warning');
|
||
return;
|
||
}
|
||
|
||
gameState.player.crystals -= cost;
|
||
gameState.player.upgrades[type]++;
|
||
|
||
// 应用升级效果
|
||
applyUpgrade(type);
|
||
|
||
updateUpgradePanel();
|
||
updateUI();
|
||
showMessage(`${type} 升级成功!`, 'success');
|
||
}
|
||
|
||
function applyUpgrade(type) {
|
||
const level = gameState.player.upgrades[type];
|
||
|
||
switch (type) {
|
||
case 'engine':
|
||
CONFIG.SHIP_SPEED = 50 + (level - 1) * 10;
|
||
break;
|
||
case 'shield':
|
||
gameState.player.maxShield = 100 + (level - 1) * 20;
|
||
gameState.player.shield = gameState.player.maxShield;
|
||
break;
|
||
case 'weapon':
|
||
CONFIG.LASER_DAMAGE = 25 + (level - 1) * 10;
|
||
break;
|
||
case 'cargo':
|
||
CONFIG.RESOURCE_MAX_CARGO = 100 + (level - 1) * 50;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ==================== 窗口调整 ====================
|
||
function onWindowResize() {
|
||
camera.aspect = window.innerWidth / window.innerHeight;
|
||
camera.updateProjectionMatrix();
|
||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
}
|
||
|
||
// ==================== 输入处理 ====================
|
||
function setupInputHandlers() {
|
||
// 键盘按下
|
||
window.addEventListener('keydown', (e) => {
|
||
input.keys[e.code] = true;
|
||
|
||
// E 键交互
|
||
if (e.code === 'KeyE' && gameState.isPlaying && !gameState.isPaused) {
|
||
performInteraction();
|
||
}
|
||
|
||
// U 键升级面板
|
||
if (e.code === 'KeyU' && gameState.isPlaying) {
|
||
toggleUpgradePanel();
|
||
}
|
||
|
||
// 空格键技能
|
||
if (e.code === 'Space' && gameState.isPlaying && !gameState.isPaused) {
|
||
activateSkill();
|
||
}
|
||
|
||
// ESC 暂停
|
||
if (e.code === 'Escape') {
|
||
togglePause();
|
||
}
|
||
});
|
||
|
||
// 键盘释放
|
||
window.addEventListener('keyup', (e) => {
|
||
input.keys[e.code] = false;
|
||
});
|
||
|
||
// 鼠标移动
|
||
window.addEventListener('mousemove', (e) => {
|
||
input.mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||
input.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
||
|
||
// 计算鼠标在3D世界中的位置
|
||
const raycaster = new THREE.Raycaster();
|
||
raycaster.setFromCamera(input.mouse, camera);
|
||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||
const target = new THREE.Vector3();
|
||
raycaster.ray.intersectPlane(plane, target);
|
||
if (target) {
|
||
input.mouseWorld.copy(target);
|
||
}
|
||
});
|
||
|
||
// 鼠标点击
|
||
window.addEventListener('mousedown', (e) => {
|
||
if (e.button === 0 && gameState.isPlaying && !gameState.isPaused) {
|
||
input.mouse.down = true;
|
||
shoot();
|
||
}
|
||
});
|
||
|
||
window.addEventListener('mouseup', (e) => {
|
||
if (e.button === 0) {
|
||
input.mouse.down = false;
|
||
}
|
||
});
|
||
|
||
// UI 按钮事件
|
||
document.getElementById('start-btn').addEventListener('click', startGame);
|
||
document.getElementById('resume-btn').addEventListener('click', togglePause);
|
||
document.getElementById('restart-btn').addEventListener('click', restartGame);
|
||
document.getElementById('quit-btn').addEventListener('click', quitGame);
|
||
document.getElementById('close-upgrade').addEventListener('click', toggleUpgradePanel);
|
||
|
||
// 升级卡片点击
|
||
document.querySelectorAll('.upgrade-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
const upgradeType = card.dataset.upgrade;
|
||
purchaseUpgrade(upgradeType);
|
||
});
|
||
});
|
||
}
|
||
|
||
// ==================== 游戏控制 ====================
|
||
function startGame() {
|
||
document.getElementById('start-screen').classList.add('hidden');
|
||
document.getElementById('status-bar').style.display = 'flex';
|
||
document.getElementById('resource-panel').style.display = 'block';
|
||
document.getElementById('crosshair').style.display = 'block';
|
||
document.getElementById('combat-stats').style.display = 'block';
|
||
document.getElementById('exp-display').style.display = 'block';
|
||
document.getElementById('minimap').style.display = 'block';
|
||
document.getElementById('controls-info').style.display = 'block';
|
||
|
||
gameState.isPlaying = true;
|
||
gameState.isPaused = false;
|
||
|
||
showMessage('欢迎来到星际文明探索者!', 'success');
|
||
showMessage('使用 WASD 移动,鼠标瞄准,左键射击', 'info');
|
||
}
|
||
|
||
function togglePause() {
|
||
gameState.isPaused = !gameState.isPaused;
|
||
document.getElementById('pause-menu').classList.toggle('visible', gameState.isPaused);
|
||
}
|
||
|
||
function restartGame() {
|
||
// 重置游戏状态
|
||
gameState.player.health = 100;
|
||
gameState.player.maxHealth = 100;
|
||
gameState.player.shield = 100;
|
||
gameState.player.maxShield = 100;
|
||
gameState.player.energy = 100;
|
||
gameState.player.maxEnergy = 100;
|
||
gameState.player.exp = 0;
|
||
gameState.player.maxExp = 100;
|
||
gameState.player.level = 1;
|
||
gameState.player.crystals = 0;
|
||
gameState.player.ores = 0;
|
||
gameState.player.relics = 0;
|
||
gameState.player.upgrades = {
|
||
engine: 1,
|
||
shield: 1,
|
||
weapon: 1,
|
||
scanner: 1,
|
||
cargo: 1,
|
||
skill: 1
|
||
};
|
||
|
||
gameState.combat.kills = 0;
|
||
gameState.combat.totalDamage = 0;
|
||
gameState.combat.laserCooldown = 0;
|
||
gameState.combat.skillCooldown = 0;
|
||
|
||
// 重置飞船位置
|
||
playerShip.position.set(0, 0, 0);
|
||
playerShip.userData.velocity.set(0, 0, 0);
|
||
|
||
// 重置敌人
|
||
for (const enemy of enemies) {
|
||
scene.remove(enemy);
|
||
}
|
||
enemies = [];
|
||
createEnemies();
|
||
|
||
// 重置小行星
|
||
for (const asteroid of asteroids) {
|
||
scene.remove(asteroid);
|
||
}
|
||
asteroids = [];
|
||
createAsteroids();
|
||
|
||
// 重置行星资源
|
||
for (const planet of planets) {
|
||
planet.userData.hasResources = Math.random() > 0.3;
|
||
planet.userData.resourceAmount = Math.floor(Math.random() * 50) + 20;
|
||
planet.children[0].material.emissiveIntensity = 0.1;
|
||
}
|
||
|
||
// 清除激光和粒子
|
||
for (const laser of lasers) {
|
||
scene.remove(laser);
|
||
}
|
||
lasers = [];
|
||
for (const particle of particles) {
|
||
scene.remove(particle);
|
||
}
|
||
particles = [];
|
||
|
||
// 隐藏暂停菜单
|
||
document.getElementById('pause-menu').classList.remove('visible');
|
||
document.querySelector('.pause-title').textContent = '⏸️ 暂停';
|
||
document.getElementById('resume-btn').style.display = 'block';
|
||
|
||
updateUI();
|
||
updateUpgradePanel();
|
||
|
||
gameState.isPlaying = true;
|
||
gameState.isPaused = false;
|
||
|
||
showMessage('游戏重新开始!', 'success');
|
||
}
|
||
|
||
function quitGame() {
|
||
if (confirm('确定要退出游戏吗?')) {
|
||
window.close();
|
||
}
|
||
}
|
||
|
||
function toggleUpgradePanel() {
|
||
gameState.isUpgrading = !gameState.isUpgrading;
|
||
const panel = document.getElementById('upgrade-panel');
|
||
panel.classList.toggle('visible', gameState.isUpgrading);
|
||
|
||
if (gameState.isUpgrading) {
|
||
updateUpgradePanel();
|
||
}
|
||
}
|
||
|
||
// ==================== 初始化 ====================
|
||
function init() {
|
||
initThree();
|
||
setupInputHandlers();
|
||
|
||
// 控制台输出操作指南
|
||
console.log('%c🚀 星际文明探索者 - 操作指南', 'color: #4CC9F0; font-size: 20px; font-weight: bold;');
|
||
console.log('%c移动: W/A/S/D 或 方向键', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c升降: 空格(上升) / Shift(下降)', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c瞄准: 鼠标移动', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c射击: 左键点击', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c采集: E 键(靠近星球时)', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c技能: 空格键(需要能量)', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c升级: U 键打开升级面板', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c暂停: ESC 键', 'color: #7B2CBF; font-size: 14px;');
|
||
console.log('%c\n收集晶体升级飞船,探索宇宙,消灭敌人!', 'color: #F72585; font-size: 16px;');
|
||
}
|
||
|
||
// 启动游戏
|
||
window.addEventListener('DOMContentLoaded', init);
|
||
</script>
|
||
</body>
|
||
</html>
|