Neon FPS
by RocketOwl777796 lines244.0 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Neon FPS - 赛博朋克第一人称射击</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #0a0a1a;
font-family: 'Courier New', monospace;
user-select: none;
-webkit-user-select: none;
}
#game-container {
width: 100vw;
height: 100vh;
position: relative;
}
canvas {
display: block;
cursor: crosshair;
}
/* 加载画面 */
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0a0a1a;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 0.5s;
}
#loading-screen h1 {
color: #00ffff;
font-size: 48px;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.8);
margin-bottom: 20px;
animation: pulse 2s infinite;
}
#loading-screen p {
color: #888;
font-size: 14px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
/* 伤害闪烁 */
#damage-flash {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 90;
opacity: 0;
transition: opacity 0.1s;
}
/* 商店遮罩 */
#shopOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(10, 10, 26, 0.95);
display: none;
align-items: center;
justify-content: center;
z-index: 200;
overflow-y: auto;
}
#shopContent {
max-width: 900px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
padding: 30px;
background: linear-gradient(135deg, rgba(0, 255, 255, 0.1), rgba(255, 0, 255, 0.1));
border: 2px solid #00ffff;
border-radius: 15px;
box-shadow: 0 0 30px rgba(0, 255, 255, 0.3);
}
.shop-header {
text-align: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(0, 255, 255, 0.3);
}
.shop-header h2 {
color: #00ffff;
font-size: 32px;
text-shadow: 0 0 15px rgba(0, 255, 255, 0.6);
margin-bottom: 10px;
}
.coins-display {
color: #ffdd00;
font-size: 20px;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 221, 0, 0.5);
}
.shop-section {
margin-bottom: 25px;
}
.shop-section h3 {
color: #ff00ff;
font-size: 20px;
margin-bottom: 15px;
text-shadow: 0 0 10px rgba(255, 0, 255, 0.4);
}
.weapon-upgrades {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.weapon-upgrade-card {
background: rgba(0, 0, 0, 0.4);
border: 2px solid;
border-radius: 10px;
padding: 15px;
transition: transform 0.2s, box-shadow 0.2s;
}
.weapon-upgrade-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 20px rgba(0, 255, 255, 0.2);
}
.weapon-upgrade-card h4 {
color: #fff;
font-size: 16px;
margin-bottom: 8px;
}
.weapon-stats {
display: flex;
gap: 15px;
margin-bottom: 12px;
font-size: 12px;
color: #aaa;
}
.upgrade-btn {
width: 100%;
padding: 8px 12px;
margin-bottom: 6px;
background: linear-gradient(135deg, #1a1a2e, #16213e);
border: 1px solid #00ffff;
color: #00ffff;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-family: inherit;
transition: all 0.2s;
}
.upgrade-btn:hover:not(.disabled) {
background: linear-gradient(135deg, #00ffff33, #ff00ff33);
transform: scale(1.02);
}
.upgrade-btn.disabled {
opacity: 0.4;
cursor: not-allowed;
border-color: #666;
color: #666;
}
.general-upgrades {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.general-upgrade-card {
background: rgba(0, 0, 0, 0.4);
border: 2px solid #ffaa00;
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
gap: 10px;
}
.upgrade-icon {
font-size: 32px;
text-align: center;
}
.upgrade-info h4 {
color: #fff;
font-size: 15px;
margin-bottom: 4px;
}
.upgrade-info p {
color: #aaa;
font-size: 12px;
}
.upgrade-level {
color: #ffaa00 !important;
font-weight: bold;
}
.shop-footer {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(0, 255, 255, 0.3);
}
.start-wave-btn {
padding: 15px 40px;
font-size: 18px;
background: linear-gradient(135deg, #ff0066, #ff00ff);
border: none;
color: white;
border-radius: 8px;
cursor: pointer;
font-family: inherit;
font-weight: bold;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
box-shadow: 0 5px 20px rgba(255, 0, 102, 0.4);
transition: all 0.2s;
}
.start-wave-btn:hover {
transform: scale(1.05);
box-shadow: 0 8px 30px rgba(255, 0, 102, 0.6);
}
/* HUD 金币显示 */
#hud-coins {
position: fixed;
top: 20px;
right: 20px;
color: #ffdd00;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 15px rgba(255, 221, 0, 0.6);
z-index: 50;
}
</style>
<style>
/* base.css */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #000;
font-family: 'Courier New', monospace;
color: #fff;
}
#gameContainer {
width: 100vw;
height: 100vh;
position: relative;
}
#crosshair {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
pointer-events: none;
z-index: 100;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background: #0ff;
box-shadow: 0 0 10px #0ff, 0 0 20px #0ff;
}
#crosshair::before {
width: 2px; height: 20px; left: 9px; top: 0;
}
#crosshair::after {
width: 20px; height: 2px; left: 0; top: 9px;
}
#hud {
position: fixed;
bottom: 20px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
z-index: 100;
pointer-events: none;
}
.hud-panel {
background: rgba(0, 255, 255, 0.1);
border: 1px solid #0ff;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3), inset 0 0 10px rgba(0, 255, 255, 0.1);
padding: 10px 20px;
font-size: 16px;
text-shadow: 0 0 10px #0ff;
}
.hud-panel.health {
color: #f0f;
border-color: #f0f;
box-shadow: 0 0 10px rgba(255, 0, 255, 0.3), inset 0 0 10px rgba(255, 0, 255, 0.1);
text-shadow: 0 0 10px #f0f;
}
.hud-panel.score {
color: #ff0;
border-color: #ff0;
box-shadow: 0 0 10px rgba(255, 255, 0, 0.3), inset 0 0 10px rgba(255, 255, 0, 0.1);
text-shadow: 0 0 10px #ff0;
}
.hud-panel.wave {
color: #0f0;
border-color: #0f0;
box-shadow: 0 0 10px rgba(0, 255, 0, 0.3), inset 0 0 10px rgba(0, 255, 0, 0.1);
text-shadow: 0 0 10px #0f0;
}
#startScreen {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.85);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 200;
text-align: center;
}
#startScreen h1 {
font-size: 72px;
color: #0ff;
text-shadow: 0 0 20px #0ff, 0 0 40px #0ff, 0 0 60px #0ff;
margin-bottom: 20px;
letter-spacing: 8px;
animation: glow 2s ease-in-out infinite alternate;
}
@keyframes glow {
from { text-shadow: 0 0 20px #0ff, 0 0 40px #0ff; }
to { text-shadow: 0 0 30px #f0f, 0 0 60px #f0f, 0 0 80px #f0f; }
}
#startScreen p {
font-size: 18px;
color: #aaa;
margin-bottom: 10px;
}
#startScreen .controls {
margin: 30px 0;
color: #666;
line-height: 1.8;
}
#startBtn {
padding: 15px 50px;
font-size: 24px;
background: transparent;
border: 2px solid #0ff;
color: #0ff;
cursor: pointer;
font-family: 'Courier New', monospace;
text-shadow: 0 0 10px #0ff;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
transition: all 0.3s;
margin-top: 20px;
}
#startBtn:hover {
background: rgba(0, 255, 255, 0.2);
box-shadow: 0 0 40px rgba(0, 255, 255, 0.5);
transform: scale(1.05);
}
#gameOver {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.9);
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 300;
text-align: center;
}
#gameOver h2 {
font-size: 64px;
color: #f00;
text-shadow: 0 0 20px #f00, 0 0 40px #f00;
margin-bottom: 20px;
}
#gameOver .final-score {
font-size: 32px;
color: #ff0;
text-shadow: 0 0 15px #ff0;
margin: 20px 0;
}
.neon-text {
color: #0ff;
text-shadow: 0 0 10px currentColor;
}
#weaponDisplay {
position: fixed;
bottom: 20px;
right: 50%;
transform: translateX(50%);
z-index: 100;
pointer-events: none;
}
/* Mobile Controls */
.mobile-controls {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 200px;
z-index: 200;
pointer-events: none;
}
.joystick-container {
position: absolute;
bottom: 30px;
left: 30px;
width: 120px;
height: 120px;
background: rgba(0, 255, 255, 0.1);
border: 2px solid rgba(0, 255, 255, 0.3);
border-radius: 50%;
pointer-events: auto;
touch-action: none;
}
.joystick-knob {
position: absolute;
width: 50px;
height: 50px;
background: rgba(0, 255, 255, 0.4);
border: 2px solid rgba(0, 255, 255, 0.6);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
}
.fire-btn {
position: absolute;
bottom: 50px;
right: 40px;
width: 80px;
height: 80px;
background: rgba(255, 0, 100, 0.3);
border: 3px solid rgba(255, 0, 100, 0.6);
border-radius: 50%;
pointer-events: auto;
touch-action: none;
display: flex;
align-items: center;
justify-content: center;
color: #ff0064;
font-weight: bold;
font-size: 14px;
text-shadow: 0 0 10px #ff0064;
box-shadow: 0 0 20px rgba(255, 0, 100, 0.3);
user-select: none;
}
.fire-btn:active {
background: rgba(255, 0, 100, 0.5);
transform: scale(0.95);
}
.jump-btn {
position: absolute;
bottom: 140px;
right: 60px;
width: 60px;
height: 60px;
background: rgba(0, 255, 255, 0.2);
border: 2px solid rgba(0, 255, 255, 0.5);
border-radius: 50%;
pointer-events: auto;
touch-action: none;
display: flex;
align-items: center;
justify-content: center;
color: #0ff;
font-size: 12px;
font-weight: bold;
user-select: none;
}
.weapon-buttons {
position: absolute;
bottom: 30px;
right: 140px;
display: flex;
gap: 8px;
pointer-events: auto;
}
.weapon-btn {
width: 45px;
height: 45px;
background: rgba(255, 255, 0, 0.2);
border: 2px solid rgba(255, 255, 0, 0.5);
border-radius: 8px;
color: #ff0;
font-size: 11px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
touch-action: none;
user-select: none;
}
.weapon-btn.active {
background: rgba(255, 255, 0, 0.5);
box-shadow: 0 0 15px rgba(255, 255, 0, 0.5);
}
.touch-look-zone {
position: fixed;
top: 0;
right: 0;
width: 50%;
height: 100%;
z-index: 50;
pointer-events: auto;
touch-action: none;
display: none;
}
@media (max-width: 768px) {
.mobile-controls {
display: block;
}
.touch-look-zone {
display: block;
}
#startScreen h1 {
font-size: 36px;
}
#startScreen .controls {
font-size: 12px;
margin: 15px 0;
}
#startBtn {
padding: 12px 30px;
font-size: 18px;
}
.hud-panel {
font-size: 14px;
padding: 8px 12px;
}
#gameOver h2 {
font-size: 36px;
}
#gameOver .final-score {
font-size: 24px;
}
}
@media (hover: none) and (pointer: coarse) {
.mobile-controls { display: block; }
.touch-look-zone { display: block; }
}
/* hud.css */
/* hud.css */
/* menu.css */
/* menu.css */
/* mobile.css */
/* mobile.css */
/* effects.css */
/* effects.css */
</style>
</head>
<body>
<div id="game-container"></div>
<div id="damage-flash"></div>
<div id="hud-coins" style="display:none;">💰 0</div>
<!-- 商店界面 -->
<div id="shopOverlay">
<div id="shopContent"></div>
</div>
<!-- Three.js CDN -->
<!-- 基础工具 -->
<!-- 核心系统 -->
<!-- UI系统 -->
<!-- 游戏系统 -->
<!-- 移动端 -->
<!-- 自动演示 -->
<!-- 游戏主循环 -->
<!-- 入口 -->
<script>
// 页面加载完成后初始化
window.addEventListener('load', () => {
// 隐藏加载画面
const loading = document.getElementById('loading-screen');
if (loading) {
setTimeout(() => {
loading.style.opacity = '0';
setTimeout(() => loading.style.display = 'none', 500);
}, 500);
}
});
// 点击画布锁定指针(PC端)
document.addEventListener('click', (e) => {
if (gameState && gameState.isPlaying && !input.isMobile) {
input.requestPointerLock(renderer.domElement || renderer.renderer.domElement);
}
});
// 首次交互时初始化音频(浏览器策略)
const initAudioOnFirstInteraction = () => {
if (audio && audio.init) {
audio.init();
}
document.removeEventListener('click', initAudioOnFirstInteraction);
document.removeEventListener('touchstart', initAudioOnFirstInteraction);
};
document.addEventListener('click', initAudioOnFirstInteraction);
document.addEventListener('touchstart', initAudioOnFirstInteraction);
</script>
<script>
// utils.js
// 工具函数
const Utils = {
// 随机数
randomRange(min, max) {
return Math.random() * (max - min) + min;
},
// 随机整数
randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
},
// 限制值范围
clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
},
// 线性插值
lerp(a, b, t) {
return a + (b - a) * t;
},
// 距离计算
distance(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
},
// 角度转弧度
degToRad(degrees) {
return degrees * Math.PI / 180;
},
// 弧度转角度
radToDeg(radians) {
return radians * 180 / Math.PI;
},
// 随机颜色
randomColor() {
return Math.floor(Math.random() * 0xffffff);
},
// HSL转RGB(用于生成霓虹色)
hslToHex(h, s, l) {
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r, g, b;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return Math.round((r + m) * 255) << 16 |
Math.round((g + m) * 255) << 8 |
Math.round((b + m) * 255);
},
// 生成霓虹色
neonColor(hue) {
return this.hslToHex(hue, 1, 0.5);
},
// 格式化数字
formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
}
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
},
// 简单的 UUID 生成
uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
},
// 检测移动设备
isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|| ('ontouchstart' in window)
|| (navigator.maxTouchPoints > 0);
},
// 创建 THREE.js 材质的快捷方式
createNeonMaterial(color, emissiveIntensity = 0.5) {
return new THREE.MeshStandardMaterial({
color: color,
emissive: color,
emissiveIntensity: emissiveIntensity,
metalness: 0.3,
roughness: 0.7
});
}
};
// 旧版兼容 - 全局函数
function createLaserBeam(start, end, color) {
if (particles) {
return particles.createLaserBeam(start, end, color);
}
return null;
}
function playShootSound() {
if (audio) {
audio.playShoot('pistol');
}
}
// config.js
// 游戏配置
const CONFIG = {
// 渲染
FOV: 75,
NEAR: 0.1,
FAR: 1000,
FOG_COLOR: 0x0a0a1a,
FOG_DENSITY: 0.02,
// 玩家
PLAYER_SPEED: 8,
PLAYER_SPRINT_SPEED: 14,
PLAYER_JUMP_FORCE: 12,
PLAYER_GRAVITY: 25,
PLAYER_MOUSE_SENSITIVITY: 0.002,
PLAYER_MAX_HEALTH: 100,
// 竞技场
ARENA_SIZE: 40,
// 波次
WAVE_BREAK_TIME: 5,
ENEMIES_PER_WAVE_BASE: 5,
ENEMIES_PER_WAVE_INCREMENT: 3,
BOSS_WAVE_INTERVAL: 5, // 每5波出现Boss
// Boss配置
BOSSES: {
cyberTitan: {
name: '赛博泰坦',
health: 1000,
speed: 1.2,
damage: 40,
score: 1000,
color: 0xff0066,
size: 3,
phase2Health: 0.5, // 50%血量进入第二阶段
abilities: ['slam', 'summon', 'laser']
},
voidWalker: {
name: '虚空行者',
health: 800,
speed: 3,
damage: 35,
score: 800,
color: 0x8800ff,
size: 2,
phase2Health: 0.4,
abilities: ['teleport', 'shadowClones', 'voidBlast']
},
plasmaCore: {
name: '等离子核心',
health: 1200,
speed: 0.5,
damage: 50,
score: 1200,
color: 0x00ffff,
size: 2.5,
phase2Health: 0.3,
abilities: ['plasmaRain', 'shield', 'overload']
}
},
// 武器配置
WEAPONS: {
pistol: {
name: '脉冲手枪',
damage: 15,
fireRate: 250,
bulletSpeed: 60,
color: 0x00ffff,
ammo: Infinity,
spread: 0.02,
price: 0,
unlocked: true,
description: '基础武器,平衡的射速和伤害'
},
shotgun: {
name: '等离子霰弹',
damage: 8,
pellets: 8,
fireRate: 800,
bulletSpeed: 45,
color: 0xff6600,
ammo: Infinity,
spread: 0.15,
price: 200,
unlocked: false,
description: '近距离毁灭性武器,8发弹丸扇形射出'
},
laser: {
name: '光束激光',
damage: 50,
fireRate: 600,
color: 0xff00ff,
ammo: Infinity,
hitscan: true,
price: 300,
unlocked: false,
description: '瞬时命中的高精度激光,无视距离'
},
smg: {
name: '霓虹冲锋枪',
damage: 6,
fireRate: 80,
bulletSpeed: 55,
color: 0x00ff88,
ammo: Infinity,
spread: 0.08,
price: 250,
unlocked: false,
description: '超高射速的冲锋枪,弹幕压制神器'
},
rocket: {
name: '爆裂火箭筒',
damage: 80,
splashRadius: 5,
splashDamage: 40,
fireRate: 1500,
bulletSpeed: 25,
color: 0xff4400,
ammo: Infinity,
spread: 0.01,
price: 400,
unlocked: false,
description: '范围伤害武器,爆炸溅射伤害敌人'
},
plasma: {
name: '等离子步枪',
damage: 25,
fireRate: 350,
bulletSpeed: 50,
color: 0x00ffaa,
ammo: Infinity,
spread: 0.03,
burnDamage: 5,
burnDuration: 3,
price: 350,
unlocked: false,
description: '中等射速,命中造成持续灼烧伤害'
},
sword: {
name: '能量武士刀',
damage: 40,
fireRate: 500,
range: 2.5,
color: 0x00ff88,
price: 150,
unlocked: false,
description: '强大的近战武器,攻速快,伤害高'
}
},
// 敌人配置
ENEMY_TYPES: {
drone: {
name: '无人机',
health: 20,
speed: 4,
damage: 5,
score: 10,
color: 0x00ff00,
size: 0.6
},
grunt: {
name: '步兵',
health: 40,
speed: 2.5,
damage: 10,
score: 25,
color: 0xffff00,
size: 1
},
tank: {
name: '重装',
health: 150,
speed: 1.5,
damage: 25,
score: 100,
color: 0xff0000,
size: 1.8,
knockbackResist: 0.7
},
bomber: {
name: '爆破手',
health: 30,
speed: 2,
damage: 30,
score: 50,
color: 0xff8800,
size: 0.9,
range: 12,
projectileSpeed: 8
},
pouncer: {
name: '突击者',
health: 35,
speed: 5,
damage: 20,
score: 75,
color: 0x8800ff,
size: 0.8,
leapForce: 15
}
},
// 可玩角色
CHARACTERS: {
soldier: {
name: '战士 - 雷克斯',
description: '经验丰富的老兵,擅长使用各种武器',
healthBonus: 50,
speedBonus: 0,
damageBonus: 0.1,
special: '狂暴 - 短时间内伤害翻倍',
color: 0xff4444
},
ninja: {
name: '忍者 - 影',
description: '敏捷的刺客,近战无敌',
healthBonus: -20,
speedBonus: 3,
damageBonus: 0.3,
special: '疾风步 - 短暂无敌并加速',
color: 0x8844ff
},
engineer: {
name: '工程师 - 齿轮',
description: '技术天才,能召唤炮塔辅助',
healthBonus: 20,
speedBonus: -1,
damageBonus: 0,
special: '召唤炮塔 - 自动攻击敌人',
color: 0xffaa00
},
medic: {
name: '医疗兵 - 曙光',
description: '团队的守护者,持续回复生命',
healthBonus: 30,
speedBonus: 1,
damageBonus: -0.1,
special: '治疗脉冲 - 瞬间回复大量生命',
color: 0x44ff88
}
},
// 音效
SOUND_ENABLED: true,
// 移动端
MOBILE_JOYSTICK_SIZE: 80,
MOBILE_SENSITIVITY: 0.005,
// 武器升级
WEAPON_UPGRADES: {
damage: { name: '伤害提升', maxLevel: 5, bonusPerLevel: 0.15 },
fireRate: { name: '射速提升', maxLevel: 5, bonusPerLevel: 0.12 },
speed: { name: '子弹速度', maxLevel: 3, bonusPerLevel: 0.2 }
},
// 拾取物
PICKUPS: {
health: { name: '生命包', color: 0x00ff00, healAmount: 30, spawnChance: 0.15 },
damageBoost: { name: '伤害加成', color: 0xff0000, duration: 10, multiplier: 1.5, spawnChance: 0.08 },
speedBoost: { name: '速度加成', color: 0xffff00, duration: 10, multiplier: 1.5, spawnChance: 0.08 },
shield: { name: '护盾', color: 0x00ffff, shieldAmount: 50, spawnChance: 0.05 },
coin: { name: '能量核心', color: 0xffdd00, scoreValue: 50, spawnChance: 0.3 }
},
// 成就系统
ACHIEVEMENTS: {
first_blood: { name: '初次击杀', description: '击杀第一个敌人', icon: '💀' },
wave_5: { name: '小有成就', description: '坚持到第5波', icon: '🎯' },
wave_10: { name: '生存专家', description: '坚持到第10波', icon: '🏆' },
wave_20: { name: '传奇战士', description: '坚持到第20波', icon: '👑' },
boss_killer: { name: '屠龙勇士', description: '击败第一个Boss', icon: '🐉' },
combo_10: { name: '连击大师', description: '连续击杀10个敌人', icon: '⚡' },
perfect_wave: { name: '完美闪避', description: '一波内不受任何伤害', icon: '✨' },
melee_master: { name: '近战大师', description: '用武士刀击杀50个敌人', icon: '🗡️' },
speedrunner: { name: '极速通关', description: '5分钟内达到第10波', icon: '⏱️' },
collector: { name: '收集者', description: '拾取20个能量核心', icon: '💎' }
}
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = CONFIG;
}
// audio.js
// 音频系统 - 使用 Web Audio API 生成音效,无需外部文件
class AudioSystem {
constructor() {
this.audioContext = null;
this.masterGain = null;
this.enabled = true;
this.initialized = false;
}
init() {
if (this.initialized) return this;
try {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.masterGain = this.audioContext.createGain();
this.masterGain.gain.value = 0.3;
this.masterGain.connect(this.audioContext.destination);
this.initialized = true;
} catch (e) {
console.log('Audio not supported:', e);
this.enabled = false;
}
return this;
}
ensureContext() {
if (!this.audioContext) {
this.init();
}
// 恢复被浏览器挂起的音频上下文
if (this.audioContext && this.audioContext.state === 'suspended') {
this.audioContext.resume();
}
}
playTone(frequency, duration, type = 'square', volume = 0.3) {
if (!this.enabled || !this.audioContext) return;
this.ensureContext();
const oscillator = this.audioContext.createOscillator();
const gainNode = this.audioContext.createGain();
oscillator.type = type;
oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime);
gainNode.gain.setValueAtTime(volume, this.audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + duration);
oscillator.connect(gainNode);
gainNode.connect(this.masterGain);
oscillator.start();
oscillator.stop(this.audioContext.currentTime + duration);
}
playNoise(duration, volume = 0.2, filterFreq = 1000) {
if (!this.enabled || !this.audioContext) return;
this.ensureContext();
const bufferSize = this.audioContext.sampleRate * duration;
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.audioContext.createBufferSource();
noise.buffer = buffer;
const filter = this.audioContext.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = filterFreq;
const gainNode = this.audioContext.createGain();
gainNode.gain.setValueAtTime(volume, this.audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + duration);
noise.connect(filter);
filter.connect(gainNode);
gainNode.connect(this.masterGain);
noise.start();
}
// 射击音效
playShoot(weaponType = 'pistol') {
if (!this.enabled) return;
switch (weaponType) {
case 'pistol':
this.playTone(800, 0.08, 'square', 0.15);
this.playNoise(0.05, 0.1, 2000);
break;
case 'shotgun':
this.playNoise(0.15, 0.25, 500);
this.playTone(150, 0.1, 'sawtooth', 0.15);
break;
case 'laser':
this.playTone(1200, 0.15, 'sine', 0.1);
this.playTone(600, 0.1, 'sine', 0.08);
break;
case 'sword':
this.playNoise(0.08, 0.15, 3000);
break;
}
}
// 命中音效
playHit() {
if (!this.enabled) return;
this.playTone(200, 0.1, 'square', 0.2);
this.playNoise(0.05, 0.1, 800);
}
// 击杀音效
playKill() {
if (!this.enabled) return;
this.playTone(300, 0.15, 'square', 0.15);
this.playTone(150, 0.2, 'sine', 0.1);
}
// 玩家受伤
playHurt() {
if (!this.enabled) return;
this.playTone(100, 0.2, 'sawtooth', 0.2);
this.playNoise(0.1, 0.15, 400);
}
// 跳跃
playJump() {
if (!this.enabled) return;
this.playTone(200, 0.1, 'sine', 0.1);
}
// 翻滚/闪避
playDodge() {
if (!this.enabled) return;
this.playNoise(0.1, 0.1, 1500);
}
// 波次开始
playWaveStart() {
if (!this.enabled) return;
this.playTone(440, 0.15, 'sine', 0.15);
setTimeout(() => this.playTone(550, 0.15, 'sine', 0.15), 100);
setTimeout(() => this.playTone(660, 0.2, 'sine', 0.15), 200);
}
// 波次完成
playWaveComplete() {
if (!this.enabled) return;
this.playTone(523, 0.1, 'sine', 0.15);
setTimeout(() => this.playTone(659, 0.1, 'sine', 0.15), 100);
setTimeout(() => this.playTone(784, 0.15, 'sine', 0.15), 200);
setTimeout(() => this.playTone(1047, 0.3, 'sine', 0.15), 300);
}
// 游戏结束
playGameOver() {
if (!this.enabled) return;
this.playTone(400, 0.3, 'sawtooth', 0.2);
setTimeout(() => this.playTone(300, 0.3, 'sawtooth', 0.2), 200);
setTimeout(() => this.playTone(200, 0.5, 'sawtooth', 0.2), 400);
}
// 切换武器
playWeaponSwitch() {
if (!this.enabled) return;
this.playTone(600, 0.05, 'square', 0.1);
}
// 治疗
playHeal() {
if (!this.enabled) return;
this.playTone(523, 0.1, 'sine', 0.1);
setTimeout(() => this.playTone(659, 0.1, 'sine', 0.1), 80);
setTimeout(() => this.playTone(784, 0.15, 'sine', 0.1), 160);
}
// 特殊技能
playSpecial() {
if (!this.enabled) return;
// 上升音效
for (let i = 0; i < 5; i++) {
setTimeout(() => {
this.playTone(200 + i * 100, 0.1, 'sine', 0.1 - i * 0.01);
}, i * 30);
}
}
// 拾取物
playPickup() {
if (!this.enabled) return;
this.playTone(880, 0.08, 'sine', 0.12);
setTimeout(() => this.playTone(1100, 0.08, 'sine', 0.12), 50);
setTimeout(() => this.playTone(1320, 0.12, 'sine', 0.1), 100);
}
// 成就解锁
playAchievement() {
if (!this.enabled) return;
// 欢快的上升音阶
const notes = [523, 659, 784, 1047, 1319];
notes.forEach((freq, i) => {
setTimeout(() => {
this.playTone(freq, 0.15, 'sine', 0.12);
}, i * 60);
});
}
// Boss技能
playBossAbility() {
if (!this.enabled) return;
this.playTone(80, 0.3, 'sawtooth', 0.2);
this.playNoise(0.2, 0.15, 200);
}
// Boss阶段转换
playBossPhase() {
if (!this.enabled) return;
// 警报声
for (let i = 0; i < 3; i++) {
setTimeout(() => {
this.playTone(400, 0.15, 'square', 0.15);
setTimeout(() => this.playTone(600, 0.15, 'square', 0.15), 100);
}, i * 300);
}
}
// Boss死亡
playBossDeath() {
if (!this.enabled) return;
// 低沉的爆炸音效
this.playTone(100, 0.5, 'sawtooth', 0.25);
this.playNoise(0.5, 0.2, 100);
setTimeout(() => {
this.playTone(50, 0.6, 'sine', 0.2);
}, 100);
}
// 商店打开
playShopOpen() {
if (!this.enabled) return;
// 清脆的开门音效
const notes = [392, 523, 659, 784];
notes.forEach((freq, i) => {
setTimeout(() => {
this.playTone(freq, 0.12, 'sine', 0.12);
}, i * 50);
});
}
// 升级购买
playUpgrade() {
if (!this.enabled) return;
// 金币+升级的音效
this.playTone(880, 0.08, 'square', 0.1);
setTimeout(() => this.playTone(1100, 0.1, 'sine', 0.12), 50);
setTimeout(() => this.playTone(1320, 0.15, 'sine', 0.1), 100);
}
toggle() {
this.enabled = !this.enabled;
if (this.masterGain) {
this.masterGain.gain.value = this.enabled ? 0.3 : 0;
}
return this.enabled;
}
}
const audio = new AudioSystem();
// particles.js
// 粒子系统
class ParticleSystem {
constructor() {
this.particles = [];
this.effects = []; // 特效对象(激光束等)
}
init() {
this.particles = [];
this.effects = [];
return this;
}
createExplosion(position, color, count = 20, size = 1) {
for (let i = 0; i < count; i++) {
const particle = new THREE.Mesh(
new THREE.SphereGeometry(0.1 * size, 4, 4),
new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 1
})
);
particle.position.copy(position);
particle.userData = {
velocity: new THREE.Vector3(
(Math.random() - 0.5) * 8 * size,
Math.random() * 6 * size,
(Math.random() - 0.5) * 8 * size
),
life: 1 + Math.random() * 0.5,
maxLife: 1.5,
gravity: -15
};
renderer.scene.add(particle);
this.particles.push(particle);
}
}
createHitEffect(position, color) {
for (let i = 0; i < 8; i++) {
const particle = new THREE.Mesh(
new THREE.SphereGeometry(0.05, 4, 4),
new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 1
})
);
particle.position.copy(position);
particle.userData = {
velocity: new THREE.Vector3(
(Math.random() - 0.5) * 5,
Math.random() * 3,
(Math.random() - 0.5) * 5
),
life: 0.3 + Math.random() * 0.2,
maxLife: 0.5,
gravity: -5
};
renderer.scene.add(particle);
this.particles.push(particle);
}
}
createMuzzleFlash(position, color, intensity = 1) {
const flash = new THREE.Mesh(
new THREE.SphereGeometry(0.2 * intensity, 8, 8),
new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.8
})
);
flash.position.copy(position);
flash.userData = {
life: 0.05,
maxLife: 0.05,
isFlash: true,
growSpeed: 5 * intensity
};
renderer.scene.add(flash);
this.particles.push(flash);
}
createLaserBeam(start, end, color) {
const points = [start, end];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({
color: color,
linewidth: 2,
transparent: true,
opacity: 1
});
const line = new THREE.Line(geometry, material);
line.userData = {
life: 0.1,
maxLife: 0.1,
isLaser: true
};
renderer.scene.add(line);
this.effects.push(line);
return line;
}
createDamageNumber(position, amount, isCrit = false) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 64;
canvas.height = 32;
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
if (isCrit) {
ctx.fillStyle = '#ff4444';
ctx.font = 'bold 28px Arial';
} else {
ctx.fillStyle = '#ffff00';
}
ctx.fillText(amount.toString(), 32, 16);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({
map: texture,
transparent: true
});
const sprite = new THREE.Sprite(material);
sprite.position.copy(position);
sprite.scale.set(1, 0.5, 1);
sprite.userData = {
life: 1,
maxLife: 1,
velocity: new THREE.Vector3(0, 2, 0),
isDamageNumber: true
};
renderer.scene.add(sprite);
this.particles.push(sprite);
}
update(deltaTime) {
// 更新粒子
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i];
const data = p.userData;
data.life -= deltaTime;
if (data.life <= 0) {
renderer.scene.remove(p);
this.particles.splice(i, 1);
continue;
}
// 物理更新
if (data.velocity) {
if (data.gravity !== undefined) {
data.velocity.y += data.gravity * deltaTime;
}
p.position.addScaledVector(data.velocity, deltaTime);
}
// 透明度衰减
if (p.material && p.material.opacity !== undefined) {
p.material.opacity = (data.life / data.maxLife) * (data.baseOpacity || 1);
}
// 放大(枪口火焰)
if (data.isFlash && data.growSpeed) {
const scale = 1 + (1 - data.life / data.maxLife) * data.growSpeed;
p.scale.setScalar(scale);
}
}
// 更新特效
for (let i = this.effects.length - 1; i >= 0; i--) {
const effect = this.effects[i];
const data = effect.userData;
data.life -= deltaTime;
if (data.life <= 0) {
renderer.scene.remove(effect);
this.effects.splice(i, 1);
continue;
}
// 淡出
if (effect.material && effect.material.opacity !== undefined) {
effect.material.opacity = data.life / data.maxLife;
}
}
}
clear() {
for (const p of this.particles) {
renderer.scene.remove(p);
}
for (const e of this.effects) {
renderer.scene.remove(e);
}
this.particles = [];
this.effects = [];
}
}
const particles = new ParticleSystem();
// effects.js
// 视觉特效系统
class Effects {
constructor() {
this.screenShakeAmount = 0;
this.screenShakeDecay = 0.92;
this.damageFlashAlpha = 0;
this.hitStopFrames = 0;
this.slowMotionFactor = 1;
this.slowMotionDuration = 0;
this.screenGlow = null;
}
init() {
// 创建屏幕光晕
this.createScreenGlow();
return this;
}
createScreenGlow() {
// 可以通过CSS实现全屏光晕
const glow = document.createElement('div');
glow.id = 'screen-glow';
glow.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 50;
opacity: 0;
transition: opacity 0.1s;
mix-blend-mode: screen;
`;
document.body.appendChild(glow);
this.screenGlow = glow;
}
shake(amount, duration = 0.1) {
this.screenShakeAmount = Math.max(this.screenShakeAmount, amount);
}
updateScreenShake(deltaTime) {
if (this.screenShakeAmount > 0.001 && renderer && renderer.camera) {
const shakeX = (Math.random() - 0.5) * this.screenShakeAmount;
const shakeY = (Math.random() - 0.5) * this.screenShakeAmount;
renderer.camera.position.x += shakeX;
renderer.camera.position.y += shakeY;
this.screenShakeAmount *= Math.pow(this.screenShakeDecay, deltaTime * 60);
}
}
showDamageFlash(alpha = 0.5) {
this.damageFlashAlpha = alpha;
// CSS伤害闪红
const flash = document.getElementById('damage-flash');
if (flash) {
flash.style.background = 'radial-gradient(ellipse at center, transparent 40%, rgba(255, 0, 0, 0.5) 100%)';
flash.style.opacity = alpha;
setTimeout(() => {
flash.style.opacity = 0;
}, 100);
}
}
hitStop(frames = 3) {
this.hitStopFrames = frames;
}
slowMotion(factor, duration) {
this.slowMotionFactor = factor;
this.slowMotionDuration = duration;
}
updateSlowMotion(deltaTime) {
if (this.slowMotionDuration > 0) {
this.slowMotionDuration -= deltaTime;
if (this.slowMotionDuration <= 0) {
this.slowMotionFactor = 1;
}
}
}
showScreenGlow(color, intensity = 0.3) {
if (this.screenGlow) {
const hex = '#' + color.toString(16).padStart(6, '0');
this.screenGlow.style.background = `radial-gradient(ellipse at center, ${hex}40 0%, transparent 70%)`;
this.screenGlow.style.opacity = intensity;
}
}
hideScreenGlow() {
if (this.screenGlow) {
this.screenGlow.style.opacity = 0;
}
}
// 包装粒子系统的方法
createExplosion(position, color, size = 1) {
if (particles) {
particles.createExplosion(position, color, Math.floor(20 * size), size);
}
}
createHitEffect(position, color) {
if (particles) {
particles.createHitEffect(position, color);
}
}
createMuzzleFlash(position, color, intensity = 1) {
if (particles) {
particles.createMuzzleFlash(position, color, intensity);
}
}
createLaserBeam(start, end, color) {
if (particles) {
particles.createLaserBeam(start, end, color);
}
}
createDamageNumber(position, amount, isCrit = false) {
if (particles) {
particles.createDamageNumber(position, amount, isCrit);
}
}
update(deltaTime) {
// 更新屏幕震动
this.updateScreenShake(deltaTime);
// 更新慢动作
// this.updateSlowMotion(deltaTime); // 在gameLoop中处理
// 更新粒子
if (particles) {
particles.update(deltaTime);
}
}
}
const effects = new Effects();
// gameState.js
// 游戏状态管理
class GameState {
constructor() {
this.state = 'menu'; // menu, playing, paused, gameover, characterSelect
this.score = 0;
this.wave = 0;
this.kills = 0;
this.selectedCharacter = 'soldier';
this.difficulty = 'normal';
}
setState(newState) {
const oldState = this.state;
this.state = newState;
// 触发状态变化事件
document.dispatchEvent(new CustomEvent('gameStateChange', {
detail: { oldState, newState }
}));
console.log(`Game state: ${oldState} -> ${newState}`);
}
isPlaying() {
return this.state === 'playing';
}
isPaused() {
return this.state === 'paused';
}
isGameOver() {
return this.state === 'gameover';
}
addScore(points) {
this.score += points;
document.dispatchEvent(new CustomEvent('scoreUpdate', { detail: { score: this.score } }));
}
addKill() {
this.kills++;
document.dispatchEvent(new CustomEvent('killUpdate', { detail: { kills: this.kills } }));
}
nextWave() {
this.wave++;
document.dispatchEvent(new CustomEvent('waveUpdate', { detail: { wave: this.wave } }));
}
reset() {
this.score = 0;
this.wave = 0;
this.kills = 0;
this.state = 'menu';
}
}
const gameState = new GameState();
// renderer.js
// 渲染器模块
class Renderer {
constructor() {
this.renderer = null;
this.scene = null;
this.camera = null;
this.container = null;
}
init(containerId = 'game-container') {
this.container = document.getElementById(containerId) || document.body;
// 创建场景
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(CONFIG.FOG_COLOR);
this.scene.fog = new THREE.FogExp2(CONFIG.FOG_COLOR, CONFIG.FOG_DENSITY);
// 创建相机
this.camera = new THREE.PerspectiveCamera(
CONFIG.FOV,
window.innerWidth / window.innerHeight,
CONFIG.NEAR,
CONFIG.FAR
);
this.camera.position.set(0, 1.6, 0);
this.scene.add(this.camera);
// 创建渲染器
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.container.appendChild(this.renderer.domElement);
// 添加光照
this.setupLights();
// 窗口大小变化
window.addEventListener('resize', () => this.onResize());
return this;
}
setupLights() {
// 环境光
const ambientLight = new THREE.AmbientLight(0x404080, 0.4);
this.scene.add(ambientLight);
// 主方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 100;
directionalLight.shadow.camera.left = -30;
directionalLight.shadow.camera.right = 30;
directionalLight.shadow.camera.top = 30;
directionalLight.shadow.camera.bottom = -30;
this.scene.add(directionalLight);
// 霓虹补光
const neonLight1 = new THREE.PointLight(0x00ffff, 1, 30);
neonLight1.position.set(-15, 5, -15);
this.scene.add(neonLight1);
const neonLight2 = new THREE.PointLight(0xff00ff, 1, 30);
neonLight2.position.set(15, 5, 15);
this.scene.add(neonLight2);
const neonLight3 = new THREE.PointLight(0xffff00, 0.8, 25);
neonLight3.position.set(0, 8, 0);
this.scene.add(neonLight3);
}
onResize() {
if (!this.camera || !this.renderer) return;
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
render() {
if (this.renderer && this.scene && this.camera) {
this.renderer.render(this.scene, this.camera);
}
}
get domElement() {
return this.renderer ? this.renderer.domElement : null;
}
}
// 全局渲染器实例
const renderer = new Renderer();
// input.js
// 输入系统
class InputManager {
constructor() {
this.keys = {};
this.mouse = { x: 0, y: 0, dx: 0, dy: 0, locked: false };
this.mouseButtons = { left: false, right: false, middle: false };
this.touch = { active: false, startX: 0, startY: 0, currentX: 0, currentY: 0 };
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
init() {
// 键盘事件
document.addEventListener('keydown', (e) => {
this.keys[e.code] = true;
this.keys[e.key.toLowerCase()] = true;
// ESC暂停
if (e.code === 'Escape' && gameState.isPlaying()) {
gameState.setState('paused');
}
});
document.addEventListener('keyup', (e) => {
this.keys[e.code] = false;
this.keys[e.key.toLowerCase()] = false;
});
// 鼠标移动
document.addEventListener('mousemove', (e) => {
if (this.mouse.locked) {
this.mouse.dx = e.movementX;
this.mouse.dy = e.movementY;
}
});
// 鼠标按钮
document.addEventListener('mousedown', (e) => {
if (e.button === 0) this.mouseButtons.left = true;
if (e.button === 2) this.mouseButtons.right = true;
if (e.button === 1) this.mouseButtons.middle = true;
});
document.addEventListener('mouseup', (e) => {
if (e.button === 0) this.mouseButtons.left = false;
if (e.button === 2) this.mouseButtons.right = false;
if (e.button === 1) this.mouseButtons.middle = false;
});
// 防止右键菜单
document.addEventListener('contextmenu', (e) => e.preventDefault());
// 指针锁定
document.addEventListener('pointerlockchange', () => {
this.mouse.locked = document.pointerLockElement !== null;
});
// 触摸事件(移动端)
if (this.isMobile) {
this.setupTouchControls();
}
return this;
}
setupTouchControls() {
// 触屏视角控制
let lastTouchX = 0;
let lastTouchY = 0;
document.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
const touch = e.touches[0];
lastTouchX = touch.clientX;
lastTouchY = touch.clientY;
}
});
document.addEventListener('touchmove', (e) => {
if (e.touches.length > 0 && gameState.isPlaying()) {
const touch = e.touches[0];
const dx = touch.clientX - lastTouchX;
const dy = touch.clientY - lastTouchY;
// 右半屏控制视角
if (touch.clientX > window.innerWidth / 2) {
this.mouse.dx = dx * CONFIG.MOBILE_SENSITIVITY * 100;
this.mouse.dy = dy * CONFIG.MOBILE_SENSITIVITY * 100;
}
lastTouchX = touch.clientX;
lastTouchY = touch.clientY;
}
});
document.addEventListener('touchend', () => {
this.mouse.dx = 0;
this.mouse.dy = 0;
});
}
requestPointerLock(element) {
if (element && element.requestPointerLock) {
element.requestPointerLock();
}
}
isKeyPressed(key) {
return this.keys[key.toLowerCase()] || this.keys[key] || false;
}
getAxis(horizontal = true) {
let value = 0;
if (horizontal) {
if (this.isKeyPressed('a') || this.isKeyPressed('arrowleft')) value -= 1;
if (this.isKeyPressed('d') || this.isKeyPressed('arrowright')) value += 1;
} else {
if (this.isKeyPressed('w') || this.isKeyPressed('arrowup')) value += 1;
if (this.isKeyPressed('s') || this.isKeyPressed('arrowdown')) value -= 1;
}
return value;
}
// 每帧后调用,重置增量
lateUpdate() {
this.mouse.dx = 0;
this.mouse.dy = 0;
}
}
const input = new InputManager();
// hud.js
// HUD - 抬头显示
class HUD {
constructor() {
this.healthBar = null;
this.healthText = null;
this.scoreText = null;
this.waveText = null;
this.weaponName = null;
this.crosshair = null;
this.damageFlash = null;
this.waveAnnouncement = null;
this.bossHealthBar = null;
this.bossHealthContainer = null;
this.bossAnnouncement = null;
}
init() {
// 创建HUD容器
const hud = document.createElement('div');
hud.id = 'hud';
hud.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
font-family: 'Orbitron', 'Courier New', monospace;
`;
// 生命值条
const healthContainer = document.createElement('div');
healthContainer.style.cssText = `
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 20px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #00ffff;
border-radius: 10px;
overflow: hidden;
`;
this.healthBar = document.createElement('div');
this.healthBar.style.cssText = `
width: 100%;
height: 100%;
background: linear-gradient(90deg, #00ff00, #88ff00);
transition: width 0.2s ease;
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
`;
this.healthText = document.createElement('div');
this.healthText.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 12px;
font-weight: bold;
text-shadow: 0 0 5px #000;
`;
this.healthText.textContent = '100 / 100';
healthContainer.appendChild(this.healthBar);
healthContainer.appendChild(this.healthText);
hud.appendChild(healthContainer);
// 分数
this.scoreText = document.createElement('div');
this.scoreText.style.cssText = `
position: absolute;
top: 20px;
right: 30px;
color: #00ffff;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.8);
`;
this.scoreText.textContent = '分数: 0';
hud.appendChild(this.scoreText);
// 金币
this.coinText = document.createElement('div');
this.coinText.style.cssText = `
position: absolute;
top: 55px;
right: 30px;
color: #ffdd00;
font-size: 20px;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 221, 0, 0.6);
`;
this.coinText.textContent = '💰 0';
hud.appendChild(this.coinText);
// 波次
this.waveText = document.createElement('div');
this.waveText.style.cssText = `
position: absolute;
top: 90px;
right: 30px;
color: #ff00ff;
font-size: 18px;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 0, 255, 0.8);
`;
this.waveText.textContent = '波次: 0';
hud.appendChild(this.waveText);
// 武器名称
this.weaponName = document.createElement('div');
this.weaponName.style.cssText = `
position: absolute;
bottom: 70px;
left: 30px;
color: #ffff00;
font-size: 16px;
font-weight: bold;
text-shadow: 0 0 10px rgba(255, 255, 0, 0.8);
`;
this.weaponName.textContent = '脉冲手枪';
hud.appendChild(this.weaponName);
// 准星
this.crosshair = document.createElement('div');
this.crosshair.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
pointer-events: none;
`;
this.crosshair.innerHTML = `
<div style="position: absolute; top: 50%; left: 0; width: 100%; height: 2px; background: rgba(0, 255, 255, 0.8); transform: translateY(-50%);"></div>
<div style="position: absolute; left: 50%; top: 0; width: 2px; height: 100%; background: rgba(0, 255, 255, 0.8); transform: translateX(-50%);"></div>
<div style="position: absolute; top: 50%; left: 50%; width: 6px; height: 6px; border: 1px solid rgba(0, 255, 255, 0.8); border-radius: 50%; transform: translate(-50%, -50%);"></div>
`;
hud.appendChild(this.crosshair);
// 伤害闪烁
this.damageFlash = document.createElement('div');
this.damageFlash.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at center, transparent 40%, rgba(255, 0, 0, 0.5) 100%);
opacity: 0;
transition: opacity 0.1s;
pointer-events: none;
`;
hud.appendChild(this.damageFlash);
// 波次公告
this.waveAnnouncement = document.createElement('div');
this.waveAnnouncement.style.cssText = `
position: absolute;
top: 30%;
left: 50%;
transform: translateX(-50%);
color: #ff00ff;
font-size: 48px;
font-weight: bold;
text-shadow: 0 0 20px rgba(255, 0, 255, 0.9);
opacity: 0;
transition: opacity 0.5s;
text-align: center;
`;
hud.appendChild(this.waveAnnouncement);
// Boss血条
this.bossHealthContainer = document.createElement('div');
this.bossHealthContainer.style.cssText = `
position: absolute;
top: 80px;
left: 50%;
transform: translateX(-50%);
width: 400px;
display: none;
text-align: center;
`;
const bossName = document.createElement('div');
bossName.style.cssText = `
color: #ff0066;
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
text-shadow: 0 0 10px rgba(255, 0, 102, 0.8);
`;
bossName.textContent = 'BOSS';
this.bossHealthContainer.appendChild(bossName);
const bossHealthBg = document.createElement('div');
bossHealthBg.style.cssText = `
width: 100%;
height: 20px;
background: rgba(0, 0, 0, 0.8);
border: 2px solid #ff0066;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 0 15px rgba(255, 0, 102, 0.5);
`;
this.bossHealthBar = document.createElement('div');
this.bossHealthBar.style.cssText = `
width: 100%;
height: 100%;
background: linear-gradient(90deg, #ff0066, #ff4400);
transition: width 0.3s ease;
box-shadow: 0 0 10px rgba(255, 0, 102, 0.8);
`;
bossHealthBg.appendChild(this.bossHealthBar);
this.bossHealthContainer.appendChild(bossHealthBg);
hud.appendChild(this.bossHealthContainer);
// Boss公告
this.bossAnnouncement = document.createElement('div');
this.bossAnnouncement.style.cssText = `
position: absolute;
top: 25%;
left: 50%;
transform: translateX(-50%);
color: #ff0066;
font-size: 36px;
font-weight: bold;
text-shadow: 0 0 20px rgba(255, 0, 102, 0.9);
opacity: 0;
transition: opacity 0.3s;
text-align: center;
white-space: nowrap;
`;
hud.appendChild(this.bossAnnouncement);
document.body.appendChild(hud);
// 监听事件更新
document.addEventListener('scoreUpdate', (e) => {
this.scoreText.textContent = `分数: ${e.detail.score}`;
});
document.addEventListener('coinsUpdate', (e) => {
if (this.coinText) {
this.coinText.textContent = `💰 ${e.detail.coins}`;
}
});
document.addEventListener('waveUpdate', (e) => {
this.waveText.textContent = `波次: ${e.detail.wave}`;
this.showWaveAnnouncement(`第 ${e.detail.wave} 波`);
});
return this;
}
updateHealth(current, max) {
const percent = (current / max) * 100;
this.healthBar.style.width = `${percent}%`;
this.healthText.textContent = `${Math.ceil(current)} / ${max}`;
// 根据血量变色
if (percent > 60) {
this.healthBar.style.background = 'linear-gradient(90deg, #00ff00, #88ff00)';
} else if (percent > 30) {
this.healthBar.style.background = 'linear-gradient(90deg, #ffaa00, #ff6600)';
} else {
this.healthBar.style.background = 'linear-gradient(90deg, #ff0000, #ff4444)';
}
}
updateWeapon(name) {
this.weaponName.textContent = name;
}
showDamage(amount = 0.5) {
this.damageFlash.style.opacity = amount;
setTimeout(() => {
this.damageFlash.style.opacity = 0;
}, 100);
}
showWaveAnnouncement(text) {
this.waveAnnouncement.textContent = text;
this.waveAnnouncement.style.opacity = 1;
setTimeout(() => {
this.waveAnnouncement.style.opacity = 0;
}, 2000);
}
showBossHealthBar(show) {
if (this.bossHealthContainer) {
this.bossHealthContainer.style.display = show ? 'block' : 'none';
}
}
updateBossHealth(current, max) {
if (this.bossHealthBar) {
const percent = (current / max) * 100;
this.bossHealthBar.style.width = `${percent}%`;
}
}
showBossAnnouncement(text) {
if (!this.bossAnnouncement) return;
this.bossAnnouncement.textContent = text;
this.bossAnnouncement.style.opacity = 1;
this.bossAnnouncement.style.transform = 'translateX(-50%) scale(1.2)';
setTimeout(() => {
this.bossAnnouncement.style.transform = 'translateX(-50%) scale(1)';
}, 200);
setTimeout(() => {
this.bossAnnouncement.style.opacity = 0;
}, 2500);
}
// 显示浮动文字(用于奖励提示等)
showFloatingText(text, x = 0, y = 2, color = 0xffff00) {
// 使用CSS创建屏幕中央浮动文字
const floatEl = document.createElement('div');
floatEl.style.cssText = `
position: fixed;
top: 40%;
left: 50%;
transform: translateX(-50%);
color: #${color.toString(16).padStart(6, '0')};
font-size: 28px;
font-weight: bold;
text-shadow: 0 0 15px #${color.toString(16).padStart(6, '0')};
pointer-events: none;
z-index: 150;
animation: floatUp 1.5s ease-out forwards;
font-family: 'Orbitron', 'Courier New', monospace;
`;
floatEl.textContent = text;
// 添加动画样式
if (!document.getElementById('floatAnimation')) {
const style = document.createElement('style');
style.id = 'floatAnimation';
style.textContent = `
@keyframes floatUp {
0% { opacity: 1; transform: translateX(-50%) translateY(0); }
100% { opacity: 0; transform: translateX(-50%) translateY(-50px); }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(floatEl);
setTimeout(() => {
if (floatEl.parentNode) {
floatEl.parentNode.removeChild(floatEl);
}
}, 1500);
}
show() {
document.getElementById('hud').style.display = 'block';
}
hide() {
const hud = document.getElementById('hud');
if (hud) hud.style.display = 'none';
}
}
const hud = new HUD();
// ui.js
// UI系统 - 菜单、暂停、游戏结束等
class UIManager {
constructor() {
this.menus = {};
this.currentMenu = null;
}
init() {
this.createMainMenu();
this.createCharacterSelect();
this.createPauseMenu();
this.createGameOverMenu();
// 监听游戏状态变化
document.addEventListener('gameStateChange', (e) => {
this.handleStateChange(e.detail.oldState, e.detail.newState);
});
return this;
}
createMainMenu() {
const menu = document.createElement('div');
menu.id = 'main-menu';
menu.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at center, #1a1a3a 0%, #0a0a1a 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 200;
font-family: 'Orbitron', 'Courier New', monospace;
`;
menu.innerHTML = `
<h1 style="color: #00ffff; font-size: 64px; margin-bottom: 10px; text-shadow: 0 0 30px rgba(0, 255, 255, 0.8); letter-spacing: 4px;">
NEON FPS
</h1>
<p style="color: #ff00ff; font-size: 18px; margin-bottom: 50px; text-shadow: 0 0 10px rgba(255, 0, 255, 0.6);">
赛博朋克 · 末日求生
</p>
<button id="start-btn" style="
padding: 15px 50px;
font-size: 20px;
background: linear-gradient(135deg, #00ffff, #0088ff);
color: #000;
border: none;
border-radius: 8px;
cursor: pointer;
margin: 10px;
font-weight: bold;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
transition: all 0.3s;
font-family: inherit;
">开始游戏</button>
<button id="character-btn" style="
padding: 12px 40px;
font-size: 16px;
background: transparent;
color: #ff00ff;
border: 2px solid #ff00ff;
border-radius: 8px;
cursor: pointer;
margin: 10px;
font-weight: bold;
box-shadow: 0 0 15px rgba(255, 0, 255, 0.3);
transition: all 0.3s;
font-family: inherit;
">选择角色</button>
<div style="margin-top: 50px; color: #888; font-size: 12px; text-align: center;">
<p>WASD - 移动 | 鼠标 - 视角 | 左键 - 射击</p>
<p>空格 - 跳跃 | Shift - 冲刺 | Ctrl - 翻滚 | F - 近战</p>
<p>1/2/3/4 - 切换武器 | ESC - 暂停</p>
</div>
<div style="position: absolute; bottom: 20px; color: #444; font-size: 10px;">
KouziTech Presents · v1.0
</div>
`;
document.body.appendChild(menu);
this.menus.main = menu;
// 按钮事件
document.getElementById('start-btn').addEventListener('click', () => {
this.startGame();
});
document.getElementById('character-btn').addEventListener('click', () => {
this.showMenu('characterSelect');
});
}
createCharacterSelect() {
const menu = document.createElement('div');
menu.id = 'character-select';
menu.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at center, #1a1a3a 0%, #0a0a1a 100%);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 200;
font-family: 'Orbitron', 'Courier New', monospace;
`;
const characters = CONFIG.CHARACTERS;
let cardsHtml = '';
for (const [key, char] of Object.entries(characters)) {
const hexColor = '#' + char.color.toString(16).padStart(6, '0');
cardsHtml += `
<div class="char-card" data-char="${key}" style="
width: 200px;
padding: 20px;
background: rgba(0, 0, 0, 0.5);
border: 2px solid ${hexColor};
border-radius: 12px;
margin: 10px;
cursor: pointer;
transition: all 0.3s;
text-align: center;
box-shadow: 0 0 15px ${hexColor}40;
">
<div style="width: 80px; height: 80px; margin: 0 auto 15px; border-radius: 50%;
background: radial-gradient(circle, ${hexColor}40 0%, ${hexColor}10 100%);
border: 3px solid ${hexColor}; display: flex; align-items: center; justify-content: center;">
<span style="font-size: 32px;">${this.getCharacterEmoji(key)}</span>
</div>
<h3 style="color: ${hexColor}; margin: 0 0 8px 0; font-size: 14px;">${char.name}</h3>
<p style="color: #aaa; font-size: 11px; margin: 0 0 10px 0; line-height: 1.4;">${char.description}</p>
<div style="font-size: 10px; color: #888; line-height: 1.6;">
${char.healthBonus > 0 ? '+' : ''}${char.healthBonus} 生命<br>
${char.speedBonus > 0 ? '+' : ''}${char.speedBonus} 速度<br>
${char.damageBonus > 0 ? '+' : ''}${Math.round(char.damageBonus * 100)}% 伤害
</div>
<p style="color: #ffd700; font-size: 10px; margin-top: 10px; font-style: italic;">
${char.special}
</p>
</div>
`;
}
menu.innerHTML = `
<h2 style="color: #ff00ff; font-size: 36px; margin-bottom: 30px; text-shadow: 0 0 20px rgba(255, 0, 255, 0.6);">
选择角色
</h2>
<div style="display: flex; flex-wrap: wrap; justify-content: center; max-width: 900px;">
${cardsHtml}
</div>
<button id="back-btn" style="
margin-top: 30px;
padding: 10px 30px;
font-size: 14px;
background: transparent;
color: #888;
border: 1px solid #666;
border-radius: 6px;
cursor: pointer;
font-family: inherit;
">返回主菜单</button>
`;
document.body.appendChild(menu);
this.menus.characterSelect = menu;
// 卡片点击事件
const cards = menu.querySelectorAll('.char-card');
cards.forEach(card => {
card.addEventListener('click', () => {
const charKey = card.dataset.char;
gameState.selectedCharacter = charKey;
// 视觉反馈
cards.forEach(c => c.style.transform = 'scale(1)');
card.style.transform = 'scale(1.05)';
// 短暂延迟后开始游戏
setTimeout(() => this.startGame(), 300);
});
});
document.getElementById('back-btn').addEventListener('click', () => {
this.showMenu('main');
});
}
getCharacterEmoji(key) {
const emojis = {
soldier: '⚔️',
ninja: '🗡️',
engineer: '🔧',
medic: '💚'
};
return emojis[key] || '🤖';
}
createPauseMenu() {
const menu = document.createElement('div');
menu.id = 'pause-menu';
menu.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 300;
font-family: 'Orbitron', 'Courier New', monospace;
`;
menu.innerHTML = `
<h2 style="color: #ffff00; font-size: 48px; margin-bottom: 40px; text-shadow: 0 0 20px rgba(255, 255, 0, 0.6);">
游戏暂停
</h2>
<button id="resume-btn" style="
padding: 15px 40px;
font-size: 18px;
background: linear-gradient(135deg, #00ff00, #00aa00);
color: #000;
border: none;
border-radius: 8px;
cursor: pointer;
margin: 10px;
font-weight: bold;
font-family: inherit;
">继续游戏</button>
<button id="restart-btn" style="
padding: 12px 35px;
font-size: 16px;
background: transparent;
color: #ffaa00;
border: 2px solid #ffaa00;
border-radius: 8px;
cursor: pointer;
margin: 10px;
font-weight: bold;
font-family: inherit;
">重新开始</button>
<button id="quit-btn" style="
padding: 10px 30px;
font-size: 14px;
background: transparent;
color: #ff4444;
border: 1px solid #ff4444;
border-radius: 6px;
cursor: pointer;
margin: 10px;
font-family: inherit;
">返回主菜单</button>
`;
document.body.appendChild(menu);
this.menus.pause = menu;
document.getElementById('resume-btn').addEventListener('click', () => this.resumeGame());
document.getElementById('restart-btn').addEventListener('click', () => this.restartGame());
document.getElementById('quit-btn').addEventListener('click', () => this.quitToMenu());
}
createGameOverMenu() {
const menu = document.createElement('div');
menu.id = 'gameover-menu';
menu.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 300;
font-family: 'Orbitron', 'Courier New', monospace;
`;
menu.innerHTML = `
<h2 style="color: #ff0000; font-size: 56px; margin-bottom: 20px; text-shadow: 0 0 30px rgba(255, 0, 0, 0.8);">
游戏结束
</h2>
<div style="text-align: center; margin: 30px 0;">
<p style="color: #00ffff; font-size: 24px; margin: 10px 0;">最终分数: <span id="final-score">0</span></p>
<p style="color: #ff00ff; font-size: 20px; margin: 10px 0;">到达波次: <span id="final-wave">0</span></p>
<p style="color: #ffff00; font-size: 18px; margin: 10px 0;">击杀数: <span id="final-kills">0</span></p>
</div>
<button id="retry-btn" style="
padding: 15px 40px;
font-size: 18px;
background: linear-gradient(135deg, #00ffff, #0088ff);
color: #000;
border: none;
border-radius: 8px;
cursor: pointer;
margin: 10px;
font-weight: bold;
font-family: inherit;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
">再来一局</button>
<button id="menu-btn" style="
padding: 12px 35px;
font-size: 16px;
background: transparent;
color: #888;
border: 1px solid #666;
border-radius: 8px;
cursor: pointer;
margin: 10px;
font-family: inherit;
">返回主菜单</button>
`;
document.body.appendChild(menu);
this.menus.gameOver = menu;
document.getElementById('retry-btn').addEventListener('click', () => this.restartGame());
document.getElementById('menu-btn').addEventListener('click', () => this.quitToMenu());
}
showMenu(menuName) {
// 隐藏所有菜单
for (const key in this.menus) {
this.menus[key].style.display = 'none';
}
// 显示指定菜单
if (this.menus[menuName]) {
this.menus[menuName].style.display = 'flex';
this.currentMenu = menuName;
}
}
hideAllMenus() {
for (const key in this.menus) {
this.menus[key].style.display = 'none';
}
this.currentMenu = null;
}
startGame() {
this.hideAllMenus();
hud.show();
gameState.setState('playing');
// 请求指针锁定
if (!input.isMobile && renderer.domElement) {
input.requestPointerLock(renderer.domElement);
}
// 重置并开始游戏
game.reset();
game.start();
}
resumeGame() {
this.hideAllMenus();
gameState.setState('playing');
if (!input.isMobile && renderer.domElement) {
input.requestPointerLock(renderer.domElement);
}
}
restartGame() {
this.hideAllMenus();
hud.show();
gameState.setState('playing');
game.reset();
game.start();
}
quitToMenu() {
gameState.setState('menu');
hud.hide();
this.showMenu('main');
game.reset();
}
handleStateChange(oldState, newState) {
if (newState === 'paused') {
document.exitPointerLock();
this.showMenu('pause');
} else if (newState === 'gameover') {
document.exitPointerLock();
document.getElementById('final-score').textContent = gameState.score;
document.getElementById('final-wave').textContent = gameState.wave;
document.getElementById('final-kills').textContent = gameState.kills;
this.showMenu('gameOver');
} else if (newState === 'playing') {
this.hideAllMenus();
}
}
}
const ui = new UIManager();
// weapons.js
// 武器系统
class WeaponSystem {
constructor() {
this.currentWeapon = 'pistol';
this.lastFireTime = 0;
this.weapons = {};
this.upgrades = {}; // 各武器升级等级
this.unlockedWeapons = new Set(['pistol', 'sword']); // 初始解锁手枪和剑
}
init() {
// 初始化武器配置
for (const [key, config] of Object.entries(CONFIG.WEAPONS)) {
this.weapons[key] = {
...config,
name: key,
baseDamage: config.damage,
baseFireRate: config.fireRate,
baseBulletSpeed: config.bulletSpeed
};
this.upgrades[key] = { damage: 0, fireRate: 0, speed: 0 };
}
return this;
}
unlockWeapon(weaponKey) {
if (this.weapons[weaponKey]) {
this.unlockedWeapons.add(weaponKey);
return true;
}
return false;
}
isUnlocked(weaponKey) {
return this.unlockedWeapons.has(weaponKey);
}
upgradeWeapon(weaponKey, upgradeType) {
const weapon = this.weapons[weaponKey];
const upgradeConfig = CONFIG.WEAPON_UPGRADES[upgradeType];
if (!weapon || !upgradeConfig) return false;
const currentLevel = this.upgrades[weaponKey][upgradeType];
if (currentLevel >= upgradeConfig.maxLevel) return false;
this.upgrades[weaponKey][upgradeType]++;
// 应用升级效果
if (upgradeType === 'damage') {
weapon.damage = weapon.baseDamage * (1 + upgradeConfig.bonusPerLevel * this.upgrades[weaponKey][upgradeType]);
} else if (upgradeType === 'fireRate') {
weapon.fireRate = weapon.baseFireRate * (1 - upgradeConfig.bonusPerLevel * this.upgrades[weaponKey][upgradeType]);
} else if (upgradeType === 'speed') {
weapon.bulletSpeed = weapon.baseBulletSpeed * (1 + upgradeConfig.bonusPerLevel * this.upgrades[weaponKey][upgradeType]);
}
return true;
}
getUpgradeLevel(weaponKey, upgradeType) {
return this.upgrades[weaponKey]?.[upgradeType] || 0;
}
getUpgradeCost(weaponKey, upgradeType) {
const level = this.getUpgradeLevel(weaponKey, upgradeType);
const baseCost = 50;
return Math.floor(baseCost * Math.pow(1.5, level));
}
switchWeapon(weaponKey) {
if (this.weapons[weaponKey] && weaponKey !== this.currentWeapon) {
this.currentWeapon = weaponKey;
if (audio) audio.playWeaponSwitch();
return true;
}
return false;
}
fire(player, targets) {
const weapon = this.weapons[this.currentWeapon];
if (!weapon) return;
const now = performance.now();
if (now - this.lastFireTime < weapon.fireRate) return;
this.lastFireTime = now;
// 根据武器类型处理
switch (this.currentWeapon) {
case 'pistol':
this.firePistol(weapon, player);
break;
case 'shotgun':
this.fireShotgun(weapon, player);
break;
case 'laser':
this.fireLaser(weapon, player, targets);
break;
case 'smg':
this.fireSMG(weapon, player);
break;
case 'rocket':
this.fireRocket(weapon, player);
break;
case 'plasma':
this.firePlasma(weapon, player);
break;
case 'sword':
// 剑在player.js的meleeAttack中处理
return;
}
// 后坐力
if (player && player.viewModel) {
player.recoilAmount = 0.15;
}
// 音效
if (audio) {
audio.playShoot(this.currentWeapon);
}
}
firePistol(weapon, player) {
const direction = new THREE.Vector3();
renderer.camera.getWorldDirection(direction);
// 轻微散射
const spread = weapon.spread || 0.02;
direction.x += (Math.random() - 0.5) * spread;
direction.y += (Math.random() - 0.5) * spread;
direction.z += (Math.random() - 0.5) * spread * 0.5;
direction.normalize();
const bullet = this.createBullet(
player.mesh.position.clone(),
direction,
weapon.bulletSpeed,
weapon.damage,
weapon.color,
true
);
game.bullets.push(bullet);
// 枪口火焰
if (effects) {
const muzzlePos = player.mesh.position.clone();
muzzlePos.y = 1.4;
const camDir = new THREE.Vector3();
renderer.camera.getWorldDirection(camDir);
muzzlePos.addScaledVector(camDir, 0.5);
effects.createMuzzleFlash(muzzlePos, weapon.color);
}
}
fireShotgun(weapon, player) {
const direction = new THREE.Vector3();
renderer.camera.getWorldDirection(direction);
const pelletCount = weapon.pellets || 8;
const spread = weapon.spread || 0.15;
for (let i = 0; i < pelletCount; i++) {
const pelletDir = direction.clone();
pelletDir.x += (Math.random() - 0.5) * spread;
pelletDir.y += (Math.random() - 0.5) * spread * 0.7;
pelletDir.z += (Math.random() - 0.5) * spread * 0.3;
pelletDir.normalize();
const bullet = this.createBullet(
player.mesh.position.clone(),
pelletDir,
weapon.bulletSpeed,
weapon.damage,
weapon.color,
true,
0.8 // 霰弹子弹更小
);
game.bullets.push(bullet);
}
// 枪口火焰
if (effects) {
const muzzlePos = player.mesh.position.clone();
muzzlePos.y = 1.4;
const camDir = new THREE.Vector3();
renderer.camera.getWorldDirection(camDir);
muzzlePos.addScaledVector(camDir, 0.5);
effects.createMuzzleFlash(muzzlePos, weapon.color, 1.5);
}
// 后坐力
if (player && player.viewModel) {
player.recoilAmount = 0.3;
}
}
fireLaser(weapon, player, targets) {
// 激光是 hitscan(瞬时命中)
const raycaster = new THREE.Raycaster();
const direction = new THREE.Vector3();
renderer.camera.getWorldDirection(direction);
raycaster.set(player.mesh.position.clone().add(new THREE.Vector3(0, 1.4, 0)), direction);
raycaster.far = 100;
// 检测敌人命中
let hitPoint = null;
let hitEnemy = null;
let minDist = Infinity;
for (const enemy of targets) {
if (enemy.dead || !enemy.mesh) continue;
const dist = raycaster.ray.distanceToPoint(enemy.mesh.position);
if (dist < (enemy.size || 1) && dist < minDist) {
// 检查是否在射线方向上
const toEnemy = new THREE.Vector3();
toEnemy.subVectors(enemy.mesh.position, raycaster.ray.origin);
const dot = toEnemy.dot(direction);
if (dot > 0) {
minDist = dist;
hitEnemy = enemy;
hitPoint = enemy.mesh.position.clone();
}
}
}
// 创建激光束视觉效果
const beamStart = player.mesh.position.clone();
beamStart.y = 1.4;
const beamEnd = hitPoint ||
raycaster.ray.origin.clone().add(direction.multiplyScalar(80));
if (effects) {
effects.createLaserBeam(beamStart, beamEnd, weapon.color);
}
// 造成伤害
if (hitEnemy) {
const damage = weapon.damage * (1 + player.damageBonus);
hitEnemy.takeDamage(damage, direction);
if (effects) {
effects.createHitEffect(hitPoint, weapon.color);
effects.shake(0.15, 0.05);
}
}
// 后坐力
if (player && player.viewModel) {
player.recoilAmount = 0.1;
}
}
fireSMG(weapon, player) {
const direction = new THREE.Vector3();
renderer.camera.getWorldDirection(direction);
// 比手枪稍大的散射
const spread = weapon.spread || 0.08;
direction.x += (Math.random() - 0.5) * spread;
direction.y += (Math.random() - 0.5) * spread * 0.6;
direction.z += (Math.random() - 0.5) * spread * 0.3;
direction.normalize();
const bullet = this.createBullet(
player.mesh.position.clone(),
direction,
weapon.bulletSpeed,
weapon.damage,
weapon.color,
true,
0.6
);
game.bullets.push(bullet);
// 枪口火焰
if (effects) {
const muzzlePos = player.mesh.position.clone();
muzzlePos.y = 1.4;
const camDir = new THREE.Vector3();
renderer.camera.getWorldDirection(camDir);
muzzlePos.addScaledVector(camDir, 0.5);
effects.createMuzzleFlash(muzzlePos, weapon.color, 0.8);
}
// 后坐力
if (player && player.viewModel) {
player.recoilAmount = 0.08;
}
}
fireRocket(weapon, player) {
const direction = new THREE.Vector3();
renderer.camera.getWorldDirection(direction);
const rocket = this.createBullet(
player.mesh.position.clone(),
direction,
weapon.bulletSpeed,
weapon.damage,
weapon.color,
true,
2.5
);
// 火箭弹特殊属性
rocket.isRocket = true;
rocket.splashRadius = weapon.splashRadius;
rocket.splashDamage = weapon.splashDamage;
// 添加烟雾拖尾
rocket.trail = [];
game.bullets.push(rocket);
// 大枪口火焰
if (effects) {
const muzzlePos = player.mesh.position.clone();
muzzlePos.y = 1.4;
const camDir = new THREE.Vector3();
renderer.camera.getWorldDirection(camDir);
muzzlePos.addScaledVector(camDir, 0.5);
effects.createMuzzleFlash(muzzlePos, weapon.color, 2);
effects.shake(0.2, 0.1);
}
// 后坐力
if (player && player.viewModel) {
player.recoilAmount = 0.5;
}
}
firePlasma(weapon, player) {
const direction = new THREE.Vector3();
renderer.camera.getWorldDirection(direction);
const spread = weapon.spread || 0.03;
direction.x += (Math.random() - 0.5) * spread;
direction.y += (Math.random() - 0.5) * spread * 0.5;
direction.normalize();
const bullet = this.createBullet(
player.mesh.position.clone(),
direction,
weapon.bulletSpeed,
weapon.damage,
weapon.color,
true,
1.2
);
// 等离子弹特殊属性
bullet.isPlasma = true;
bullet.burnDamage = weapon.burnDamage;
bullet.burnDuration = weapon.burnDuration;
// 发光效果更强
bullet.mesh.children[0].scale.setScalar(2);
bullet.mesh.children[0].material.opacity = 0.5;
game.bullets.push(bullet);
// 枪口火焰
if (effects) {
const muzzlePos = player.mesh.position.clone();
muzzlePos.y = 1.4;
const camDir = new THREE.Vector3();
renderer.camera.getWorldDirection(camDir);
muzzlePos.addScaledVector(camDir, 0.5);
effects.createMuzzleFlash(muzzlePos, weapon.color, 1.2);
}
// 后坐力
if (player && player.viewModel) {
player.recoilAmount = 0.15;
}
}
createBullet(position, direction, speed, damage, color, isPlayerBullet = true, size = 1) {
const geometry = new THREE.SphereGeometry(0.05 * size, 6, 6);
const material = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.9
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(position);
mesh.position.y = 1.4; // 从枪口高度发射
// 添加发光效果
const glowGeometry = new THREE.SphereGeometry(0.1 * size, 6, 6);
const glowMaterial = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.3
});
const glow = new THREE.Mesh(glowGeometry, glowMaterial);
mesh.add(glow);
renderer.scene.add(mesh);
return {
mesh: mesh,
velocity: direction.clone().multiplyScalar(speed),
damage: damage,
life: 3, // 3秒后消失
color: color,
isPlayerBullet: isPlayerBullet
};
}
getCurrentWeapon() {
return this.weapons[this.currentWeapon];
}
}
const weapons = new WeaponSystem();
// enemies.js
// 敌人系统
class Enemy {
constructor(type, position) {
this.type = type;
this.config = CONFIG.ENEMY_TYPES[type] || CONFIG.ENEMY_TYPES.drone;
this.health = this.config.health;
this.maxHealth = this.config.health;
this.speed = this.config.speed;
this.damage = this.config.damage;
this.scoreValue = this.config.score;
this.size = this.config.size || 1;
this.color = this.config.color;
this.height = this.size * 2;
this.mesh = null;
this.dead = false;
this.dying = false;
this.deathTime = 0;
this.velocity = new THREE.Vector3();
this.isAttacking = false;
this.attackCooldown = 0;
this.staggerTime = 0;
this.knockback = new THREE.Vector3();
// 状态效果
this.burning = false;
this.burnDamage = 0;
this.burnDuration = 0;
this.burnTickTimer = 0;
this.slowAmount = 0;
this.slowDuration = 0;
// 特殊敌人属性
if (type === 'bomber') {
this.range = this.config.range || 12;
this.projectileSpeed = this.config.projectileSpeed || 8;
}
if (type === 'pouncer') {
this.leapForce = this.config.leapForce || 15;
this.isPouncing = false;
this.pounceCooldown = 0;
}
this.position = position || new THREE.Vector3();
this.createMesh();
}
createMesh() {
const group = new THREE.Group();
// 主体
const bodyGeo = new THREE.BoxGeometry(this.size, this.size * 1.5, this.size);
const bodyMat = new THREE.MeshStandardMaterial({
color: this.color,
emissive: this.color,
emissiveIntensity: 0.2,
metalness: 0.3,
roughness: 0.7
});
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.y = this.size * 0.75;
body.castShadow = true;
group.add(body);
// 头部
const headGeo = new THREE.SphereGeometry(this.size * 0.4, 8, 6);
const headMat = new THREE.MeshStandardMaterial({
color: this.color,
emissive: this.color,
emissiveIntensity: 0.3
});
const head = new THREE.Mesh(headGeo, headMat);
head.position.y = this.size * 1.5;
group.add(head);
// 眼睛
const eyeGeo = new THREE.SphereGeometry(this.size * 0.12, 6, 4);
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const leftEye = new THREE.Mesh(eyeGeo, eyeMat);
leftEye.position.set(-this.size * 0.15, this.size * 1.55, this.size * 0.35);
group.add(leftEye);
const rightEye = new THREE.Mesh(eyeGeo, eyeMat);
rightEye.position.set(this.size * 0.15, this.size * 1.55, this.size * 0.35);
group.add(rightEye);
// 血条背景
const healthBarBg = new THREE.Mesh(
new THREE.PlaneGeometry(this.size * 1.5, 0.1),
new THREE.MeshBasicMaterial({ color: 0x333333, side: THREE.DoubleSide })
);
healthBarBg.position.y = this.size * 2.2;
healthBarBg.name = 'healthBarBg';
group.add(healthBarBg);
// 血条
const healthBar = new THREE.Mesh(
new THREE.PlaneGeometry(this.size * 1.5, 0.1),
new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide })
);
healthBar.position.y = this.size * 2.2;
healthBar.position.z = 0.01;
healthBar.name = 'healthBar';
group.add(healthBar);
group.position.copy(this.position);
group.position.y = 0;
this.mesh = group;
renderer.scene.add(group);
}
update(deltaTime, playerPos) {
if (this.dead) {
this.updateDeath(deltaTime);
return;
}
// 硬直
if (this.staggerTime > 0) {
this.staggerTime -= deltaTime;
// 击退效果
this.mesh.position.addScaledVector(this.knockback, deltaTime * 5);
this.knockback.multiplyScalar(0.9);
return;
}
// 攻击冷却
if (this.attackCooldown > 0) {
this.attackCooldown -= deltaTime;
}
// 状态效果 - 灼烧
if (this.burning && this.burnDuration > 0) {
this.burnDuration -= deltaTime;
this.burnTickTimer -= deltaTime;
if (this.burnTickTimer <= 0) {
this.health -= this.burnDamage;
this.burnTickTimer = 0.5; // 每0.5秒一跳
// 跳字
if (effects && effects.createDamageNumber) {
effects.createDamageNumber(
this.mesh.position.clone().add(new THREE.Vector3(0, this.size * 2, 0)),
Math.round(this.burnDamage),
0xff6600
);
}
if (this.health <= 0) {
this.die();
return;
}
this.updateHealthBar();
}
// 灼烧结束
if (this.burnDuration <= 0) {
this.burning = false;
this.burnDamage = 0;
// 恢复原来的发光颜色
if (this.mesh) {
this.mesh.traverse((child) => {
if (child.isMesh && child.material && child.material.emissive) {
child.material.emissive.setHex(this.color);
child.material.emissiveIntensity = 0.2;
}
});
}
}
}
// 状态效果 - 减速
let speedMultiplier = 1;
if (this.slowDuration > 0) {
this.slowDuration -= deltaTime;
speedMultiplier = 1 - this.slowAmount;
if (this.slowDuration <= 0) {
this.slowAmount = 0;
}
}
// 移动向玩家
const direction = new THREE.Vector3();
direction.subVectors(playerPos, this.mesh.position);
direction.y = 0;
const distance = direction.length();
direction.normalize();
// 根据敌人类型行为
switch (this.type) {
case 'drone':
// 无人机:快速接近,撞击
this.mesh.position.addScaledVector(direction, this.speed * deltaTime);
// 上下浮动
this.mesh.position.y = Math.sin(performance.now() * 0.003 + this.mesh.position.x) * 0.3 + 1;
if (distance < 1.5) {
this.attack(playerPos);
}
break;
case 'grunt':
// 步兵:稳定接近,近战攻击
this.mesh.position.addScaledVector(direction, this.speed * deltaTime);
if (distance < 2) {
this.attack(playerPos);
}
break;
case 'tank':
// 重装:缓慢但血厚伤害高
this.mesh.position.addScaledVector(direction, this.speed * deltaTime);
if (distance < 2.5) {
this.attack(playerPos);
}
break;
case 'bomber':
// 爆破手:远程投掷炸弹
if (distance > this.range * 0.8) {
// 接近到射程内
this.mesh.position.addScaledVector(direction, this.speed * deltaTime);
} else if (distance < this.range * 0.5) {
// 太近了后撤
this.mesh.position.addScaledVector(direction, -this.speed * 0.5 * deltaTime);
}
if (distance < this.range && this.attackCooldown <= 0) {
this.throwBomb(playerPos);
this.attackCooldown = 2;
}
break;
case 'pouncer':
// 突击者:间歇性跳跃攻击
if (this.pounceCooldown > 0) {
this.pounceCooldown -= deltaTime;
}
if (!this.isPouncing) {
if (distance < 8 && this.pounceCooldown <= 0) {
// 开始跳跃
this.isPouncing = true;
this.velocity.y = this.leapForce * 0.5;
this.velocity.x = direction.x * this.speed * 3;
this.velocity.z = direction.z * this.speed * 3;
this.pounceCooldown = 3;
} else {
this.mesh.position.addScaledVector(direction, this.speed * deltaTime);
}
} else {
// 跳跃中
this.velocity.y -= 20 * deltaTime;
this.mesh.position.x += this.velocity.x * deltaTime;
this.mesh.position.z += this.velocity.z * deltaTime;
this.mesh.position.y = Math.max(0, this.mesh.position.y + this.velocity.y * deltaTime);
if (this.mesh.position.y <= 0) {
this.isPouncing = false;
this.mesh.position.y = 0;
// 落地时如果在玩家附近造成伤害
if (distance < 2) {
this.attack(playerPos);
}
}
}
break;
}
// 边界
const boundary = CONFIG.ARENA_SIZE - 1;
this.mesh.position.x = Math.max(-boundary, Math.min(boundary, this.mesh.position.x));
this.mesh.position.z = Math.max(-boundary, Math.min(boundary, this.mesh.position.z));
// 面向玩家
this.mesh.lookAt(playerPos.x, this.mesh.position.y, playerPos.z);
// 更新血条朝向
this.updateHealthBar();
}
attack(playerPos) {
if (this.attackCooldown > 0) return;
this.attackCooldown = 1;
this.isAttacking = true;
// 攻击动画
const originalScale = this.mesh.scale.x;
this.mesh.scale.x = originalScale * 1.1;
setTimeout(() => {
if (this.mesh) this.mesh.scale.x = originalScale;
}, 100);
// 造成伤害(交给碰撞检测)
setTimeout(() => {
if (!this.dead && player) {
const dist = this.mesh.position.distanceTo(player.mesh.position);
if (dist < 2.5) {
player.takeDamage(this.damage);
}
}
this.isAttacking = false;
}, 200);
}
throwBomb(playerPos) {
// 创建投掷物
const bomb = {
mesh: new THREE.Mesh(
new THREE.SphereGeometry(0.15, 8, 6),
new THREE.MeshBasicMaterial({ color: 0xff4400 })
),
velocity: new THREE.Vector3(),
damage: this.damage,
life: 3,
isPlayerBullet: false,
isBomb: true,
explosionRadius: 3
};
bomb.mesh.position.copy(this.mesh.position);
bomb.mesh.position.y = 1;
// 计算抛物线
const direction = new THREE.Vector3();
direction.subVectors(playerPos, this.mesh.position);
const distance = direction.length();
direction.normalize();
const time = distance / this.projectileSpeed;
bomb.velocity.x = direction.x * this.projectileSpeed;
bomb.velocity.z = direction.z * this.projectileSpeed;
bomb.velocity.y = 8; // 向上的初始速度
renderer.scene.add(bomb.mesh);
game.bullets.push(bomb);
}
applyBurn(damage, duration) {
if (this.dead) return;
this.burning = true;
this.burnDamage = Math.max(this.burnDamage, damage);
this.burnDuration = Math.max(this.burnDuration, duration);
// 灼烧视觉效果 - 微微发红
if (this.mesh) {
this.mesh.traverse((child) => {
if (child.isMesh && child.material && child.material.emissive) {
child.material.emissive.setHex(0xff4400);
child.material.emissiveIntensity = 0.5;
}
});
}
}
takeDamage(amount, direction) {
if (this.dead) return;
this.health -= amount;
this.staggerTime = 0.1;
// 击退
if (direction) {
const knockbackForce = (this.config.knockbackResist) ?
2 * (1 - this.config.knockbackResist) : 2;
this.knockback.copy(direction).multiplyScalar(knockbackForce);
this.knockback.y = 0;
}
// 闪白
this.hitFlash();
// 更新血条
this.updateHealthBar();
// 伤害数字
if (effects && effects.createDamageNumber) {
effects.createDamageNumber(this.mesh.position.clone().add(new THREE.Vector3(0, this.size * 2, 0)), Math.round(amount));
}
if (this.health <= 0) {
this.die();
}
}
hitFlash() {
const body = this.mesh.children[0];
if (body && body.material) {
const originalEmissive = body.material.emissive.getHex();
body.material.emissive.setHex(0xffffff);
body.material.emissiveIntensity = 1;
setTimeout(() => {
if (body.material) {
body.material.emissive.setHex(originalEmissive);
body.material.emissiveIntensity = 0.2;
}
}, 50);
}
}
updateHealthBar() {
const healthBar = this.mesh.getObjectByName('healthBar');
const healthBarBg = this.mesh.getObjectByName('healthBarBg');
if (healthBar) {
const healthPercent = this.health / this.maxHealth;
healthBar.scale.x = healthPercent;
// 根据血量变色
if (healthPercent > 0.6) {
healthBar.material.color.setHex(0x00ff00);
} else if (healthPercent > 0.3) {
healthBar.material.color.setHex(0xffff00);
} else {
healthBar.material.color.setHex(0xff0000);
}
// 血条面向相机
healthBar.lookAt(renderer.camera.position);
}
if (healthBarBg) {
healthBarBg.lookAt(renderer.camera.position);
}
}
die() {
this.dead = true;
this.dying = true;
this.deathTime = 0;
// 加分
gameState.addScore(this.scoreValue);
gameState.addKill();
// 死亡特效
if (effects && effects.createExplosion) {
effects.createExplosion(this.mesh.position.clone(), this.color, this.size);
}
if (audio) audio.playKill();
// 概率掉落拾取物
if (pickups && pickups.trySpawnOnEnemyDeath) {
pickups.trySpawnOnEnemyDeath(this);
}
// 移除网格(延迟一点让特效播放)
setTimeout(() => {
if (this.mesh) {
renderer.scene.remove(this.mesh);
this.mesh = null;
}
}, 300);
}
updateDeath(deltaTime) {
this.deathTime += deltaTime;
if (this.mesh) {
this.mesh.scale.multiplyScalar(1 - deltaTime * 3);
this.mesh.rotation.y += deltaTime * 5;
}
}
}
class EnemyManager {
constructor() {
this.list = [];
this.spawnTimer = 0;
}
init() {
this.list = [];
return this;
}
spawnEnemy(type, position) {
const enemy = new Enemy(type, position);
this.list.push(enemy);
return enemy;
}
spawnWave(waveNumber) {
const baseCount = 3 + waveNumber * 2;
const types = ['drone', 'grunt'];
if (waveNumber >= 3) types.push('pouncer');
if (waveNumber >= 5) types.push('bomber');
if (waveNumber >= 7) types.push('tank');
for (let i = 0; i < baseCount; i++) {
const type = types[Math.floor(Math.random() * types.length)];
// 在竞技场边缘生成
const angle = Math.random() * Math.PI * 2;
const distance = CONFIG.ARENA_SIZE - 2;
const position = new THREE.Vector3(
Math.cos(angle) * distance,
0,
Math.sin(angle) * distance
);
// 延迟生成,避免同时出现
setTimeout(() => {
if (gameState.isPlaying()) {
this.spawnEnemy(type, position);
}
}, i * 300);
}
}
update(deltaTime, playerPos) {
for (let i = this.list.length - 1; i >= 0; i--) {
const enemy = this.list[i];
enemy.update(deltaTime, playerPos);
if (enemy.dead && enemy.deathTime > 0.5) {
this.list.splice(i, 1);
}
}
// 检查波次是否完成
if (this.list.length === 0 && gameState.isPlaying() && gameState.wave > 0) {
// 波次完成,通知波次管理器
if (waveManager && waveManager.onWaveComplete) {
waveManager.onWaveComplete();
}
}
}
clear() {
for (const enemy of this.list) {
if (enemy.mesh) {
renderer.scene.remove(enemy.mesh);
}
}
this.list = [];
}
get aliveCount() {
return this.list.filter(e => !e.dead).length;
}
}
const enemies = new EnemyManager();
// boss.js
// Boss系统
class Boss {
constructor(type, position) {
this.type = type;
this.config = CONFIG.BOSSES[type];
this.maxHealth = this.config.health;
this.health = this.config.health;
this.speed = this.config.speed;
this.damage = this.config.damage;
this.score = this.config.score;
this.color = this.config.color;
this.size = this.config.size;
this.dead = false;
this.isBoss = true;
this.phase = 1;
this.phase2Threshold = this.config.phase2Health;
this.abilities = this.config.abilities;
this.abilityCooldown = {};
this.lastAttackTime = 0;
this.attackCooldown = 2000; // 毫秒
this.mesh = null;
this.targetPosition = new THREE.Vector3();
this.velocity = new THREE.Vector3();
this.hitFlashTime = 0;
this.staggerTime = 0;
// 特殊效果
this.shieldActive = false;
this.shieldAmount = 0;
this.isCharging = false;
this.chargeDirection = new THREE.Vector3();
this.createMesh(position);
this.initAbilities();
}
createMesh(position) {
this.mesh = new THREE.Group();
this.mesh.position.copy(position);
this.mesh.position.y = this.size * 0.5;
// Boss主体
const bodyGeo = new THREE.BoxGeometry(this.size * 0.8, this.size, this.size * 0.6);
const bodyMat = new THREE.MeshStandardMaterial({
color: this.color,
emissive: this.color,
emissiveIntensity: 0.3,
metalness: 0.7,
roughness: 0.3
});
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.y = 0;
this.mesh.add(body);
// 头部/核心
const headGeo = new THREE.IcosahedronGeometry(this.size * 0.35, 1);
const headMat = new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: this.color,
emissiveIntensity: 0.5,
metalness: 0.9,
roughness: 0.1
});
const head = new THREE.Mesh(headGeo, headMat);
head.position.y = this.size * 0.5;
this.mesh.add(head);
// 眼睛
for (let i = -1; i <= 1; i += 2) {
const eyeGeo = new THREE.SphereGeometry(this.size * 0.08, 8, 8);
const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const eye = new THREE.Mesh(eyeGeo, eyeMat);
eye.position.set(i * this.size * 0.15, this.size * 0.55, this.size * 0.25);
this.mesh.add(eye);
}
// 手臂/武器
for (let i = -1; i <= 1; i += 2) {
const armGeo = new THREE.CylinderGeometry(this.size * 0.1, this.size * 0.15, this.size * 0.8, 8);
const armMat = new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.8 });
const arm = new THREE.Mesh(armGeo, armMat);
arm.position.set(i * this.size * 0.5, 0, 0);
arm.rotation.z = i * 0.3;
this.mesh.add(arm);
// 武器端
const weaponGeo = new THREE.ConeGeometry(this.size * 0.2, this.size * 0.4, 8);
const weaponMat = new THREE.MeshStandardMaterial({
color: this.color,
emissive: this.color,
emissiveIntensity: 0.4
});
const weapon = new THREE.Mesh(weaponGeo, weaponMat);
weapon.position.set(i * this.size * 0.5, -this.size * 0.5, 0);
weapon.rotation.x = Math.PI;
this.mesh.add(weapon);
}
// 护盾(初始隐藏)
this.shieldMesh = new THREE.Mesh(
new THREE.SphereGeometry(this.size * 1.2, 16, 16),
new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,
opacity: 0.3,
side: THREE.DoubleSide
})
);
this.shieldMesh.visible = false;
this.mesh.add(this.shieldMesh);
// 血条
this.createHealthBar();
renderer.scene.add(this.mesh);
}
createHealthBar() {
// 世界空间血条 - 在Boss头顶
this.healthBarGroup = new THREE.Group();
this.healthBarGroup.position.y = this.size + 0.5;
const bgGeo = new THREE.PlaneGeometry(this.size * 1.5, 0.3);
const bgMat = new THREE.MeshBasicMaterial({ color: 0x333333, side: THREE.DoubleSide });
const bg = new THREE.Mesh(bgGeo, bgMat);
this.healthBarGroup.add(bg);
const fillGeo = new THREE.PlaneGeometry(this.size * 1.5, 0.3);
const fillMat = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide });
this.healthBarFill = new THREE.Mesh(fillGeo, fillMat);
this.healthBarFill.position.z = 0.01;
this.healthBarGroup.add(this.healthBarFill);
// Boss名字
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 28px Arial';
ctx.textAlign = 'center';
ctx.fillText(this.config.name, 128, 40);
const texture = new THREE.CanvasTexture(canvas);
const nameMat = new THREE.MeshBasicMaterial({ map: texture, transparent: true, side: THREE.DoubleSide });
const nameMesh = new THREE.Mesh(new THREE.PlaneGeometry(this.size * 2, 0.5), nameMat);
nameMesh.position.y = 0.5;
this.healthBarGroup.add(nameMesh);
this.mesh.add(this.healthBarGroup);
}
initAbilities() {
for (const ability of this.abilities) {
this.abilityCooldown[ability] = 0;
}
}
update(deltaTime, playerPos) {
if (this.dead) return;
// 更新冷却
for (const ability in this.abilityCooldown) {
if (this.abilityCooldown[ability] > 0) {
this.abilityCooldown[ability] -= deltaTime * 1000;
}
}
if (this.hitFlashTime > 0) this.hitFlashTime -= deltaTime;
if (this.staggerTime > 0) {
this.staggerTime -= deltaTime;
return; // 硬直中不行动
}
// 阶段转换
if (this.phase === 1 && this.health <= this.maxHealth * this.phase2Threshold) {
this.enterPhase2();
}
// 血条朝向玩家
this.healthBarGroup.lookAt(playerPos);
// AI行为
const toPlayer = new THREE.Vector3();
toPlayer.subVectors(playerPos, this.mesh.position);
const distance = toPlayer.length();
toPlayer.y = 0;
toPlayer.normalize();
// 面向玩家
this.mesh.lookAt(playerPos.x, this.mesh.position.y, playerPos.z);
// 移动
if (distance > 5) {
this.mesh.position.x += toPlayer.x * this.speed * deltaTime;
this.mesh.position.z += toPlayer.z * this.speed * deltaTime;
}
// 普通攻击
const now = Date.now();
if (distance < 8 && now - this.lastAttackTime > this.attackCooldown) {
this.basicAttack(playerPos);
this.lastAttackTime = now;
}
// 技能使用
this.useAbilities(deltaTime, playerPos, distance);
// 碰撞伤害
if (distance < this.size * 0.8 && player) {
player.takeDamage(this.damage * deltaTime * 0.5);
}
// 边界限制
const boundary = CONFIG.ARENA_SIZE - 3;
this.mesh.position.x = Math.max(-boundary, Math.min(boundary, this.mesh.position.x));
this.mesh.position.z = Math.max(-boundary, Math.min(boundary, this.mesh.position.z));
}
basicAttack(playerPos) {
// 发射投射物
const direction = new THREE.Vector3();
direction.subVectors(playerPos, this.mesh.position);
direction.normalize();
if (particles) {
particles.createEnemyBullet(this.mesh.position.clone(), direction, this.damage, this.color, 15);
}
}
useAbilities(deltaTime, playerPos, distance) {
const now = Date.now();
// 根据Boss类型使用不同技能
switch (this.type) {
case 'cyberTitan':
this.cyberTitanAbilities(playerPos, distance, now);
break;
case 'voidWalker':
this.voidWalkerAbilities(playerPos, distance, now);
break;
case 'plasmaCore':
this.plasmaCoreAbilities(playerPos, distance, now);
break;
}
}
cyberTitanAbilities(playerPos, distance, now) {
// 地震波
if (this.abilityCooldown['slam'] <= 0 && distance < 10) {
this.abilityCooldown['slam'] = this.phase === 2 ? 6000 : 10000;
this.slamAttack();
}
// 召唤小怪
if (this.abilityCooldown['summon'] <= 0) {
this.abilityCooldown['summon'] = this.phase === 2 ? 8000 : 15000;
this.summonMinions();
}
// 激光
if (this.abilityCooldown['laser'] <= 0 && distance > 8) {
this.abilityCooldown['laser'] = this.phase === 2 ? 4000 : 7000;
this.laserAttack(playerPos);
}
}
voidWalkerAbilities(playerPos, distance, now) {
// 传送
if (this.abilityCooldown['teleport'] <= 0 && distance < 5) {
this.abilityCooldown['teleport'] = this.phase === 2 ? 4000 : 8000;
this.teleport(playerPos);
}
// 影子分身
if (this.abilityCooldown['shadowClones'] <= 0) {
this.abilityCooldown['shadowClones'] = this.phase === 2 ? 12000 : 20000;
this.shadowClones();
}
// 虚空冲击
if (this.abilityCooldown['voidBlast'] <= 0) {
this.abilityCooldown['voidBlast'] = this.phase === 2 ? 5000 : 10000;
this.voidBlast();
}
}
plasmaCoreAbilities(playerPos, distance, now) {
// 等离子雨
if (this.abilityCooldown['plasmaRain'] <= 0) {
this.abilityCooldown['plasmaRain'] = this.phase === 2 ? 8000 : 15000;
this.plasmaRain();
}
// 护盾
if (this.abilityCooldown['shield'] <= 0 && this.health < this.maxHealth * 0.6) {
this.abilityCooldown['shield'] = this.phase === 2 ? 15000 : 25000;
this.activateShield();
}
// 过载(全屏AOE)
if (this.abilityCooldown['overload'] <= 0 && this.phase === 2) {
this.abilityCooldown['overload'] = 20000;
this.overload();
}
}
slamAttack() {
// 地震波 - 地面扩散的冲击波
if (effects) {
effects.createShockwave(this.mesh.position.clone(), this.color, 15, 0.5);
}
// 延迟伤害
setTimeout(() => {
if (this.dead || !player) return;
const dist = this.mesh.position.distanceTo(player.mesh.position);
if (dist < 10) {
const damage = this.damage * (1 - dist / 12);
player.takeDamage(damage);
}
}, 500);
if (audio) audio.playBossAbility();
}
summonMinions() {
// 召唤2-3个小怪
const count = this.phase === 2 ? 3 : 2;
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 / count) * i + Math.random() * 0.5;
const spawnPos = this.mesh.position.clone();
spawnPos.x += Math.cos(angle) * 3;
spawnPos.z += Math.sin(angle) * 3;
if (enemies) {
const enemy = enemies.spawnEnemy('drone', spawnPos);
if (enemy) {
enemy.health *= 1.5; // 强化小怪
enemy.maxHealth *= 1.5;
}
}
}
hud.showBossAnnouncement('召唤了援军!');
}
laserAttack(playerPos) {
// 激光攻击 - 追踪玩家的持续激光
if (effects) {
effects.createLaserBeam(this.mesh.position.clone(), playerPos.clone(), this.color, 1);
}
setTimeout(() => {
if (this.dead || !player) return;
player.takeDamage(this.damage * 0.8);
}, 300);
}
teleport(playerPos) {
// 传送到玩家背后
const direction = new THREE.Vector3();
direction.subVectors(this.mesh.position, playerPos);
direction.normalize();
const newPos = playerPos.clone();
newPos.addScaledVector(direction, 5);
newPos.y = this.size * 0.5;
// 传送特效
if (particles) {
particles.createExplosion(this.mesh.position.clone(), this.color, 20);
particles.createExplosion(newPos.clone(), this.color, 20);
}
this.mesh.position.copy(newPos);
if (audio) audio.playBossAbility();
}
shadowClones() {
// 影子分身 - 创建两个假身
hud.showBossAnnouncement('召唤了影子分身!');
if (enemies) {
for (let i = 0; i < 2; i++) {
const angle = i * Math.PI + Math.PI / 2;
const spawnPos = this.mesh.position.clone();
spawnPos.x += Math.cos(angle) * 4;
spawnPos.z += Math.sin(angle) * 4;
const clone = enemies.spawnEnemy('grunt', spawnPos);
if (clone) {
clone.health = this.health * 0.2;
clone.maxHealth = clone.health;
clone.mesh.scale.setScalar(0.7);
clone.isClone = true;
}
}
}
}
voidBlast() {
// 虚空冲击 - 范围AOE
if (effects) {
effects.createShockwave(this.mesh.position.clone(), 0x8800ff, 20, 0.8);
}
setTimeout(() => {
if (this.dead || !player) return;
const dist = this.mesh.position.distanceTo(player.mesh.position);
if (dist < 15) {
player.takeDamage(this.damage * 0.7);
}
}, 400);
if (audio) audio.playBossAbility();
}
plasmaRain() {
// 等离子雨 - 从天而降的等离子弹
hud.showBossAnnouncement('等离子雨来袭!');
for (let i = 0; i < 8; i++) {
setTimeout(() => {
if (this.dead) return;
const targetPos = new THREE.Vector3(
(Math.random() - 0.5) * CONFIG.ARENA_SIZE * 1.5,
20,
(Math.random() - 0.5) * CONFIG.ARENA_SIZE * 1.5
);
if (particles) {
const direction = new THREE.Vector3(0, -1, 0);
particles.createEnemyBullet(targetPos, direction, this.damage * 0.5, 0x00ffff, 20);
}
}, i * 200);
}
if (audio) audio.playBossAbility();
}
activateShield() {
// 激活护盾
this.shieldActive = true;
this.shieldAmount = this.maxHealth * 0.3;
this.shieldMesh.visible = true;
hud.showBossAnnouncement('激活了能量护盾!');
// 护盾持续10秒
setTimeout(() => {
this.shieldActive = false;
this.shieldMesh.visible = false;
}, 10000);
}
overload() {
// 过载 - 全屏大爆炸
hud.showBossAnnouncement('警告:Boss即将过载!');
// 蓄力2秒
if (effects) {
effects.showScreenGlow(this.color, 0.5);
}
setTimeout(() => {
if (this.dead || !player) return;
// 全屏伤害
player.takeDamage(this.damage * 1.5);
if (effects) {
effects.createShockwave(this.mesh.position.clone(), this.color, 30, 1);
effects.shake(1, 0.3);
effects.hideScreenGlow();
}
}, 2000);
}
enterPhase2() {
this.phase = 2;
this.speed *= 1.5;
this.attackCooldown *= 0.7;
// 阶段转换特效
if (effects) {
effects.createShockwave(this.mesh.position.clone(), this.color, 20, 0.5);
effects.shake(0.8, 0.2);
}
hud.showBossAnnouncement('进入狂暴状态!');
// 血条变色
this.healthBarFill.material.color.setHex(0xff0000);
if (audio) audio.playBossPhase();
}
takeDamage(amount, hitDirection = null) {
if (this.dead) return false;
// 护盾减伤
let actualDamage = amount;
if (this.shieldActive && this.shieldAmount > 0) {
const shieldAbsorb = Math.min(this.shieldAmount, amount * 0.8);
this.shieldAmount -= shieldAbsorb;
actualDamage = amount - shieldAbsorb;
if (this.shieldAmount <= 0) {
this.shieldActive = false;
this.shieldMesh.visible = false;
}
}
this.health -= actualDamage;
this.hitFlashTime = 0.1;
// 更新血条
const healthPercent = Math.max(0, this.health / this.maxHealth);
this.healthBarFill.scale.x = healthPercent;
// 血条颜色变化
if (healthPercent > 0.6) {
this.healthBarFill.material.color.setHex(0x00ff00);
} else if (healthPercent > 0.3) {
this.healthBarFill.material.color.setHex(0xffff00);
} else {
this.healthBarFill.material.color.setHex(0xff0000);
}
// 受伤闪烁
this.mesh.traverse((child) => {
if (child.isMesh && child.material && child.material.emissive) {
child.material.emissiveIntensity = 0.8;
setTimeout(() => {
if (child.material) {
child.material.emissiveIntensity = 0.3;
}
}, 100);
}
});
if (this.health <= 0) {
this.die();
}
return true;
}
die() {
this.dead = true;
// 死亡爆炸
if (particles) {
particles.createExplosion(this.mesh.position.clone(), this.color, 50);
}
if (effects) {
effects.shake(1, 0.3);
effects.createShockwave(this.mesh.position.clone(), this.color, 25, 0.8);
}
// 掉落拾取物
if (pickups) {
pickups.spawnPickup(this.mesh.position.clone(), 'health');
pickups.spawnPickup(this.mesh.position.clone().add(new THREE.Vector3(2, 0, 0)), 'coin');
pickups.spawnPickup(this.mesh.position.clone().add(new THREE.Vector3(-2, 0, 2)), 'damageBoost');
}
// 加分
if (gameState) {
gameState.addScore(this.score);
}
// 成就
if (achievements) {
achievements.unlock('boss_killer');
}
if (audio) audio.playBossDeath();
// 延迟移除
setTimeout(() => {
if (this.mesh && this.mesh.parent) {
this.mesh.parent.remove(this.mesh);
}
}, 1000);
}
}
// Boss管理器
class BossManager {
constructor() {
this.bosses = [];
this.activeBoss = null;
}
spawnBoss(type, position) {
const boss = new Boss(type, position);
this.bosses.push(boss);
this.activeBoss = boss;
hud.showBossAnnouncement(`${boss.config.name} 出现了!`);
hud.showBossHealthBar(true);
return boss;
}
update(deltaTime, playerPos) {
for (let i = this.bosses.length - 1; i >= 0; i--) {
const boss = this.bosses[i];
boss.update(deltaTime, playerPos);
if (boss.dead) {
this.bosses.splice(i, 1);
if (this.activeBoss === boss) {
this.activeBoss = null;
hud.showBossHealthBar(false);
}
}
}
}
getActiveBoss() {
return this.activeBoss;
}
reset() {
for (const boss of this.bosses) {
if (boss.mesh && boss.mesh.parent) {
boss.mesh.parent.remove(boss.mesh);
}
}
this.bosses = [];
this.activeBoss = null;
hud.showBossHealthBar(false);
}
}
const bossManager = new BossManager();
// pickups.js
// 拾取物系统
class Pickup {
constructor(type, position) {
this.type = type;
this.config = CONFIG.PICKUPS[type];
this.position = position.clone();
this.position.y = 0.5;
this.dead = false;
this.bobOffset = Math.random() * Math.PI * 2;
this.createMesh();
}
createMesh() {
this.mesh = new THREE.Group();
const color = this.config.color;
// 根据类型创建不同形状
let geometry;
switch (this.type) {
case 'health':
// 十字形生命包
geometry = new THREE.BoxGeometry(0.6, 0.6, 0.2);
const horizontal = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color }));
const vertical = new THREE.Mesh(
new THREE.BoxGeometry(0.2, 0.6, 0.6),
new THREE.MeshBasicMaterial({ color })
);
this.mesh.add(horizontal);
this.mesh.add(vertical);
break;
case 'damageBoost':
// 火焰形状
geometry = new THREE.ConeGeometry(0.3, 0.8, 8);
this.mesh.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color })));
break;
case 'speedBoost':
// 闪电形状
const lightningShape = new THREE.Shape();
lightningShape.moveTo(0, 0.4);
lightningShape.lineTo(-0.2, 0);
lightningShape.lineTo(0, -0.1);
lightningShape.lineTo(-0.15, -0.4);
lightningShape.lineTo(0.15, -0.1);
lightningShape.lineTo(0, 0);
geometry = new THREE.ExtrudeGeometry(lightningShape, { depth: 0.1, bevelEnabled: false });
geometry.center();
this.mesh.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color })));
break;
case 'shield':
// 盾牌形状
geometry = new THREE.TorusGeometry(0.35, 0.08, 8, 16);
const shieldRing = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color }));
shieldRing.rotation.x = Math.PI / 2;
this.mesh.add(shieldRing);
const shieldCenter = new THREE.Mesh(
new THREE.CircleGeometry(0.25, 16),
new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.5 })
);
shieldCenter.rotation.x = -Math.PI / 2;
shieldCenter.position.y = 0.05;
this.mesh.add(shieldCenter);
break;
case 'coin':
// 硬币/能量核心
geometry = new THREE.OctahedronGeometry(0.3);
const coinMat = new THREE.MeshStandardMaterial({
color,
emissive: color,
emissiveIntensity: 0.5,
metalness: 0.9,
roughness: 0.1
});
this.mesh.add(new THREE.Mesh(geometry, coinMat));
// 光环
const ringGeo = new THREE.TorusGeometry(0.4, 0.03, 8, 16);
const ringMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.6 });
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.rotation.x = Math.PI / 2;
this.mesh.add(ring);
break;
default:
geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
this.mesh.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color })));
}
// 发光效果
const light = new THREE.PointLight(color, 0.5, 3);
light.position.y = 0.5;
this.mesh.add(light);
this.mesh.position.copy(this.position);
renderer.scene.add(this.mesh);
}
update(deltaTime, playerPos) {
if (this.dead) return;
// 上下浮动 + 旋转
this.bobOffset += deltaTime * 2;
this.mesh.position.y = this.position.y + Math.sin(this.bobOffset) * 0.2;
this.mesh.rotation.y += deltaTime;
// 检测玩家拾取
const distance = this.mesh.position.distanceTo(playerPos);
if (distance < 1.5) {
this.collect();
}
}
collect() {
this.dead = true;
// 应用效果
switch (this.type) {
case 'health':
player.heal(this.config.healAmount);
break;
case 'damageBoost':
player.damageBoostMultiplier = this.config.multiplier;
player.damageBoostTime = this.config.duration;
break;
case 'speedBoost':
player.speedBoostMultiplier = this.config.multiplier;
player.speedBoostTime = this.config.duration;
break;
case 'shield':
player.shield = (player.shield || 0) + this.config.shieldAmount;
break;
case 'coin':
gameState.addScore(this.config.scoreValue);
gameState.coinsCollected = (gameState.coinsCollected || 0) + 1;
// 添加到商店金币
if (shop && shop.addCoins) {
shop.addCoins(5); // 每个金币拾取值5个币
}
if (achievements && gameState.coinsCollected >= 20) {
achievements.unlock('collector');
}
break;
}
// 拾取特效
if (particles) {
particles.createExplosion(this.mesh.position.clone(), this.config.color, 10);
}
if (audio) {
audio.playPickup();
}
// 移除
if (this.mesh.parent) {
this.mesh.parent.remove(this.mesh);
}
}
}
// 拾取物管理器
class PickupManager {
constructor() {
this.pickups = [];
}
spawnPickup(position, type = null) {
// 如果没指定类型,按概率随机
if (!type) {
const rand = Math.random();
let cumulative = 0;
for (const [key, config] of Object.entries(CONFIG.PICKUPS)) {
cumulative += config.spawnChance;
if (rand < cumulative) {
type = key;
break;
}
}
if (!type) type = 'coin'; // 默认
}
const pickup = new Pickup(type, position);
this.pickups.push(pickup);
return pickup;
}
trySpawnOnEnemyDeath(enemy) {
// 敌人死亡时有概率掉落
const rand = Math.random();
const dropChance = enemy.isBoss ? 1.0 : 0.15; // Boss必掉,小怪15%
if (rand < dropChance) {
this.spawnPickup(enemy.mesh.position.clone());
}
}
update(deltaTime, playerPos) {
for (let i = this.pickups.length - 1; i >= 0; i--) {
const pickup = this.pickups[i];
pickup.update(deltaTime, playerPos);
if (pickup.dead) {
this.pickups.splice(i, 1);
}
}
}
reset() {
for (const pickup of this.pickups) {
if (pickup.mesh && pickup.mesh.parent) {
pickup.mesh.parent.remove(pickup.mesh);
}
}
this.pickups = [];
}
}
const pickups = new PickupManager();
// achievements.js
// 成就系统
class AchievementManager {
constructor() {
this.unlocked = new Set();
this.queue = []; // 待显示的成就
this.showing = false;
}
unlock(achievementId) {
if (this.unlocked.has(achievementId)) return false;
const achievement = CONFIG.ACHIEVEMENTS[achievementId];
if (!achievement) return false;
this.unlocked.add(achievementId);
this.queue.push(achievement);
// 保存到本地存储
this.save();
// 显示成就
this.showNext();
console.log(`🏆 成就解锁: ${achievement.name}`);
return true;
}
showNext() {
if (this.showing || this.queue.length === 0) return;
this.showing = true;
const achievement = this.queue.shift();
// 创建成就通知
this.showNotification(achievement);
if (audio) {
audio.playAchievement();
}
}
showNotification(achievement) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 100px;
right: -300px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 2px solid #ffd700;
border-radius: 10px;
padding: 15px 20px;
display: flex;
align-items: center;
gap: 15px;
z-index: 1000;
transition: right 0.5s ease-out;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
min-width: 250px;
`;
notification.innerHTML = `
<div style="font-size: 40px;">${achievement.icon}</div>
<div>
<div style="color: #ffd700; font-weight: bold; font-size: 14px;">成就解锁!</div>
<div style="color: white; font-size: 16px; font-weight: bold; margin-top: 2px;">${achievement.name}</div>
<div style="color: #aaa; font-size: 12px; margin-top: 2px;">${achievement.description}</div>
</div>
`;
document.body.appendChild(notification);
// 滑入动画
setTimeout(() => {
notification.style.right = '20px';
}, 100);
// 停留3秒后滑出
setTimeout(() => {
notification.style.right = '-300px';
setTimeout(() => {
if (notification.parentElement) {
notification.parentElement.removeChild(notification);
}
this.showing = false;
this.showNext();
}, 500);
}, 3000);
}
checkAchievements(stats) {
// 根据游戏状态检查成就
if (stats.wave >= 5) this.unlock('wave_5');
if (stats.wave >= 10) this.unlock('wave_10');
if (stats.wave >= 20) this.unlock('wave_20');
if (stats.kills >= 1) this.unlock('first_blood');
if (stats.maxCombo >= 10) this.unlock('combo_10');
if (stats.meleeKills >= 50) this.unlock('melee_master');
if (stats.perfectWave) this.unlock('perfect_wave');
}
getAllUnlocked() {
return Array.from(this.unlocked);
}
getAllAchievements() {
return Object.entries(CONFIG.ACHIEVEMENTS).map(([id, data]) => ({
id,
...data,
unlocked: this.unlocked.has(id)
}));
}
save() {
try {
localStorage.setItem('neonFPS_achievements', JSON.stringify(Array.from(this.unlocked)));
} catch (e) {
// localStorage不可用
}
}
load() {
try {
const saved = localStorage.getItem('neonFPS_achievements');
if (saved) {
this.unlocked = new Set(JSON.parse(saved));
}
} catch (e) {
// localStorage不可用
}
}
reset() {
this.unlocked.clear();
this.save();
}
}
const achievements = new AchievementManager();
// combo.js
// 连击系统
class ComboSystem {
constructor() {
this.combo = 0;
this.maxCombo = 0;
this.comboTimer = 0;
this.comboDuration = 3.0; // 连击持续时间(秒)
this.score = 0;
this.totalKills = 0;
this.totalDamageDealt = 0;
this.headshotCount = 0;
// 连击加成
this.comboMultipliers = [
{ threshold: 5, multiplier: 1.5, name: "连杀" },
{ threshold: 10, multiplier: 2.0, name: "狂杀" },
{ threshold: 20, multiplier: 3.0, name: "屠戮" },
{ threshold: 30, multiplier: 4.0, name: "灭世" },
{ threshold: 50, multiplier: 5.0, name: "神挡杀神" },
];
// 成就相关
this.achievements = null;
}
init(achievements) {
this.achievements = achievements;
}
reset() {
this.combo = 0;
this.maxCombo = 0;
this.comboTimer = 0;
this.score = 0;
this.totalKills = 0;
this.totalDamageDealt = 0;
this.headshotCount = 0;
}
update(deltaTime) {
if (this.combo > 0) {
this.comboTimer -= deltaTime;
if (this.comboTimer <= 0) {
this.resetCombo();
}
}
}
addKill(enemy, isHeadshot = false) {
this.combo++;
this.comboTimer = this.comboDuration;
this.totalKills++;
if (this.combo > this.maxCombo) {
this.maxCombo = this.combo;
}
// 计算得分
let baseScore = enemy.scoreValue || 100;
let multiplier = this.getMultiplier();
if (isHeadshot) {
baseScore *= 2;
this.headshotCount++;
}
const scoreGain = Math.floor(baseScore * multiplier);
this.score += scoreGain;
// 检查成就
if (this.achievements) {
if (this.combo >= 10) {
this.achievements.unlock('combo_10');
}
if (this.combo >= 25) {
this.achievements.unlock('combo_25');
}
if (this.combo >= 50) {
this.achievements.unlock('combo_50');
}
if (this.headshotCount >= 50) {
this.achievements.unlock('headshot_master');
}
if (this.totalKills >= 100) {
this.achievements.unlock('killer_100');
}
}
return {
combo: this.combo,
score: this.score,
scoreGain: scoreGain,
multiplier: multiplier,
isHeadshot: isHeadshot
};
}
addDamage(damage) {
this.totalDamageDealt += damage;
}
resetCombo() {
this.combo = 0;
this.comboTimer = 0;
}
getMultiplier() {
let multiplier = 1.0;
for (const tier of this.comboMultipliers) {
if (this.combo >= tier.threshold) {
multiplier = tier.multiplier;
}
}
return multiplier;
}
getComboName() {
let name = "";
for (const tier of this.comboMultipliers) {
if (this.combo >= tier.threshold) {
name = tier.name;
}
}
return name;
}
getGrade() {
// 根据分数和表现评级
if (this.score >= 50000) return { grade: "S+", color: "#ffd700" };
if (this.score >= 30000) return { grade: "S", color: "#ffd700" };
if (this.score >= 20000) return { grade: "A", color: "#00ff88" };
if (this.score >= 10000) return { grade: "B", color: "#00aaff" };
if (this.score >= 5000) return { grade: "C", color: "#aa88ff" };
if (this.score >= 2000) return { grade: "D", color: "#ff8888" };
return { grade: "F", color: "#ff4444" };
}
getStats() {
return {
score: this.score,
combo: this.combo,
maxCombo: this.maxCombo,
totalKills: this.totalKills,
totalDamageDealt: Math.floor(this.totalDamageDealt),
headshotCount: this.headshotCount,
grade: this.getGrade()
};
}
}
// 飘字系统
class FloatingText {
constructor(x, y, z, text, color = '#ffffff', size = 24) {
this.x = x;
this.y = y;
this.z = z;
this.text = text;
this.color = color;
this.size = size;
this.life = 1.5;
this.maxLife = 1.5;
this.velocityY = 2;
this.velocityX = (Math.random() - 0.5) * 0.5;
this.opacity = 1;
this.scale = 1;
}
update(deltaTime) {
this.life -= deltaTime;
this.y += this.velocityY * deltaTime;
this.x += this.velocityX * deltaTime;
this.opacity = this.life / this.maxLife;
this.scale = 1 + (1 - this.opacity) * 0.5;
return this.life > 0;
}
}
class FloatingTextManager {
constructor() {
this.texts = [];
}
add(x, y, z, text, color = '#ffffff', size = 24) {
this.texts.push(new FloatingText(x, y, z, text, color, size));
}
addDamage(x, y, z, damage, isCrit = false, isHeadshot = false) {
let color = isHeadshot ? '#ff4444' : '#ffffff';
let size = isHeadshot ? 28 : 20;
let text = Math.floor(damage).toString();
if (isHeadshot) {
text = "暴击! " + text;
}
this.add(x, y + 1, z, text, color, size);
}
addCombo(x, y, z, combo, multiplier) {
const text = `${combo} 连击! x${multiplier}`;
this.add(x, y + 2, z, text, '#ffd700', 32);
}
update(deltaTime) {
this.texts = this.texts.filter(t => t.update(deltaTime));
}
getTexts() {
return this.texts;
}
clear() {
this.texts = [];
}
}
// 导出
if (typeof window !== 'undefined') {
window.ComboSystem = ComboSystem;
window.FloatingTextManager = FloatingTextManager;
}
// skills.js
// 技能系统
class Skill {
constructor(config) {
this.name = config.name;
this.description = config.description;
this.cooldown = config.cooldown; // 冷却时间(秒)
this.currentCooldown = 0;
this.duration = config.duration || 0; // 持续时间
this.remainingDuration = 0;
this.isActive = false;
this.icon = config.icon || '⚡';
this.key = config.key || 'Q';
// 效果参数
this.effects = config.effects || {};
this.onActivate = config.onActivate || (() => {});
this.onDeactivate = config.onDeactivate || (() => {});
this.onUpdate = config.onUpdate || (() => {});
}
canUse() {
return this.currentCooldown <= 0;
}
use() {
if (!this.canUse()) return false;
this.currentCooldown = this.cooldown;
this.remainingDuration = this.duration;
this.isActive = true;
this.onActivate();
return true;
}
update(deltaTime) {
if (this.currentCooldown > 0) {
this.currentCooldown -= deltaTime;
if (this.currentCooldown < 0) this.currentCooldown = 0;
}
if (this.isActive && this.duration > 0) {
this.remainingDuration -= deltaTime;
this.onUpdate(deltaTime);
if (this.remainingDuration <= 0) {
this.isActive = false;
this.remainingDuration = 0;
this.onDeactivate();
}
}
}
getCooldownPercent() {
if (this.cooldown <= 0) return 0;
return this.currentCooldown / this.cooldown;
}
getDurationPercent() {
if (this.duration <= 0) return 0;
return this.remainingDuration / this.duration;
}
}
// 角色技能定义
const CharacterSkills = {
warrior: {
skills: [
{
name: '狂暴',
description: '进入狂暴状态,伤害+50%,移动速度+30%,持续8秒',
cooldown: 30,
duration: 8,
icon: '💢',
key: 'Q',
effects: {
damageMultiplier: 1.5,
speedMultiplier: 1.3
}
},
{
name: '战吼',
description: '战吼震慑周围敌人,造成伤害并减速3秒',
cooldown: 20,
duration: 0,
icon: '📢',
key: 'E',
effects: {
damage: 50,
slowAmount: 0.5,
slowDuration: 3,
radius: 10
}
}
],
passive: {
name: '坚韧',
description: '最大生命值+50'
}
},
ninja: {
skills: [
{
name: '疾风步',
description: '瞬移一段距离,短暂无敌',
cooldown: 15,
duration: 0.5,
icon: '💨',
key: 'Q',
effects: {
dashDistance: 15,
invincible: true
}
},
{
name: '影分身',
description: '创建一个分身吸引敌人注意力,持续5秒',
cooldown: 25,
duration: 5,
icon: '👥',
key: 'E',
effects: {
cloneCount: 1,
cloneHealth: 100
}
}
],
passive: {
name: '敏捷',
description: '移动速度+20%,暴击率+10%'
}
},
engineer: {
skills: [
{
name: '召唤炮塔',
description: '部署一个自动攻击的炮塔,持续15秒',
cooldown: 35,
duration: 15,
icon: '🔫',
key: 'Q',
effects: {
turretDamage: 15,
turretFireRate: 2,
turretRange: 20
}
},
{
name: '力场护盾',
description: '展开护盾,减伤50%,持续10秒',
cooldown: 30,
duration: 10,
icon: '🛡️',
key: 'E',
effects: {
damageReduction: 0.5
}
}
],
passive: {
name: '工程学',
description: '金币获取+25%,商店物品-10%价格'
}
},
medic: {
skills: [
{
name: '治疗脉冲',
description: '立即恢复50生命值',
cooldown: 20,
duration: 0,
icon: '💚',
key: 'Q',
effects: {
healAmount: 50
}
},
{
name: '再生领域',
description: '持续恢复生命值,持续8秒',
cooldown: 25,
duration: 8,
icon: '✨',
key: 'E',
effects: {
healPerSecond: 10
}
}
],
passive: {
name: '医学',
description: '生命恢复速度+50%,拾取生命包效果+25%'
}
}
};
// 技能管理器
class SkillManager {
constructor() {
this.skills = [];
this.characterType = null;
this.player = null;
}
init(player, characterType) {
this.player = player;
this.characterType = characterType;
this.skills = [];
const charConfig = CharacterSkills[characterType];
if (charConfig) {
for (const skillConfig of charConfig.skills) {
const skill = new Skill({
...skillConfig,
onActivate: () => this.onSkillActivate(skillConfig),
onDeactivate: () => this.onSkillDeactivate(skillConfig),
onUpdate: (dt) => this.onSkillUpdate(skillConfig, dt)
});
this.skills.push(skill);
}
}
}
onSkillActivate(config) {
const effects = config.effects;
// 伤害加成
if (effects.damageMultiplier) {
this.player.damageMultiplier *= effects.damageMultiplier;
}
// 速度加成
if (effects.speedMultiplier) {
this.player.speedMultiplier *= effects.speedMultiplier;
}
// 治疗
if (effects.healAmount) {
this.player.heal(effects.healAmount);
}
// 无敌
if (effects.invincible) {
this.player.invincible = true;
}
// 减伤
if (effects.damageReduction) {
this.player.damageReduction += effects.damageReduction;
}
// 播放音效
if (window.audioManager) {
window.audioManager.playSound('skill');
}
// 视觉效果
if (window.effectsManager) {
window.effectsManager.spawnEffect(
this.player.position.x,
this.player.position.y + 1,
this.player.position.z,
'skill_activate'
);
}
}
onSkillDeactivate(config) {
const effects = config.effects;
if (effects.damageMultiplier) {
this.player.damageMultiplier /= effects.damageMultiplier;
}
if (effects.speedMultiplier) {
this.player.speedMultiplier /= effects.speedMultiplier;
}
if (effects.invincible) {
this.player.invincible = false;
}
if (effects.damageReduction) {
this.player.damageReduction -= effects.damageReduction;
}
}
onSkillUpdate(config, deltaTime) {
const effects = config.effects;
// 持续治疗
if (effects.healPerSecond) {
this.player.heal(effects.healPerSecond * deltaTime);
}
}
useSkill(index) {
if (index >= 0 && index < this.skills.length) {
return this.skills[index].use();
}
return false;
}
update(deltaTime) {
for (const skill of this.skills) {
skill.update(deltaTime);
}
}
getSkills() {
return this.skills;
}
getPassive() {
const charConfig = CharacterSkills[this.characterType];
return charConfig ? charConfig.passive : null;
}
}
// 导出
if (typeof window !== 'undefined') {
window.Skill = Skill;
window.SkillManager = SkillManager;
window.CharacterSkills = CharacterSkills;
}
// shop.js
// 商店系统 - 波次间隙购买武器升级
class ShopSystem {
constructor() {
this.coins = 0;
this.weaponLevels = {}; // 每种武器的升级等级
this.generalUpgrades = {
maxHealth: 0,
speed: 0,
healthRegen: 0
};
this.shopOpen = false;
}
init() {
// 初始化所有武器等级为0
for (const weaponKey of Object.keys(CONFIG.WEAPONS)) {
this.weaponLevels[weaponKey] = {
damage: 0,
fireRate: 0,
speed: 0
};
}
return this;
}
addCoins(amount) {
this.coins += amount;
document.dispatchEvent(new CustomEvent('coinsUpdate', { detail: { coins: this.coins } }));
}
spendCoins(amount) {
if (this.coins >= amount) {
this.coins -= amount;
document.dispatchEvent(new CustomEvent('coinsUpdate', { detail: { coins: this.coins } }));
return true;
}
return false;
}
// 获取升级价格
getUpgradePrice(weaponKey, upgradeType, level) {
const basePrices = {
damage: 50,
fireRate: 60,
speed: 40
};
const base = basePrices[upgradeType] || 50;
return Math.floor(base * Math.pow(1.5, level));
}
getGeneralUpgradePrice(upgradeType, level) {
const basePrices = {
maxHealth: 80,
speed: 70,
healthRegen: 100
};
const base = basePrices[upgradeType] || 80;
return Math.floor(base * Math.pow(1.6, level));
}
// 升级武器
upgradeWeapon(weaponKey, upgradeType) {
const levels = this.weaponLevels[weaponKey];
if (!levels) return false;
const maxLevel = CONFIG.WEAPON_UPGRADES[upgradeType]?.maxLevel || 5;
if (levels[upgradeType] >= maxLevel) return false;
const price = this.getUpgradePrice(weaponKey, upgradeType, levels[upgradeType]);
if (!this.spendCoins(price)) return false;
levels[upgradeType]++;
// 应用升级到武器
this.applyWeaponUpgrade(weaponKey, upgradeType);
if (audio) audio.playUpgrade();
return true;
}
// 升级通用属性
upgradeGeneral(upgradeType) {
const level = this.generalUpgrades[upgradeType];
const maxLevel = 5;
if (level >= maxLevel) return false;
const price = this.getGeneralUpgradePrice(upgradeType, level);
if (!this.spendCoins(price)) return false;
this.generalUpgrades[upgradeType]++;
this.applyGeneralUpgrade(upgradeType);
if (audio) audio.playUpgrade();
return true;
}
// 应用武器升级
applyWeaponUpgrade(weaponKey, upgradeType) {
const weapon = weapons.weapons[weaponKey];
if (!weapon) return;
const level = this.weaponLevels[weaponKey][upgradeType];
const bonus = CONFIG.WEAPON_UPGRADES[upgradeType]?.bonusPerLevel || 0.1;
switch (upgradeType) {
case 'damage':
// 存储基础伤害以便叠加
if (!weapon.baseDamage) weapon.baseDamage = CONFIG.WEAPONS[weaponKey].damage;
weapon.damage = weapon.baseDamage * (1 + bonus * level);
break;
case 'fireRate':
if (!weapon.baseFireRate) weapon.baseFireRate = CONFIG.WEAPONS[weaponKey].fireRate;
weapon.fireRate = weapon.baseFireRate * (1 - bonus * level); // 射速提升=间隔减少
break;
case 'speed':
if (weapon.bulletSpeed !== undefined) {
if (!weapon.baseBulletSpeed) weapon.baseBulletSpeed = CONFIG.WEAPONS[weaponKey].bulletSpeed;
weapon.bulletSpeed = weapon.baseBulletSpeed * (1 + bonus * level);
}
break;
}
}
// 应用通用升级
applyGeneralUpgrade(upgradeType) {
const level = this.generalUpgrades[upgradeType];
switch (upgradeType) {
case 'maxHealth':
if (player) {
const bonus = 20 * level;
player.maxHealth = CONFIG.PLAYER_MAX_HEALTH + bonus;
player.health = Math.min(player.health + 20, player.maxHealth);
}
break;
case 'speed':
if (player) {
player.speedBonus = (player.speedBonus || 0) + 0.5;
}
break;
case 'healthRegen':
if (player) {
player.regenRate = (player.regenRate || 0) + 1; // 每秒回复1点
}
break;
}
}
// 获取武器当前属性
getWeaponStats(weaponKey) {
const weapon = weapons.weapons[weaponKey];
if (!weapon) return null;
return {
damage: weapon.damage,
fireRate: weapon.fireRate,
bulletSpeed: weapon.bulletSpeed
};
}
// 打开商店
openShop() {
this.shopOpen = true;
this.renderShop();
document.getElementById('shopOverlay').style.display = 'flex';
if (audio) audio.playShopOpen();
}
// 关闭商店
closeShop() {
this.shopOpen = false;
document.getElementById('shopOverlay').style.display = 'none';
}
// 渲染商店UI
renderShop() {
const shopContent = document.getElementById('shopContent');
if (!shopContent) return;
let html = '<div class="shop-header">';
html += '<h2>⚡ 武器升级站</h2>';
html += `<p class="coins-display">💰 能量核心: <span id="shopCoins">${this.coins}</span></p>`;
html += '</div>';
// 武器购买区(未解锁的武器)
const lockedWeapons = Object.entries(CONFIG.WEAPONS).filter(
([key, w]) => !weapons.isUnlocked(key) && w.price > 0
);
if (lockedWeapons.length > 0) {
html += '<div class="shop-section">';
html += '<h3>🛒 武器商店</h3>';
html += '<div class="weapon-upgrades">';
for (const [weaponKey, weapon] of lockedWeapons) {
const canAfford = this.coins >= weapon.price;
html += `<div class="weapon-upgrade-card locked" style="border-color: #${weapon.color.toString(16).padStart(6, '0')}">`;
html += `<h4>🔒 ${weapon.name}</h4>`;
html += `<p class="weapon-desc">${weapon.description || ''}</p>`;
html += `<div class="weapon-stats">`;
html += `<span>伤害: ${weapon.damage}</span>`;
html += `<span>射速: ${(1000/weapon.fireRate).toFixed(1)}/秒</span>`;
html += `</div>`;
html += `<button class="buy-btn ${canAfford ? '' : 'disabled'}"
onclick="shop.buyWeapon('${weaponKey}'); shop.renderShop();">
${canAfford ? `购买 (${weapon.price}💰)` : `需要 ${weapon.price}💰`}
</button>`;
html += '</div>';
}
html += '</div></div>';
}
// 武器升级区
html += '<div class="shop-section">';
html += '<h3>⬆️ 武器升级</h3>';
html += '<div class="weapon-upgrades">';
for (const [weaponKey, weapon] of Object.entries(CONFIG.WEAPONS)) {
if (weaponKey === 'sword') continue;
if (!weapons.isUnlocked(weaponKey)) continue; // 未解锁的不显示升级
const levels = this.weaponLevels[weaponKey];
const weaponStats = this.getWeaponStats(weaponKey) || weapon;
html += `<div class="weapon-upgrade-card" style="border-color: #${weapon.color.toString(16).padStart(6, '0')}">`;
html += `<h4>${weapon.name}</h4>`;
html += `<div class="weapon-stats">`;
html += `<span>伤害: ${weaponStats.damage?.toFixed(0) || weapon.damage}</span>`;
html += `<span>射速: ${(1000/(weaponStats.fireRate || weapon.fireRate)).toFixed(1)}/秒</span>`;
html += `</div>`;
// 伤害升级
const dmgLevel = levels?.damage || 0;
const dmgMax = CONFIG.WEAPON_UPGRADES.damage.maxLevel;
const dmgPrice = this.getUpgradePrice(weaponKey, 'damage', dmgLevel);
const dmgDisabled = dmgLevel >= dmgMax || this.coins < dmgPrice;
html += `<button class="upgrade-btn ${dmgDisabled ? 'disabled' : ''}"
onclick="shop.upgradeWeapon('${weaponKey}', 'damage'); shop.renderShop();">
伤害+ ${dmgLevel}/${dmgMax}
${dmgLevel >= dmgMax ? '已满级' : `(${dmgPrice}💰)`}
</button>`;
// 射速升级
const frLevel = levels?.fireRate || 0;
const frMax = CONFIG.WEAPON_UPGRADES.fireRate.maxLevel;
const frPrice = this.getUpgradePrice(weaponKey, 'fireRate', frLevel);
const frDisabled = frLevel >= frMax || this.coins < frPrice;
html += `<button class="upgrade-btn ${frDisabled ? 'disabled' : ''}"
onclick="shop.upgradeWeapon('${weaponKey}', 'fireRate'); shop.renderShop();">
射速+ ${frLevel}/${frMax}
${frLevel >= frMax ? '已满级' : `(${frPrice}💰)`}
</button>`;
// 子弹速度升级(仅非激光武器)
if (weapon.bulletSpeed) {
const spdLevel = levels?.speed || 0;
const spdMax = CONFIG.WEAPON_UPGRADES.speed.maxLevel;
const spdPrice = this.getUpgradePrice(weaponKey, 'speed', spdLevel);
const spdDisabled = spdLevel >= spdMax || this.coins < spdPrice;
html += `<button class="upgrade-btn ${spdDisabled ? 'disabled' : ''}"
onclick="shop.upgradeWeapon('${weaponKey}', 'speed'); shop.renderShop();">
弹速+ ${spdLevel}/${spdMax}
${spdLevel >= spdMax ? '已满级' : `(${spdPrice}💰)`}
</button>`;
}
html += '</div>';
}
html += '</div></div>';
// 通用升级区
html += '<div class="shop-section">';
html += '<h3>💪 属性强化</h3>';
html += '<div class="general-upgrades">';
const generalUpgrades = [
{ key: 'maxHealth', name: '生命上限', icon: '❤️', desc: '+20 最大生命' },
{ key: 'speed', name: '移动速度', icon: '👟', desc: '+0.5 移动速度' },
{ key: 'healthRegen', name: '生命回复', icon: '💚', desc: '+1/秒 自动回复' }
];
for (const upgrade of generalUpgrades) {
const level = this.generalUpgrades[upgrade.key];
const maxLevel = 5;
const price = this.getGeneralUpgradePrice(upgrade.key, level);
const disabled = level >= maxLevel || this.coins < price;
html += `<div class="general-upgrade-card">`;
html += `<span class="upgrade-icon">${upgrade.icon}</span>`;
html += `<div class="upgrade-info">`;
html += `<h4>${upgrade.name}</h4>`;
html += `<p>${upgrade.desc}</p>`;
html += `<p class="upgrade-level">等级: ${level}/${maxLevel}</p>`;
html += `</div>`;
html += `<button class="upgrade-btn ${disabled ? 'disabled' : ''}"
onclick="shop.upgradeGeneral('${upgrade.key}'); shop.renderShop();">
${level >= maxLevel ? '已满级' : `${price}💰`}
</button>`;
html += '</div>';
}
html += '</div></div>';
// 继续按钮
html += '<div class="shop-footer">';
html += `<button class="start-wave-btn" onclick="shop.closeShop(); game.startNextWave();">
▶️ 开始第 ${gameState.wave + 1} 波
</button>`;
html += '</div>';
shopContent.innerHTML = html;
}
// 购买武器
buyWeapon(weaponKey) {
if (!weapons || !weapons.weapons[weaponKey]) return false;
if (weapons.isUnlocked(weaponKey)) return false;
const weapon = CONFIG.WEAPONS[weaponKey];
const price = weapon.price || 200;
if (!this.spendCoins(price)) return false;
weapons.unlockWeapon(weaponKey);
if (audio) audio.playUpgrade();
return true;
}
// 重置商店(新游戏)
reset() {
this.coins = 0;
this.generalUpgrades = {
maxHealth: 0,
speed: 0,
healthRegen: 0
};
for (const weaponKey of Object.keys(CONFIG.WEAPONS)) {
this.weaponLevels[weaponKey] = {
damage: 0,
fireRate: 0,
speed: 0
};
}
// 重置武器解锁状态(只保留初始手枪和武士刀)
if (weapons && weapons.unlockedWeapons) {
weapons.unlockedWeapons.clear();
weapons.unlockedWeapons.add('pistol');
weapons.unlockedWeapons.add('sword');
}
// 重置武器属性
for (const [weaponKey, weapon] of Object.entries(weapons.weapons)) {
if (weapon.baseDamage) weapon.damage = weapon.baseDamage;
if (weapon.baseFireRate) weapon.fireRate = weapon.baseFireRate;
if (weapon.baseBulletSpeed) weapon.bulletSpeed = weapon.baseBulletSpeed;
}
}
}
const shop = new ShopSystem();
// waveManager.js
// 波次管理器
class WaveManager {
constructor() {
this.currentWave = 0;
this.waveActive = false;
this.waveBreak = false;
this.breakTime = 0;
this.enemiesRemaining = 0;
this.isBossWave = false;
}
init() {
this.currentWave = 0;
this.waveActive = false;
this.waveBreak = false;
return this;
}
startWave(waveNumber) {
this.currentWave = waveNumber;
this.waveActive = true;
this.waveBreak = false;
gameState.wave = waveNumber;
gameState.waveDamageTaken = 0; // 重置本波伤害记录
gameState.nextWave();
// 判断是否是Boss波
this.isBossWave = waveNumber % CONFIG.BOSS_WAVE_INTERVAL === 0;
if (this.isBossWave) {
this.spawnBossWave(waveNumber);
} else {
// 生成普通敌人
enemies.spawnWave(waveNumber);
}
// 播放波次开始音效
if (audio) audio.playWaveStart();
console.log(`Wave ${waveNumber} started! ${this.isBossWave ? '[BOSS WAVE]' : ''}`);
}
spawnBossWave(waveNumber) {
// Boss波:先清理小怪,再生成Boss
// 选择Boss类型
const bossTypes = Object.keys(CONFIG.BOSSES);
const bossIndex = Math.floor((waveNumber / CONFIG.BOSS_WAVE_INTERVAL - 1) % bossTypes.length);
const bossType = bossTypes[bossIndex];
// 生成一些小怪作为先锋
const minionCount = 3 + Math.floor(waveNumber / 10);
for (let i = 0; i < minionCount; i++) {
const angle = (Math.PI * 2 / minionCount) * i;
const distance = 10 + Math.random() * 5;
const pos = new THREE.Vector3(
Math.cos(angle) * distance,
0,
Math.sin(angle) * distance
);
enemies.spawnEnemy('grunt', pos);
}
// 延迟生成Boss
setTimeout(() => {
if (bossManager) {
const bossPos = new THREE.Vector3(0, 0, -15);
bossManager.spawnBoss(bossType, bossPos);
}
}, 2000);
}
onWaveComplete() {
if (!this.waveActive) return;
this.waveActive = false;
this.waveBreak = true;
// 检查完美波次成就
if (gameState.waveDamageTaken === 0 && gameState.wave > 1) {
if (achievements) {
achievements.unlock('perfect_wave');
}
}
// 检查波次成就
if (achievements) {
achievements.checkAchievements({
wave: this.currentWave
});
}
// 波次间回复
if (player) {
player.heal(25);
}
// 给予波次完成奖励金币
if (shop) {
const coinReward = 30 + this.currentWave * 10;
shop.addCoins(coinReward);
hud.showFloatingText(`+${coinReward} 💰`, 0, 2, 0xffff00);
}
// 打开商店(如果有商店系统)
if (shop && shop.openShop) {
shop.openShop();
if (input && input.isMobile) {
input.releasePointerLock();
}
} else {
this.breakTime = CONFIG.WAVE_BREAK_TIME;
}
// 波次完成提示
if (hud) {
hud.showWaveAnnouncement(`波次 ${this.currentWave} 完成!`);
}
if (audio) audio.playWaveComplete();
console.log(`Wave ${this.currentWave} complete!`);
}
update(deltaTime) {
if (this.waveBreak) {
// 如果商店打开,不自动开始下一波
const shopOpen = shop && shop.shopOpen;
if (!shopOpen) {
this.breakTime -= deltaTime;
if (this.breakTime <= 0) {
// 开始下一波
this.startWave(this.currentWave + 1);
}
}
}
// 检查波次是否完成
if (this.waveActive) {
const enemiesAlive = enemies ? enemies.list.filter(e => !e.dead).length : 0;
const bossAlive = bossManager && bossManager.activeBoss ? 1 : 0;
if (enemiesAlive === 0 && bossAlive === 0) {
// 延迟一点再判定完成,给死亡动画时间
setTimeout(() => {
if (this.waveActive) {
const stillAlive = enemies ? enemies.list.filter(e => !e.dead).length : 0;
const bossStillAlive = bossManager && bossManager.activeBoss ? 1 : 0;
if (stillAlive === 0 && bossStillAlive === 0) {
this.onWaveComplete();
}
}
}, 500);
}
}
}
reset() {
this.currentWave = 0;
this.waveActive = false;
this.waveBreak = false;
this.breakTime = 0;
this.isBossWave = false;
}
}
const waveManager = new WaveManager();
// arena.js
// 竞技场/场景
class Arena {
constructor() {
this.size = CONFIG.ARENA_SIZE;
this.mesh = null;
}
create() {
const scene = renderer.scene;
const size = this.size;
// 地面
const floorGeometry = new THREE.PlaneGeometry(size * 2, size * 2, 50, 50);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0x1a1a2e,
roughness: 0.8,
metalness: 0.2
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// 网格线
const gridHelper = new THREE.GridHelper(size * 2, 40, 0x00ffff, 0x004466);
gridHelper.position.y = 0.01;
gridHelper.material.opacity = 0.3;
gridHelper.material.transparent = true;
scene.add(gridHelper);
// 边界墙
const wallHeight = 5;
const wallMaterial = new THREE.MeshStandardMaterial({
color: 0x16213e,
emissive: 0x001122,
transparent: true,
opacity: 0.8
});
// 四面墙
const walls = [
{ pos: [0, wallHeight/2, -size], size: [size * 2, wallHeight, 0.5] },
{ pos: [0, wallHeight/2, size], size: [size * 2, wallHeight, 0.5] },
{ pos: [-size, wallHeight/2, 0], size: [0.5, wallHeight, size * 2] },
{ pos: [size, wallHeight/2, 0], size: [0.5, wallHeight, size * 2] },
];
for (const wall of walls) {
const wallGeo = new THREE.BoxGeometry(...wall.size);
const wallMesh = new THREE.Mesh(wallGeo, wallMaterial);
wallMesh.position.set(...wall.pos);
scene.add(wallMesh);
}
// 霓虹装饰柱
const pillarCount = 8;
const pillarColors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0088];
for (let i = 0; i < pillarCount; i++) {
const angle = (i / pillarCount) * Math.PI * 2;
const radius = size - 1;
const pillarGeo = new THREE.CylinderGeometry(0.3, 0.4, wallHeight, 8);
const pillarMat = new THREE.MeshStandardMaterial({
color: pillarColors[i % pillarColors.length],
emissive: pillarColors[i % pillarColors.length],
emissiveIntensity: 0.5,
metalness: 0.8,
roughness: 0.2
});
const pillar = new THREE.Mesh(pillarGeo, pillarMat);
pillar.position.set(
Math.cos(angle) * radius,
wallHeight / 2,
Math.sin(angle) * radius
);
pillar.castShadow = true;
scene.add(pillar);
// 顶部光球
const lightSphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
new THREE.MeshBasicMaterial({
color: pillarColors[i % pillarColors.length],
transparent: true,
opacity: 0.8
})
);
lightSphere.position.copy(pillar.position);
lightSphere.position.y = wallHeight;
scene.add(lightSphere);
// 点光源
const pointLight = new THREE.PointLight(
pillarColors[i % pillarColors.length],
0.5,
10
);
pointLight.position.copy(lightSphere.position);
scene.add(pointLight);
}
// 天空雾效
scene.fog = new THREE.Fog(0x0a0a1a, size * 0.5, size * 2);
// 添加一些浮空几何体装饰
this.addFloatingDecorations();
this.mesh = floor;
return this;
}
addFloatingDecorations() {
const scene = renderer.scene;
const shapes = [];
// 几个浮动的几何体
for (let i = 0; i < 6; i++) {
let geometry;
const type = i % 3;
if (type === 0) {
geometry = new THREE.TorusGeometry(1, 0.2, 8, 16);
} else if (type === 1) {
geometry = new THREE.OctahedronGeometry(0.8);
} else {
geometry = new THREE.IcosahedronGeometry(0.7, 0);
}
const material = new THREE.MeshBasicMaterial({
color: new THREE.Color().setHSL(i / 6, 0.8, 0.6),
wireframe: true,
transparent: true,
opacity: 0.3
});
const mesh = new THREE.Mesh(geometry, material);
const angle = (i / 6) * Math.PI * 2;
const radius = this.size * 0.6;
mesh.position.set(
Math.cos(angle) * radius,
6 + Math.random() * 4,
Math.sin(angle) * radius
);
mesh.userData = {
floatSpeed: 0.5 + Math.random() * 0.5,
rotationSpeed: 0.2 + Math.random() * 0.3,
baseY: mesh.position.y,
phase: Math.random() * Math.PI * 2
};
scene.add(mesh);
shapes.push(mesh);
}
this.floatingShapes = shapes;
// 动画这些浮动形状(在effects中更新)
const originalUpdate = effects.update.bind(effects);
effects.update = (dt) => {
originalUpdate(dt);
const time = performance.now() * 0.001;
for (const shape of shapes) {
if (!shape.parent) continue;
shape.position.y = shape.userData.baseY +
Math.sin(time * shape.userData.floatSpeed + shape.userData.phase) * 0.5;
shape.rotation.x += shape.userData.rotationSpeed * dt;
shape.rotation.y += shape.userData.rotationSpeed * dt * 0.7;
}
};
}
clear() {
// 清空竞技场(保留相机和光照)
// 这个方法根据需要实现
}
}
const arena = new Arena();
// player.js
// 玩家系统
class Player {
constructor() {
this.mesh = null;
this.health = 100;
this.maxHealth = 100;
this.speed = CONFIG.PLAYER_SPEED;
this.velocity = new THREE.Vector3();
this.onGround = false;
this.isSprinting = false;
this.isDodging = false;
this.dodgeCooldown = 0;
this.dodgeDirection = new THREE.Vector3();
this.dodgeTime = 0;
// 角色属性加成
this.character = null;
this.healthBonus = 0;
this.speedBonus = 0;
this.damageBonus = 0;
// 技能
this.specialCooldown = 0;
this.specialActive = false;
this.specialDuration = 0;
// 受击
this.hitFlashTime = 0;
// 碰撞
this.collisionRadius = 0.4;
this.height = 1.6;
}
init(characterKey = 'soldier') {
this.character = characterKey;
const charData = CONFIG.CHARACTERS[characterKey];
if (charData) {
this.healthBonus = charData.healthBonus;
this.speedBonus = charData.speedBonus;
this.damageBonus = charData.damageBonus;
}
this.maxHealth = CONFIG.PLAYER_MAX_HEALTH + this.healthBonus;
this.health = this.maxHealth;
this.speed = CONFIG.PLAYER_SPEED + this.speedBonus;
// 创建玩家相机容器(相机就是玩家视角)
this.mesh = new THREE.Object3D();
this.mesh.position.set(0, this.height, 0);
renderer.scene.add(this.mesh);
// 将相机附加到玩家
renderer.camera.position.set(0, 0, 0);
this.mesh.add(renderer.camera);
// 创建武器视模型
this.createViewModel();
return this;
}
createViewModel() {
// 创建武器视模型组
this.viewModel = new THREE.Group();
this.viewModel.position.set(0.3, -0.25, -0.5);
renderer.camera.add(this.viewModel);
// 默认武器
this.updateViewModel('pistol');
}
updateViewModel(weaponKey) {
// 清除旧武器
while (this.viewModel.children.length > 0) {
this.viewModel.remove(this.viewModel.children[0]);
}
const weaponConfig = CONFIG.WEAPONS[weaponKey];
if (!weaponConfig) return;
const color = weaponConfig.color;
if (weaponKey === 'pistol') {
// 手枪模型
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.08, 0.12, 0.25),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.3, metalness: 0.8, roughness: 0.2 })
);
this.viewModel.add(body);
const barrel = new THREE.Mesh(
new THREE.CylinderGeometry(0.025, 0.025, 0.15, 8),
new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.9, roughness: 0.1 })
);
barrel.rotation.x = -Math.PI / 2;
barrel.position.z = -0.15;
this.viewModel.add(barrel);
const grip = new THREE.Mesh(
new THREE.BoxGeometry(0.06, 0.15, 0.06),
new THREE.MeshStandardMaterial({ color: 0x222222 })
);
grip.position.y = -0.1;
grip.position.z = 0.05;
this.viewModel.add(grip);
} else if (weaponKey === 'shotgun') {
// 霰弹枪
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.1, 0.12, 0.4),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.2, metalness: 0.7, roughness: 0.3 })
);
this.viewModel.add(body);
// 双管
for (let i = -1; i <= 1; i += 2) {
const barrel = new THREE.Mesh(
new THREE.CylinderGeometry(0.02, 0.02, 0.35, 8),
new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.9 })
);
barrel.rotation.x = -Math.PI / 2;
barrel.position.set(i * 0.035, 0, -0.2);
this.viewModel.add(barrel);
}
} else if (weaponKey === 'laser') {
// 激光枪
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.09, 0.15, 0.5),
new THREE.MeshStandardMaterial({ color: 0x222222, metalness: 0.8, roughness: 0.2 })
);
this.viewModel.add(body);
const barrel = new THREE.Mesh(
new THREE.CylinderGeometry(0.04, 0.03, 0.3, 8),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.5, transparent: true, opacity: 0.8 })
);
barrel.rotation.x = -Math.PI / 2;
barrel.position.z = -0.3;
this.viewModel.add(barrel);
// 发光环
for (let i = 0; i < 3; i++) {
const ring = new THREE.Mesh(
new THREE.TorusGeometry(0.05, 0.01, 8, 16),
new THREE.MeshBasicMaterial({ color: color })
);
ring.position.z = -0.15 + i * 0.1;
this.viewModel.add(ring);
}
} else if (weaponKey === 'smg') {
// 霓虹冲锋枪
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.06, 0.1, 0.35),
new THREE.MeshStandardMaterial({ color: 0x222222, metalness: 0.8, roughness: 0.2 })
);
this.viewModel.add(body);
const barrel = new THREE.Mesh(
new THREE.CylinderGeometry(0.015, 0.02, 0.2, 6),
new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.9 })
);
barrel.rotation.x = -Math.PI / 2;
barrel.position.z = -0.25;
this.viewModel.add(barrel);
// 霓虹发光条
const glow = new THREE.Mesh(
new THREE.BoxGeometry(0.02, 0.02, 0.3),
new THREE.MeshBasicMaterial({ color: color })
);
glow.position.set(0, 0.04, 0);
this.viewModel.add(glow);
// 弹夹
const mag = new THREE.Mesh(
new THREE.BoxGeometry(0.04, 0.15, 0.03),
new THREE.MeshStandardMaterial({ color: 0x1a1a1a })
);
mag.position.set(0, -0.08, 0.05);
this.viewModel.add(mag);
} else if (weaponKey === 'plasma') {
// 等离子步枪
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.08, 0.12, 0.45),
new THREE.MeshStandardMaterial({ color: 0x1a1a2e, metalness: 0.7, roughness: 0.3 })
);
this.viewModel.add(body);
// 等离子发射管
const barrel = new THREE.Mesh(
new THREE.CylinderGeometry(0.03, 0.025, 0.25, 8),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.6, transparent: true, opacity: 0.7 })
);
barrel.rotation.x = -Math.PI / 2;
barrel.position.z = -0.3;
this.viewModel.add(barrel);
// 能量罐
const canister = new THREE.Mesh(
new THREE.CylinderGeometry(0.03, 0.03, 0.08, 8),
new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.8 })
);
canister.position.set(0.05, -0.02, 0.1);
canister.rotation.z = Math.PI / 6;
this.viewModel.add(canister);
// 散热片
for (let i = 0; i < 3; i++) {
const fin = new THREE.Mesh(
new THREE.BoxGeometry(0.07, 0.01, 0.02),
new THREE.MeshStandardMaterial({ color: 0x444444 })
);
fin.position.set(0, 0.05, -0.05 + i * 0.05);
this.viewModel.add(fin);
}
} else if (weaponKey === 'rocket') {
// 爆裂火箭筒
const body = new THREE.Mesh(
new THREE.CylinderGeometry(0.05, 0.06, 0.5, 8),
new THREE.MeshStandardMaterial({ color: 0x2a2a2a, metalness: 0.6, roughness: 0.4 })
);
body.rotation.x = -Math.PI / 2;
this.viewModel.add(body);
// 瞄准镜
const scope = new THREE.Mesh(
new THREE.CylinderGeometry(0.015, 0.015, 0.08, 6),
new THREE.MeshStandardMaterial({ color: 0x333333 })
);
scope.rotation.z = Math.PI / 2;
scope.position.set(0, 0.05, 0.05);
this.viewModel.add(scope);
// 火箭弹预览
const rocket = new THREE.Mesh(
new THREE.CylinderGeometry(0.02, 0.015, 0.15, 6),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.3 })
);
rocket.rotation.x = -Math.PI / 2;
rocket.position.z = -0.2;
this.viewModel.add(rocket);
// 握把
const grip = new THREE.Mesh(
new THREE.BoxGeometry(0.04, 0.12, 0.04),
new THREE.MeshStandardMaterial({ color: 0x1a1a1a })
);
grip.position.set(0, -0.08, 0.1);
this.viewModel.add(grip);
} else if (weaponKey === 'sword') {
// 武士刀
const blade = new THREE.Mesh(
new THREE.BoxGeometry(0.03, 0.8, 0.01),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.4, metalness: 0.9 })
);
blade.position.set(0, 0.3, -0.3);
blade.rotation.x = -0.3;
this.viewModel.add(blade);
const hilt = new THREE.Mesh(
new THREE.BoxGeometry(0.08, 0.1, 0.03),
new THREE.MeshStandardMaterial({ color: 0x333333 })
);
hilt.position.set(0, -0.1, -0.3);
this.viewModel.add(hilt);
}
}
update(deltaTime) {
if (!gameState.isPlaying()) return;
// 冷却更新
if (this.dodgeCooldown > 0) this.dodgeCooldown -= deltaTime;
if (this.specialCooldown > 0) this.specialCooldown -= deltaTime;
if (this.hitFlashTime > 0) this.hitFlashTime -= deltaTime;
// 特殊技能持续时间
if (this.specialActive) {
this.specialDuration -= deltaTime;
if (this.specialDuration <= 0) {
this.endSpecial();
}
}
// 生命回复
if (this.regenRate && this.regenRate > 0 && this.health < this.maxHealth) {
this.health = Math.min(this.maxHealth, this.health + this.regenRate * deltaTime);
// 更新HUD(降低频率)
if (Math.random() < 0.1) {
// 偶尔触发HUD更新,避免太频繁
}
}
// 翻滚
if (this.isDodging) {
this.dodgeTime -= deltaTime;
if (this.dodgeTime <= 0) {
this.isDodging = false;
}
}
// 移动
this.handleMovement(deltaTime);
// 重力
this.applyGravity(deltaTime);
// 视角控制
this.handleLook();
// 武器切换
this.handleWeaponSwitch();
// 近战攻击
if ((input.isKeyPressed('f') || input.mouseButtons.right) && !this.isAttacking) {
this.meleeAttack();
}
// 特殊技能
if (input.isKeyPressed('e') && this.specialCooldown <= 0) {
this.activateSpecial();
}
// 武器后坐力恢复
if (this.recoilAmount > 0) {
this.recoilAmount -= deltaTime * 5;
this.recoilAmount = Math.max(0, this.recoilAmount);
this.viewModel.position.z = -0.5 + this.recoilAmount * 0.1;
this.viewModel.rotation.x = this.recoilAmount * 0.5;
}
// 检查死亡
if (this.health <= 0) {
this.die();
}
}
handleMovement(deltaTime) {
const moveSpeed = this.isSprinting ? CONFIG.PLAYER_SPRINT_SPEED + this.speedBonus : this.speed;
// 获取输入
const moveForward = input.getAxis(false); // W/S
const moveStrafe = input.getAxis(true); // A/D
// 计算移动方向(基于相机朝向)
const direction = new THREE.Vector3();
const cameraDirection = new THREE.Vector3();
renderer.camera.getWorldDirection(cameraDirection);
cameraDirection.y = 0;
cameraDirection.normalize();
const right = new THREE.Vector3();
right.crossVectors(cameraDirection, new THREE.Vector3(0, 1, 0)).normalize();
direction.addScaledVector(cameraDirection, moveForward);
direction.addScaledVector(right, moveStrafe);
if (direction.length() > 0) {
direction.normalize();
}
// 翻滚
if (this.isDodging) {
direction.copy(this.dodgeDirection);
const dodgeSpeed = 15;
this.mesh.position.x += direction.x * dodgeSpeed * deltaTime;
this.mesh.position.z += direction.z * dodgeSpeed * deltaTime;
} else {
// 正常移动
this.mesh.position.x += direction.x * moveSpeed * deltaTime;
this.mesh.position.z += direction.z * moveSpeed * deltaTime;
}
// 边界限制
const boundary = CONFIG.ARENA_SIZE - 2;
this.mesh.position.x = Math.max(-boundary, Math.min(boundary, this.mesh.position.x));
this.mesh.position.z = Math.max(-boundary, Math.min(boundary, this.mesh.position.z));
// 冲刺检测
this.isSprinting = input.isKeyPressed('shift') && this.onGround && moveForward > 0;
// 翻滚
if (input.isKeyPressed('control') && this.dodgeCooldown <= 0 && this.onGround) {
this.dodge(direction);
}
// 跳跃
if (input.isKeyPressed(' ') && this.onGround) {
this.velocity.y = CONFIG.PLAYER_JUMP_FORCE;
this.onGround = false;
if (audio) audio.playJump();
}
}
dodge(direction) {
if (direction.length() === 0) {
// 没有方向输入就向后翻滚
const cameraDir = new THREE.Vector3();
renderer.camera.getWorldDirection(cameraDir);
cameraDir.y = 0;
cameraDir.normalize();
this.dodgeDirection.copy(cameraDir).negate();
} else {
this.dodgeDirection.copy(direction).normalize();
}
this.isDodging = true;
this.dodgeTime = 0.3;
this.dodgeCooldown = 1.5;
this.isInvincible = true;
setTimeout(() => {
this.isInvincible = false;
}, 300);
if (audio) audio.playDodge();
}
applyGravity(deltaTime) {
this.velocity.y -= CONFIG.PLAYER_GRAVITY * deltaTime;
this.mesh.position.y += this.velocity.y * deltaTime;
// 地面检测
if (this.mesh.position.y <= this.height) {
this.mesh.position.y = this.height;
this.velocity.y = 0;
this.onGround = true;
}
}
handleLook() {
if (!input.mouse.locked && !input.isMobile) return;
// 鼠标/触摸视角
if (Math.abs(input.mouse.dx) > 0 || Math.abs(input.mouse.dy) > 0) {
const sensitivity = input.isMobile ? CONFIG.MOBILE_SENSITIVITY : CONFIG.PLAYER_MOUSE_SENSITIVITY;
// 水平旋转(绕Y轴)
this.mesh.rotation.y -= input.mouse.dx * sensitivity;
// 垂直旋转(绕X轴,限制范围)
renderer.camera.rotation.x -= input.mouse.dy * sensitivity;
renderer.camera.rotation.x = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, renderer.camera.rotation.x));
}
}
handleWeaponSwitch() {
// 武器列表(按切换顺序)
const weaponList = ['pistol', 'shotgun', 'smg', 'plasma', 'laser', 'rocket', 'sword'];
// 数字键切换
for (let i = 0; i < weaponList.length; i++) {
const key = (i + 1).toString();
if (input.isKeyPressed(key) && weapons.isUnlocked(weaponList[i])) {
weapons.switchWeapon(weaponList[i]);
this.updateViewModel(weaponList[i]);
hud.updateWeapon(CONFIG.WEAPONS[weaponList[i]].name);
break;
}
}
// Q/E快速切换下一把/上一把已解锁武器
if (input.isKeyPressed('q') || input.isKeyPressed('e')) {
const currentIndex = weaponList.indexOf(weapons.currentWeapon);
const direction = input.isKeyPressed('e') ? 1 : -1;
let nextIndex = (currentIndex + direction + weaponList.length) % weaponList.length;
let attempts = 0;
while (!weapons.isUnlocked(weaponList[nextIndex]) && attempts < weaponList.length) {
nextIndex = (nextIndex + direction + weaponList.length) % weaponList.length;
attempts++;
}
if (weapons.isUnlocked(weaponList[nextIndex])) {
weapons.switchWeapon(weaponList[nextIndex]);
this.updateViewModel(weaponList[nextIndex]);
hud.updateWeapon(CONFIG.WEAPONS[weaponList[nextIndex]].name);
}
}
}
takeDamage(amount) {
if (this.isInvincible) return false;
// 护盾先吸收伤害
if (this.shield && this.shield > 0) {
const shieldAbsorb = Math.min(this.shield, amount);
this.shield -= shieldAbsorb;
amount -= shieldAbsorb;
if (amount <= 0) {
// 护盾完全吸收了伤害
if (effects) effects.shake(0.1, 0.05);
return true;
}
}
this.health -= amount;
this.health = Math.max(0, this.health);
// 记录本波伤害(用于完美波次成就)
if (gameState && gameState.waveDamageTaken !== undefined) {
gameState.waveDamageTaken += amount;
}
hud.updateHealth(this.health, this.maxHealth);
hud.showDamage(0.5);
if (audio) audio.playHurt();
// 屏幕震动
if (effects) {
effects.shake(0.3, 0.1);
}
// 检查死亡
if (this.health <= 0) {
this.die();
}
return true;
}
heal(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
hud.updateHealth(this.health, this.maxHealth);
}
meleeAttack() {
this.isAttacking = true;
this.recoilAmount = 0.3;
// 挥刀动画
const originalRot = this.viewModel.rotation.z;
this.viewModel.rotation.z = -0.5;
setTimeout(() => {
this.viewModel.rotation.z = 0.5;
setTimeout(() => {
this.viewModel.rotation.z = originalRot;
}, 100);
}, 50);
// 检测命中
const range = CONFIG.WEAPONS.sword.range;
const damage = CONFIG.WEAPONS.sword.damage * (1 + this.damageBonus);
if (enemies) {
const cameraDir = new THREE.Vector3();
renderer.camera.getWorldDirection(cameraDir);
for (const enemy of enemies.list) {
if (enemy.dead) continue;
const toEnemy = new THREE.Vector3();
toEnemy.subVectors(enemy.mesh.position, this.mesh.position);
const distance = toEnemy.length();
toEnemy.y = 0;
toEnemy.normalize();
const dot = cameraDir.dot(toEnemy);
if (distance < range && dot > 0.7) {
enemy.takeDamage(damage, cameraDir);
// 命中特效
if (effects) {
effects.createHitEffect(enemy.mesh.position.clone(), CONFIG.WEAPONS.sword.color);
effects.shake(0.2, 0.05);
effects.slowMotion(0.05, 0.1);
}
if (audio) audio.playHit();
}
}
}
setTimeout(() => {
this.isAttacking = false;
}, CONFIG.WEAPONS.sword.fireRate);
}
activateSpecial() {
const charData = CONFIG.CHARACTERS[this.character];
if (!charData) return;
this.specialActive = true;
this.specialCooldown = 30; // 30秒冷却
// 根据角色应用不同效果
switch (this.character) {
case 'soldier':
// 狂暴:伤害翻倍
this.specialDuration = 8;
this.damageBonus += 1.0; // 额外+100%
if (effects) effects.showScreenGlow(0xff0000, 0.3);
break;
case 'ninja':
// 疾风步:加速+无敌
this.specialDuration = 5;
this.speedBonus += 10;
this.isInvincible = true;
if (effects) effects.showScreenGlow(0x8844ff, 0.3);
break;
case 'engineer':
// 召唤炮塔
this.specialDuration = 15;
this.summonTurret();
break;
case 'medic':
// 治疗脉冲
this.specialDuration = 0;
this.heal(50);
if (effects) effects.showScreenGlow(0x44ff88, 0.5);
if (audio) audio.playHeal();
break;
}
if (audio) audio.playSpecial();
}
endSpecial() {
this.specialActive = false;
switch (this.character) {
case 'soldier':
this.damageBonus = CONFIG.CHARACTERS.soldier.damageBonus;
break;
case 'ninja':
this.speedBonus = CONFIG.CHARACTERS.ninja.speedBonus;
this.isInvincible = false;
break;
}
if (effects) effects.hideScreenGlow();
}
summonTurret() {
// 召唤一个自动攻击的炮塔
const turret = new THREE.Group();
const base = new THREE.Mesh(
new THREE.CylinderGeometry(0.5, 0.6, 0.3, 8),
new THREE.MeshStandardMaterial({ color: 0xffaa00, metalness: 0.7 })
);
turret.add(base);
const gun = new THREE.Mesh(
new THREE.BoxGeometry(0.15, 0.15, 0.8),
new THREE.MeshStandardMaterial({ color: 0x666666, metalness: 0.8 })
);
gun.position.y = 0.3;
turret.add(gun);
// 放在玩家旁边
const spawnPos = this.mesh.position.clone();
spawnPos.x += 1;
spawnPos.y = 0.15;
turret.position.copy(spawnPos);
renderer.scene.add(turret);
// 炮塔AI - 简化版
this.turret = {
mesh: turret,
damage: 20,
fireRate: 500,
lastFire: 0,
range: 20,
lifetime: 15 // 15秒后消失
};
// 添加到游戏更新
if (!game.turrets) game.turrets = [];
game.turrets.push(this.turret);
}
die() {
if (gameState.isGameOver()) return;
gameState.setState('gameover');
if (audio) audio.playGameOver();
}
reset() {
this.mesh.position.set(0, this.height, 0);
this.mesh.rotation.set(0, 0, 0);
renderer.camera.rotation.x = 0;
this.velocity.set(0, 0, 0);
this.health = this.maxHealth;
this.dodgeCooldown = 0;
this.specialCooldown = 0;
this.isDodging = false;
this.specialActive = false;
// 重置角色属性
const charData = CONFIG.CHARACTERS[this.character];
if (charData) {
this.healthBonus = charData.healthBonus;
this.speedBonus = charData.speedBonus;
this.damageBonus = charData.damageBonus;
}
hud.updateHealth(this.health, this.maxHealth);
}
}
const player = new Player();
// mobile.js
// 移动端适配
class MobileControls {
constructor() {
this.isMobile = false;
this.active = false;
// 虚拟摇杆
this.joystick = {
active: false,
startX: 0,
startY: 0,
currentX: 0,
currentY: 0,
deltaX: 0,
deltaY: 0,
maxDistance: 50
};
// 触摸视角
this.touchLook = {
active: false,
lastX: 0,
lastY: 0,
sensitivity: 0.005
};
// 按钮状态
this.buttons = {
fire: false,
jump: false,
melee: false,
weapon1: false,
weapon2: false,
weapon3: false
};
this.isFiring = false;
}
init() {
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|| ('ontouchstart' in window)
|| (navigator.maxTouchPoints > 0);
if (this.isMobile) {
this.active = true;
this.createControls();
this.setupEventListeners();
console.log('Mobile controls initialized');
}
return this;
}
createControls() {
// 左侧虚拟摇杆容器
const joystickContainer = document.createElement('div');
joystickContainer.id = 'joystick-container';
joystickContainer.style.cssText = `
position: fixed;
left: 30px;
bottom: 30px;
width: 120px;
height: 120px;
background: rgba(0, 0, 0, 0.3);
border: 2px solid rgba(0, 255, 255, 0.5);
border-radius: 50%;
z-index: 200;
touch-action: none;
`;
const joystickKnob = document.createElement('div');
joystickKnob.id = 'joystick-knob';
joystickKnob.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
background: rgba(0, 255, 255, 0.6);
border-radius: 50%;
box-shadow: 0 0 15px rgba(0, 255, 255, 0.8);
`;
joystickContainer.appendChild(joystickKnob);
document.body.appendChild(joystickContainer);
// 右侧触摸视角区域
const touchZone = document.createElement('div');
touchZone.id = 'touch-look-zone';
touchZone.style.cssText = `
position: fixed;
right: 0;
top: 0;
width: 50%;
height: 100%;
z-index: 150;
touch-action: none;
`;
document.body.appendChild(touchZone);
// 开火按钮
const fireBtn = document.createElement('button');
fireBtn.id = 'fire-btn';
fireBtn.innerHTML = '🔫';
fireBtn.style.cssText = `
position: fixed;
right: 30px;
bottom: 80px;
width: 70px;
height: 70px;
background: rgba(255, 0, 100, 0.6);
border: 2px solid rgba(255, 0, 100, 0.8);
border-radius: 50%;
font-size: 28px;
z-index: 200;
touch-action: none;
display: flex;
align-items: center;
justify-content: center;
`;
document.body.appendChild(fireBtn);
// 跳跃按钮
const jumpBtn = document.createElement('button');
jumpBtn.id = 'jump-btn';
jumpBtn.innerHTML = '⬆️';
jumpBtn.style.cssText = `
position: fixed;
right: 120px;
bottom: 30px;
width: 60px;
height: 60px;
background: rgba(0, 255, 100, 0.6);
border: 2px solid rgba(0, 255, 100, 0.8);
border-radius: 50%;
font-size: 24px;
z-index: 200;
touch-action: none;
display: flex;
align-items: center;
justify-content: center;
`;
document.body.appendChild(jumpBtn);
// 武器切换按钮
const weaponButtons = ['1', '2', '3', '4'];
const weaponIcons = ['🔫', '💥', '⚡', '⚔️'];
weaponButtons.forEach((num, i) => {
const btn = document.createElement('button');
btn.id = `weapon-${num}-btn`;
btn.innerHTML = weaponIcons[i];
btn.style.cssText = `
position: fixed;
right: ${30 + i * 55}px;
bottom: 170px;
width: 45px;
height: 45px;
background: rgba(255, 255, 0, 0.5);
border: 2px solid rgba(255, 255, 0, 0.7);
border-radius: 8px;
font-size: 18px;
z-index: 200;
touch-action: none;
display: flex;
align-items: center;
justify-content: center;
`;
btn.dataset.weapon = ['pistol', 'shotgun', 'laser', 'sword'][i];
document.body.appendChild(btn);
});
}
setupEventListeners() {
const joystickContainer = document.getElementById('joystick-container');
const joystickKnob = document.getElementById('joystick-knob');
const touchZone = document.getElementById('touch-look-zone');
const fireBtn = document.getElementById('fire-btn');
const jumpBtn = document.getElementById('jump-btn');
// 虚拟摇杆
joystickContainer.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = joystickContainer.getBoundingClientRect();
this.joystick.active = true;
this.joystick.startX = rect.left + rect.width / 2;
this.joystick.startY = rect.top + rect.height / 2;
this.updateJoystick(touch.clientX, touch.clientY, joystickKnob);
}, { passive: false });
joystickContainer.addEventListener('touchmove', (e) => {
e.preventDefault();
if (this.joystick.active) {
const touch = e.touches[0];
this.updateJoystick(touch.clientX, touch.clientY, joystickKnob);
}
}, { passive: false });
const endJoystick = () => {
this.joystick.active = false;
this.joystick.deltaX = 0;
this.joystick.deltaY = 0;
if (joystickKnob) {
joystickKnob.style.transform = 'translate(-50%, -50%)';
}
};
joystickContainer.addEventListener('touchend', endJoystick);
joystickContainer.addEventListener('touchcancel', endJoystick);
// 触摸视角
touchZone.addEventListener('touchstart', (e) => {
e.preventDefault();
if (e.touches.length > 0) {
this.touchLook.active = true;
this.touchLook.lastX = e.touches[0].clientX;
this.touchLook.lastY = e.touches[0].clientY;
}
}, { passive: false });
touchZone.addEventListener('touchmove', (e) => {
e.preventDefault();
if (this.touchLook.active && gameState.isPlaying()) {
const touch = e.touches[0];
const dx = touch.clientX - this.touchLook.lastX;
const dy = touch.clientY - this.touchLook.lastY;
// 更新相机旋转
if (player && player.mesh) {
player.mesh.rotation.y -= dx * this.touchLook.sensitivity;
renderer.camera.rotation.x -= dy * this.touchLook.sensitivity;
renderer.camera.rotation.x = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, renderer.camera.rotation.x));
}
this.touchLook.lastX = touch.clientX;
this.touchLook.lastY = touch.clientY;
}
}, { passive: false });
const endTouchLook = () => {
this.touchLook.active = false;
};
touchZone.addEventListener('touchend', endTouchLook);
touchZone.addEventListener('touchcancel', endTouchLook);
// 开火按钮
fireBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
this.isFiring = true;
this.buttons.fire = true;
}, { passive: false });
fireBtn.addEventListener('touchend', (e) => {
e.preventDefault();
this.isFiring = false;
this.buttons.fire = false;
});
// 跳跃按钮
jumpBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
this.buttons.jump = true;
// 模拟跳跃输入
if (player && player.onGround && gameState.isPlaying()) {
player.velocity.y = CONFIG.PLAYER_JUMP_FORCE;
player.onGround = false;
if (audio) audio.playJump();
}
}, { passive: false });
jumpBtn.addEventListener('touchend', () => {
this.buttons.jump = false;
});
// 武器切换按钮
for (let i = 1; i <= 4; i++) {
const btn = document.getElementById(`weapon-${i}-btn`);
if (btn) {
btn.addEventListener('touchstart', (e) => {
e.preventDefault();
const weapon = btn.dataset.weapon;
if (weapons && weapons.switchWeapon) {
weapons.switchWeapon(weapon);
hud.updateWeapon(CONFIG.WEAPONS[weapon].name);
}
}, { passive: false });
}
}
}
updateJoystick(clientX, clientY, knob) {
const dx = clientX - this.joystick.startX;
const dy = clientY - this.joystick.startY;
const distance = Math.sqrt(dx * dx + dy * dy);
const maxDist = this.joystick.maxDistance;
if (distance > maxDist) {
const angle = Math.atan2(dy, dx);
this.joystick.deltaX = Math.cos(angle) * maxDist / maxDist;
this.joystick.deltaY = Math.sin(angle) * maxDist / maxDist;
knob.style.transform = `translate(calc(-50% + ${Math.cos(angle) * maxDist}px), calc(-50% + ${Math.sin(angle) * maxDist}px))`;
} else {
this.joystick.deltaX = dx / maxDist;
this.joystick.deltaY = dy / maxDist;
knob.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
}
}
getMoveInput() {
return {
x: this.joystick.deltaX,
y: -this.joystick.deltaY // 负值因为向下是正Y
};
}
show() {
const controls = ['joystick-container', 'touch-look-zone', 'fire-btn', 'jump-btn', 'weapon-1-btn', 'weapon-2-btn', 'weapon-3-btn', 'weapon-4-btn'];
controls.forEach(id => {
const el = document.getElementById(id);
if (el) el.style.display = 'flex';
});
}
hide() {
const controls = ['joystick-container', 'touch-look-zone', 'fire-btn', 'jump-btn', 'weapon-1-btn', 'weapon-2-btn', 'weapon-3-btn', 'weapon-4-btn'];
controls.forEach(id => {
const el = document.getElementById(id);
if (el) el.style.display = 'none';
});
}
}
const mobile = new MobileControls();
// autoDemo.js
// 自动演示模式 - AI自动玩游戏,用于首页展示/吸引模式
class AutoDemo {
constructor() {
this.enabled = false;
this.active = false;
this.state = {
targetPosition: null,
changeDirectionTimer: 0,
shootTimer: 0,
weaponSwitchTimer: 0,
jumpTimer: 0
};
}
init() {
this.state.targetPosition = new THREE.Vector3(
(Math.random() - 0.5) * CONFIG.ARENA_SIZE * 1.2,
1.7,
(Math.random() - 0.5) * CONFIG.ARENA_SIZE * 1.2
);
return this;
}
start() {
if (!gameState.isPlaying()) {
gameState.setState('playing');
game.start();
}
this.active = true;
this.enabled = true;
}
stop() {
this.active = false;
this.enabled = false;
}
update(deltaTime) {
if (!this.active || !gameState.isPlaying()) return;
const state = this.state;
state.changeDirectionTimer -= deltaTime;
state.shootTimer -= deltaTime;
state.weaponSwitchTimer -= deltaTime;
state.jumpTimer -= deltaTime;
// 找最近的敌人
let closestEnemy = null;
let closestDist = Infinity;
for (const enemy of enemies.list) {
if (enemy.dead) continue;
const dist = player.mesh.position.distanceTo(enemy.mesh.position);
if (dist < closestDist) {
closestDist = dist;
closestEnemy = enemy;
}
}
// 如果有敌人,面向敌人并射击
if (closestEnemy) {
// 面向敌人
const direction = new THREE.Vector3();
direction.subVectors(closestEnemy.mesh.position, player.mesh.position);
direction.y = 0;
direction.normalize();
const targetAngle = Math.atan2(direction.x, direction.z);
player.mesh.rotation.y = targetAngle;
// 移动(保持距离)
const idealDistance = 15;
if (closestDist > idealDistance + 2) {
// 靠近
player.mesh.position.addScaledVector(direction, 5 * deltaTime);
} else if (closestDist < idealDistance - 2) {
// 后退
player.mesh.position.addScaledVector(direction, -3 * deltaTime);
} else {
// 侧移
const strafe = Math.sin(performance.now() * 0.002) * 2;
const right = new THREE.Vector3();
right.crossVectors(direction, new THREE.Vector3(0, 1, 0));
player.mesh.position.addScaledVector(right, strafe * deltaTime);
}
// 射击
if (state.shootTimer <= 0) {
weapons.fire(player, enemies.list);
state.shootTimer = 0.15 + Math.random() * 0.1;
}
// 跳跃(随机)
if (state.jumpTimer <= 0 && player.onGround && Math.random() < 0.3) {
player.velocity.y = CONFIG.PLAYER_JUMP_FORCE;
player.onGround = false;
state.jumpTimer = 2 + Math.random() * 3;
}
} else {
// 没有敌人,随便逛逛
if (state.changeDirectionTimer <= 0) {
state.targetPosition = new THREE.Vector3(
(Math.random() - 0.5) * CONFIG.ARENA_SIZE * 1.2,
1.7,
(Math.random() - 0.5) * CONFIG.ARENA_SIZE * 1.2
);
state.changeDirectionTimer = 3 + Math.random() * 4;
}
// 向目标移动
const direction = new THREE.Vector3();
direction.subVectors(state.targetPosition, player.mesh.position);
direction.y = 0;
if (direction.length() > 1) {
direction.normalize();
player.mesh.position.addScaledVector(direction, 6 * deltaTime);
player.mesh.rotation.y = Math.atan2(direction.x, direction.z);
}
// 随机射击
if (state.shootTimer <= 0 && Math.random() < 0.3) {
weapons.fire(player, enemies.list);
}
if (state.shootTimer <= 0) {
state.shootTimer = 0.5 + Math.random() * 1;
}
}
// 切换武器
if (state.weaponSwitchTimer <= 0) {
const weapons_list = ['pistol', 'shotgun', 'laser'];
const randomWeapon = weapons_list[Math.floor(Math.random() * weapons_list.length)];
weapons.switchWeapon(randomWeapon);
hud.updateWeapon(CONFIG.WEAPONS[randomWeapon].name);
state.weaponSwitchTimer = 5 + Math.random() * 10;
}
// 边界限制
const boundary = CONFIG.ARENA_SIZE - 1;
player.mesh.position.x = Math.max(-boundary, Math.min(boundary, player.mesh.position.x));
player.mesh.position.z = Math.max(-boundary, Math.min(boundary, player.mesh.position.z));
}
}
const autoDemo = new AutoDemo();
// gameLoop.js
// 游戏主循环
class Game {
constructor() {
this.lastTime = 0;
this.deltaTime = 0;
this.isRunning = false;
this.animationId = null;
this.enemies = [];
this.bullets = [];
this.particles = [];
this.turrets = [];
this.waveManager = null;
}
init() {
// 初始化所有系统
renderer.init();
input.init();
hud.init();
ui.init();
audio.init();
effects.init();
// 创建竞技场
arena.create();
// 初始化武器系统
weapons.init();
// 初始化商店系统
if (shop && shop.init) {
shop.init();
}
// 初始化敌人系统
enemies.init();
// 初始化波次管理器
this.waveManager = waveManager;
if (waveManager && waveManager.init) {
waveManager.init();
}
// 初始化自动演示模式
if (autoDemo && autoDemo.init) {
autoDemo.init();
}
// 移动端适配
if (input.isMobile && mobile) {
mobile.init();
}
// 显示主菜单
ui.showMenu('main');
hud.hide();
// 开始渲染循环
this.isRunning = true;
this.animate();
console.log('Neon FPS initialized successfully!');
console.log('Character:', gameState.selectedCharacter);
console.log('Mobile:', input.isMobile);
}
start() {
// 重置游戏状态
gameState.score = 0;
gameState.wave = 0;
gameState.kills = 0;
gameState.waveDamageTaken = 0;
gameState.coinsCollected = 0;
// 重置商店
if (shop && shop.reset) {
shop.reset();
}
// 初始化玩家
player.init(gameState.selectedCharacter);
// 清空敌人和子弹
enemies.clear();
this.clearBullets();
this.clearParticles();
this.turrets = [];
// 重置Boss
if (bossManager && bossManager.reset) {
bossManager.reset();
}
// 重置拾取物
if (pickups && pickups.reset) {
pickups.reset();
}
// 开始第一波
if (this.waveManager && this.waveManager.startWave) {
setTimeout(() => {
this.waveManager.startWave(1);
}, 2000);
}
hud.updateHealth(player.health, player.maxHealth);
hud.updateWeapon(CONFIG.WEAPONS[weapons.currentWeapon].name);
// 加载成就
if (achievements && achievements.load) {
achievements.load();
}
}
reset() {
// 清空所有游戏对象
enemies.clear();
this.clearBullets();
this.clearParticles();
this.turrets = [];
if (player.mesh) {
player.reset();
}
}
animate() {
if (!this.isRunning) return;
this.animationId = requestAnimationFrame(() => this.animate());
const currentTime = performance.now();
this.deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
this.lastTime = currentTime;
// 慢动作效果
if (effects && effects.slowMotionFactor !== undefined && effects.slowMotionFactor < 1) {
this.deltaTime *= effects.slowMotionFactor;
effects.updateSlowMotion(this.deltaTime);
}
if (gameState.isPlaying()) {
this.update();
}
// 渲染
renderer.render();
// 输入后置处理
input.lateUpdate();
}
update() {
// 更新玩家
player.update(this.deltaTime);
// 更新武器/射击
if (input.mouseButtons.left || (input.isMobile && mobile && mobile.isFiring)) {
weapons.fire(player, enemies.list);
}
// 更新子弹
this.updateBullets();
// 更新敌人
enemies.update(this.deltaTime, player.mesh.position);
// 更新Boss
if (bossManager && bossManager.update) {
bossManager.update(this.deltaTime, player.mesh.position);
// 更新Boss血条
const activeBoss = bossManager.getActiveBoss();
if (activeBoss && hud) {
hud.updateBossHealth(activeBoss.health, activeBoss.maxHealth);
}
}
// 更新拾取物
if (pickups && pickups.update) {
pickups.update(this.deltaTime, player.mesh.position);
}
// 更新波次
if (this.waveManager && this.waveManager.update) {
this.waveManager.update(this.deltaTime);
}
// 更新粒子
if (particles && particles.update) {
particles.update(this.deltaTime);
}
// 更新特效
if (effects && effects.update) {
effects.update(this.deltaTime);
}
// 更新炮塔
this.updateTurrets();
// 自动演示模式
if (autoDemo && autoDemo.enabled) {
autoDemo.update(this.deltaTime);
}
}
updateBullets() {
for (let i = this.bullets.length - 1; i >= 0; i--) {
const bullet = this.bullets[i];
// 移动子弹
bullet.mesh.position.addScaledVector(bullet.velocity, this.deltaTime);
bullet.life -= this.deltaTime;
// 生命结束
if (bullet.life <= 0) {
renderer.scene.remove(bullet.mesh);
this.bullets.splice(i, 1);
continue;
}
// 碰撞检测(与敌人)
if (bullet.isPlayerBullet) {
for (const enemy of enemies.list) {
if (enemy.dead) continue;
const dx = bullet.mesh.position.x - enemy.mesh.position.x;
const dz = bullet.mesh.position.z - enemy.mesh.position.z;
const dy = bullet.mesh.position.y - (enemy.height || 1);
const dist = Math.sqrt(dx * dx + dz * dz + dy * dy);
if (dist < (enemy.size || 1)) {
// 命中
const damage = bullet.damage * (1 + player.damageBonus);
const hitPos = bullet.mesh.position.clone();
const hitDir = bullet.velocity.clone().normalize();
// 火箭弹:范围爆炸伤害
if (bullet.isRocket) {
this.createExplosion(hitPos, bullet.splashRadius, bullet.splashDamage, bullet.color);
} else {
// 普通命中
enemy.takeDamage(damage, hitDir);
// 等离子弹:灼烧效果
if (bullet.isPlasma) {
enemy.applyBurn(bullet.burnDamage, bullet.burnDuration);
}
// 命中特效
if (effects) {
effects.createHitEffect(hitPos, bullet.color);
}
}
// 移除子弹
renderer.scene.remove(bullet.mesh);
this.bullets.splice(i, 1);
break;
}
}
} else {
// 敌人子弹打玩家
const dx = bullet.mesh.position.x - player.mesh.position.x;
const dz = bullet.mesh.position.z - player.mesh.position.z;
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist < player.collisionRadius) {
player.takeDamage(bullet.damage);
renderer.scene.remove(bullet.mesh);
this.bullets.splice(i, 1);
}
}
}
}
createExplosion(position, radius, damage, color) {
// 爆炸特效
if (effects) {
effects.createShockwave(position, color, radius, 0.3);
effects.shake(0.3, 0.15);
}
if (particles) {
particles.createExplosion(position, color, 30);
}
// 范围伤害
for (const enemy of enemies.list) {
if (enemy.dead) continue;
const dx = position.x - enemy.mesh.position.x;
const dz = position.z - enemy.mesh.position.z;
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist < radius) {
// 距离衰减
const falloff = 1 - (dist / radius);
const actualDamage = damage * falloff * (1 + player.damageBonus);
const direction = new THREE.Vector3(-dx, 0, -dz).normalize();
enemy.takeDamage(actualDamage, direction);
}
}
// 也可能伤到玩家(自残)
if (player) {
const dx = position.x - player.mesh.position.x;
const dz = position.z - player.mesh.position.z;
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist < radius * 0.7) {
const falloff = 1 - (dist / (radius * 0.7));
player.takeDamage(damage * falloff * 0.3); // 自残伤害减半再减半
}
}
}
updateTurrets() {
if (this.turrets.length === 0) return;
const now = performance.now();
for (let i = this.turrets.length - 1; i >= 0; i--) {
const turret = this.turrets[i];
turret.lifetime -= this.deltaTime;
if (turret.lifetime <= 0) {
renderer.scene.remove(turret.mesh);
this.turrets.splice(i, 1);
continue;
}
// 寻找最近的敌人
let nearestEnemy = null;
let nearestDist = turret.range;
for (const enemy of enemies.list) {
if (enemy.dead) continue;
const dist = turret.mesh.position.distanceTo(enemy.mesh.position);
if (dist < nearestDist) {
nearestDist = dist;
nearestEnemy = enemy;
}
}
if (nearestEnemy && now - turret.lastFire > turret.fireRate) {
// 转向敌人
const direction = new THREE.Vector3();
direction.subVectors(nearestEnemy.mesh.position, turret.mesh.position);
direction.y = 0;
direction.normalize();
// 发射子弹
const bullet = {
mesh: new THREE.Mesh(
new THREE.SphereGeometry(0.08, 6, 6),
new THREE.MeshBasicMaterial({ color: 0xffaa00 })
),
velocity: direction.multiplyScalar(30),
damage: turret.damage,
life: 2,
color: 0xffaa00,
isPlayerBullet: true
};
bullet.mesh.position.copy(turret.mesh.position);
bullet.mesh.position.y = 0.5;
renderer.scene.add(bullet.mesh);
this.bullets.push(bullet);
turret.lastFire = now;
}
}
}
clearBullets() {
for (const bullet of this.bullets) {
if (bullet.mesh) {
renderer.scene.remove(bullet.mesh);
}
}
this.bullets = [];
}
clearParticles() {
if (particles && particles.clear) {
particles.clear();
}
}
startNextWave() {
// 从商店界面开始下一波
if (shop && shop.closeShop) {
shop.closeShop();
}
if (this.waveManager && this.waveManager.startWave) {
this.waveManager.startWave(this.waveManager.currentWave + 1);
}
// 移动端重新锁定指针
if (input && input.isMobile && input.isPointerLocked) {
// 移动端不需要指针锁定
}
}
stop() {
this.isRunning = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
}
const game = new Game();
// main.js
// Neon FPS - 主入口
// 所有模块按依赖顺序加载
// 确保Three.js已加载
if (typeof THREE === 'undefined') {
console.error('Three.js is not loaded!');
}
// 等待DOM加载完成
document.addEventListener('DOMContentLoaded', () => {
console.log('Neon FPS loading...');
// 加载字体(可选)
const fontLink = document.createElement('link');
fontLink.href = 'https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap';
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);
// 初始化游戏
setTimeout(() => {
game.init();
console.log('Neon FPS started!');
}, 100);
});
// 兼容性:如果是直接通过script标签加载,确保全局可用
window.NeonFPS = {
game: game,
player: player,
renderer: renderer,
input: input,
hud: hud,
ui: ui,
audio: audio,
effects: effects,
enemies: enemies,
weapons: weapons,
gameState: gameState,
CONFIG: CONFIG
};
</script>
</body>
</html>
Game Source: Neon FPS
Creator: RocketOwl77
Libraries: three
Complexity: complex (7796 lines, 244.0 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: neon-fps-rocketowl77" to link back to the original. Then publish at arcadelab.ai/publish.