/* DSES CZ原创水印 */
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>霓虹突围 - HTML5 枪战游戏</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
body {
margin: 0;
overflow: hidden;
background-color: #0f172a;
font-family: 'Orbitron', sans-serif;
user-select: none;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
box-shadow: 0 0 50px rgba(0, 255, 255, 0.1);
border: 2px solid #334155;
background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%);
cursor: crosshair;
}
.ui-layer {
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.hud-panel {
pointer-events: auto;
backdrop-filter: blur(4px);
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(56, 189, 248, 0.3);
box-shadow: 0 0 15px rgba(56, 189, 248, 0.1);
}
.health-bar-fill {
transition: width 0.2s ease-out, background-color 0.2s;
}
/* 动画效果 */
@keyframes pulse-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
.damage-effect {
animation: pulse-red 0.5s infinite;
}
.hidden { display: none !important; }
/* 准星样式 */
#crosshair {
position: fixed;
pointer-events: none;
z-index: 50;
transform: translate(-50%, -50%);
mix-blend-mode: difference;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas"></canvas>
<!-- 准星 -->
<div id="crosshair">
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="18" stroke="#0ea5e9" stroke-width="2" stroke-opacity="0.8"/>
<circle cx="20" cy="20" r="2" fill="#0ea5e9"/>
<line x1="20" y1="5" x2="20" y2="12" stroke="#0ea5e9" stroke-width="2"/>
<line x1="20" y1="28" x2="20" y2="35" stroke="#0ea5e9" stroke-width="2"/>
<line x1="5" y1="20" x2="12" y2="20" stroke="#0ea5e9" stroke-width="2"/>
<line x1="28" y1="20" x2="35" y2="20" stroke="#0ea5e9" stroke-width="2"/>
</svg>
</div>
<!-- UI 层 -->
<div class="ui-layer flex flex-col justify-between p-6">
<!-- 顶部 HUD -->
<div class="flex justify-between items-start w-full max-w-6xl mx-auto">
<div class="hud-panel rounded-lg p-4 flex flex-col gap-2 min-w-[200px]">
<div class="flex justify-between text-cyan-400 text-sm font-bold tracking-wider">
<span>HP</span>
<span id="hp-text">100/100</span>
</div>
<div class="w-full h-3 bg-slate-700 rounded-full overflow-hidden">
<div id="health-bar" class="health-bar-fill h-full bg-gradient-to-r from-cyan-500 to-blue-600 w-full"></div>
</div>
<div class="flex justify-between text-yellow-400 text-xs mt-1">
<span>SCORE</span>
<span id="score-text" class="font-mono text-lg">0</span>
</div>
</div>
<div class="hud-panel rounded-lg p-3 text-right">
<div class="text-slate-400 text-xs uppercase tracking-widest mb-1">Wave</div>
<div id="wave-text" class="text-2xl font-bold text-white">1</div>
</div>
</div>
<!-- 底部 HUD -->
<div class="flex justify-between items-end w-full max-w-6xl mx-auto">
<div class="hud-panel rounded-lg p-3 text-slate-300 text-xs">
<div class="flex items-center gap-2 mb-1"><i class="fas fa-keyboard text-cyan-500"></i> WASD 移动</div>
<div class="flex items-center gap-2"><i class="fas fa-mouse text-cyan-500"></i> 鼠标瞄准/射击</div>
<div class="flex items-center gap-2 mt-1"><i class="fas fa-r text-cyan-500"></i> R 换弹</div>
</div>
<div class="hud-panel rounded-lg p-4 flex flex-col items-end">
<div class="text-slate-400 text-xs uppercase tracking-widest mb-1">Ammo</div>
<div class="flex items-baseline gap-2">
<span id="ammo-current" class="text-4xl font-bold text-white font-mono">20</span>
<span class="text-slate-500 text-xl">/</span>
<span id="ammo-reserve" class="text-xl text-slate-400 font-mono">∞</span>
</div>
<div id="reload-indicator" class="hidden text-yellow-400 text-xs font-bold animate-pulse mt-1">RELOADING...</div>
</div>
</div>
</div>
<!-- 开始/结束 菜单 -->
<div id="menu-overlay" class="absolute inset-0 bg-slate-900/90 backdrop-blur-sm flex items-center justify-center z-50">
<div class="bg-slate-800 border border-cyan-500/30 p-8 rounded-2xl shadow-2xl max-w-md w-full text-center relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-cyan-500 to-transparent"></div>
<h1 class="text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-blue-600 mb-2 tracking-tighter">NEON STRIKE</h1>
<p class="text-slate-400 mb-8 text-sm">生存下去,消灭所有威胁</p>
<div id="start-screen">
<button id="start-btn" class="w-full py-4 bg-cyan-600 hover:bg-cyan-500 text-white font-bold rounded-lg transition-all transform hover:scale-105 shadow-lg shadow-cyan-500/20 mb-4 group">
<i class="fas fa-play mr-2 group-hover:animate-pulse"></i> 开始游戏
</button>
<div class="text-xs text-slate-500 mt-4">最高分: <span id="high-score-display" class="text-cyan-400">0</span></div>
</div>
<div id="game-over-screen" class="hidden">
<div class="text-red-500 text-6xl mb-2"><i class="fas fa-skull"></i></div>
<h2 class="text-3xl text-white font-bold mb-2">任务失败</h2>
<p class="text-slate-300 mb-6">最终得分: <span id="final-score" class="text-yellow-400 font-mono text-xl">0</span></p>
<button id="restart-btn" class="w-full py-4 bg-slate-700 hover:bg-slate-600 text-white font-bold rounded-lg transition-all border border-slate-600 hover:border-cyan-500">
<i class="fas fa-redo mr-2"></i> 再次挑战
</button>
</div>
</div>
</div>
</div>
<script>
/**
* 游戏核心逻辑
* 采用 Canvas API 进行渲染,RequestAnimationFrame 驱动循环
*/
// --- 配置与常量 ---
const CONFIG = {
PLAYER_SPEED: 4,
PLAYER_HP: 100,
BULLET_SPEED: 12,
ENEMY_BASE_SPEED: 1.5,
FIRE_RATE: 150, // ms
RELOAD_TIME: 1500, // ms
MAG_SIZE: 20,
COLORS: {
PLAYER: '#0ea5e9', // Sky 500
ENEMY: '#ef4444', // Red 500
BULLET: '#facc15', // Yellow 400
PARTICLE: ['#facc15', '#f87171', '#ffffff']
}
};
// --- 游戏状态管理 ---
const state = {
isPlaying: false,
score: 0,
wave: 1,
lastTime: 0,
keys: {},
mouse: { x: 0, y: 0, down: false },
camera: { x: 0, y: 0 },
entities: {
player: null,
bullets: [],
enemies: [],
particles: [],
texts: []
},
stats: {
hp: CONFIG.PLAYER_HP,
ammo: CONFIG.MAG_SIZE,
isReloading: false,
lastShot: 0
}
};
// --- DOM 元素 ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const ui = {
hpBar: document.getElementById('health-bar'),
hpText: document.getElementById('hp-text'),
score: document.getElementById('score-text'),
wave: document.getElementById('wave-text'),
ammoCur: document.getElementById('ammo-current'),
ammoRes: document.getElementById('ammo-reserve'),
reloadInd: document.getElementById('reload-indicator'),
menu: document.getElementById('menu-overlay'),
startScreen: document.getElementById('start-screen'),
gameOverScreen: document.getElementById('game-over-screen'),
finalScore: document.getElementById('final-score'),
highScore: document.getElementById('high-score-display'),
crosshair: document.getElementById('crosshair')
};
// --- 初始化 Canvas ---
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// --- 输入处理 ---
window.addEventListener('keydown', e => {
state.keys[e.code] = true;
if (e.code === 'KeyR' && state.isPlaying) reloadWeapon();
});
window.addEventListener('keyup', e => state.keys[e.code] = false);
window.addEventListener('mousemove', e => {
state.mouse.x = e.clientX;
state.mouse.y = e.clientY;
// 更新自定义准星位置
ui.crosshair.style.left = e.clientX + 'px';
ui.crosshair.style.top = e.clientY + 'px';
});
window.addEventListener('mousedown', () => state.mouse.down = true);
window.addEventListener('mouseup', () => state.mouse.down = false);
// --- 类定义 ---
class Player {
constructor() {
this.x = canvas.width / 2;
this.y = canvas.height / 2;
this.radius = 15;
this.angle = 0;
}
update(dt) {
// 移动
let dx = 0, dy = 0;
if (state.keys['KeyW'] || state.keys['ArrowUp']) dy -= 1;
if (state.keys['KeyS'] || state.keys['ArrowDown']) dy += 1;
if (state.keys['KeyA'] || state.keys['ArrowLeft']) dx -= 1;
if (state.keys['KeyD'] || state.keys['ArrowRight']) dx += 1;
// 归一化向量
if (dx !== 0 || dy !== 0) {
const len = Math.sqrt(dx*dx + dy*dy);
dx /= len;
dy /= len;
this.x += dx * CONFIG.PLAYER_SPEED;
this.y += dy * CONFIG.PLAYER_SPEED;
}
// 边界限制
this.x = Math.max(this.radius, Math.min(canvas.width - this.radius, this.x));
this.y = Math.max(this.radius, Math.min(canvas.height - this.radius, this.y));
// 计算朝向角度
this.angle = Math.atan2(state.mouse.y - this.y, state.mouse.x - this.x);
// 射击逻辑
if (state.mouse.down && !state.stats.isReloading && state.stats.ammo > 0) {
const now = Date.now();
if (now - state.stats.lastShot > CONFIG.FIRE_RATE) {
shoot();
state.stats.lastShot = now;
}
} else if (state.mouse.down && state.stats.ammo <= 0 && !state.stats.isReloading) {
reloadWeapon();
}
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
// 身体
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.fillStyle = CONFIG.COLORS.PLAYER;
ctx.fill();
ctx.shadowBlur = 15;
ctx.shadowColor = CONFIG.COLORS.PLAYER;
ctx.fill();
ctx.shadowBlur = 0;
// 枪管
ctx.fillStyle = '#334155';
ctx.fillRect(0, -5, 25, 10);
ctx.restore();
}
}
class Bullet {
constructor(x, y, angle) {
this.x = x;
this.y = y;
this.vx = Math.cos(angle) * CONFIG.BULLET_SPEED;
this.vy = Math.sin(angle) * CONFIG.BULLET_SPEED;
this.radius = 3;
this.active = true;
}
update() {
this.x += this.vx;
this.y += this.vy;
// 出界移除
if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height) {
this.active = false;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = CONFIG.COLORS.BULLET;
ctx.fill();
// 拖尾效果
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - this.vx * 2, this.y - this.vy * 2);
ctx.strokeStyle = `rgba(250, 204, 21, 0.3)`;
ctx.lineWidth = 2;
ctx.stroke();
}
}
class Enemy {
constructor(wave) {
// 随机生成在屏幕边缘外
const side = Math.floor(Math.random() * 4);
if (side === 0) { this.x = Math.random() * canvas.width; this.y = -30; } // Top
else if (side === 1) { this.x = canvas.width + 30; this.y = Math.random() * canvas.height; } // Right
else if (side === 2) { this.x = Math.random() * canvas.width; this.y = canvas.height + 30; } // Bottom
else { this.x = -30; this.y = Math.random() * canvas.height; } // Left
this.radius = 12 + Math.random() * 8;
this.speed = CONFIG.ENEMY_BASE_SPEED + (wave * 0.1) + Math.random();
this.hp = 1 + Math.floor(wave / 3);
this.active = true;
this.color = CONFIG.COLORS.ENEMY;
}
update(player) {
const angle = Math.atan2(player.y - this.y, player.x - this.x);
this.x += Math.cos(angle) * this.speed;
this.y += Math.sin(angle) * this.speed;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
// 简单的眼睛指示方向
/*
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius/2, 0, Math.PI*2);
ctx.fill();
*/
}
}
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 3 + 1;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = 1.0;
this.decay = Math.random() * 0.03 + 0.02;
this.color = color;
this.size = Math.random() * 3 + 1;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= this.decay;
}
draw() {
ctx.globalAlpha = this.life;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1.0;
}
}
class FloatingText {
constructor(x, y, text, color) {
this.x = x;
this.y = y;
this.text = text;
this.color = color;
this.life = 1.0;
this.vy = -1;
}
update() {
this.y += this.vy;
this.life -= 0.02;
}
draw() {
ctx.globalAlpha = this.life;
ctx.fillStyle = this.color;
ctx.font = "bold 16px Orbitron";
ctx.fillText(this.text, this.x, this.y);
ctx.globalAlpha = 1.0;
}
}
// --- 游戏功能函数 ---
function initGame() {
state.entities.player = new Player();
state.entities.bullets = [];
state.entities.enemies = [];
state.entities.particles = [];
state.entities.texts = [];
state.score = 0;
state.wave = 1;
state.stats.hp = CONFIG.PLAYER_HP;
state.stats.ammo = CONFIG.MAG_SIZE;
state.stats.isReloading = false;
state.isPlaying = true;
updateUI();
ui.menu.classList.add('hidden');
ui.startScreen.classList.add('hidden');
ui.gameOverScreen.classList.add('hidden');
// 启动敌人生成器
spawnEnemyLoop();
requestAnimationFrame(gameLoop);
}
function gameOver() {
state.isPlaying = false;
ui.menu.classList.remove('hidden');
ui.gameOverScreen.classList.remove('hidden');
ui.finalScore.innerText = state.score;
// 更新最高分
const highScore = localStorage.getItem('neonStrikeHighScore') || 0;
if (state.score > highScore) {
localStorage.setItem('neonStrikeHighScore', state.score);
ui.highScore.innerText = state.score;
}
}
function shoot() {
if (state.stats.ammo > 0) {
state.stats.ammo--;
// 枪口位置
const p = state.entities.player;
const muzzleDist = 25;
const bx = p.x + Math.cos(p.angle) * muzzleDist;
const by = p.y + Math.sin(p.angle) * muzzleDist;
// 添加后坐力抖动
state.camera.x = (Math.random() - 0.5) * 4;
state.camera.y = (Math.random() - 0.5) * 4;
setTimeout(() => { state.camera.x = 0; state.camera.y = 0; }, 50);
state.entities.bullets.push(new Bullet(bx, by, p.angle + (Math.random()-0.5)*0.1)); // 轻微散射
updateUI();
}
}
function reloadWeapon() {
if (state.stats.isReloading || state.stats.ammo === CONFIG.MAG_SIZE) return;
state.stats.isReloading = true;
ui.reloadInd.classList.remove('hidden');
setTimeout(() => {
state.stats.ammo = CONFIG.MAG_SIZE;
state.stats.isReloading = false;
ui.reloadInd.classList.add('hidden');
updateUI();
}, CONFIG.RELOAD_TIME);
}
let enemySpawnTimer = null;
function spawnEnemyLoop() {
if (!state.isPlaying) return;
// 随着波次增加,生成速度加快
const spawnRate = Math.max(500, 2000 - (state.wave * 100));
state.entities.enemies.push(new Enemy(state.wave));
enemySpawnTimer = setTimeout(spawnEnemyLoop, spawnRate);
}
function createExplosion(x, y, count = 8) {
for(let i=0; i<count; i++) {
state.entities.particles.push(new Particle(x, y, CONFIG.COLORS.PARTICLE[Math.floor(Math.random()*CONFIG.COLORS.PARTICLE.length)]));
}
}
function checkCollisions() {
const player = state.entities.player;
const bullets = state.entities.bullets;
const enemies = state.entities.enemies;
// 子弹击中敌人
for (let i = bullets.length - 1; i >= 0; i--) {
const b = bullets[i];
let hit = false;
for (let j = enemies.length - 1; j >= 0; j--) {
const e = enemies[j];
const dist = Math.hypot(b.x - e.x, b.y - e.y);
if (dist < e.radius + b.radius) {
// 击中
e.hp--;
hit = true;
createExplosion(b.x, b.y, 3);
if (e.hp <= 0) {
e.active = false;
state.score += 10 * state.wave;
createExplosion(e.x, e.y, 10);
state.entities.texts.push(new FloatingText(e.x, e.y, "+" + (10*state.wave), "#facc15"));
// 波次逻辑:每100分升一波
if (state.score > state.wave * 100) {
state.wave++;
state.entities.texts.push(new FloatingText(canvas.width/2, canvas.height/2 - 50, "WAVE " + state.wave, "#0ea5e9"));
// 回血奖励
state.stats.hp = Math.min(CONFIG.PLAYER_HP, state.stats.hp + 10);
}
}
break;
}
}
if (hit) b.active = false;
}
// 敌人接触玩家
for (let e of enemies) {
const dist = Math.hypot(player.x - e.x, player.y - e.y);
if (dist < player.radius + e.radius) {
state.stats.hp -= 1; // 持续伤害
createExplosion((player.x+e.x)/2, (player.y+e.y)/2, 1);
// 屏幕红闪
document.body.classList.add('damage-effect');
setTimeout(() => document.body.classList.remove('damage-effect'), 200);
if (state.stats.hp <= 0) {
gameOver();
}
}
}
}
function updateUI() {
ui.hpText.innerText = `${Math.ceil(state.stats.hp)}/${CONFIG.PLAYER_HP}`;
ui.hpBar.style.width = `${(state.stats.hp / CONFIG.PLAYER_HP) * 100}%`;
// 血量低时变红
if (state.stats.hp < 30) {
ui.hpBar.className = "health-bar-fill h-full bg-red-600 w-full";
} else {
ui.hpBar.className = "health-bar-fill h-full bg-gradient-to-r from-cyan-500 to-blue-600 w-full";
}
ui.score.innerText = state.score;
ui.wave.innerText = state.wave;
ui.ammoCur.innerText = state.stats.ammo;
if (state.stats.ammo === 0) {
ui.ammoCur.classList.add('text-red-500');
} else {
ui.ammoCur.classList.remove('text-red-500');
}
}
// --- 主循环 ---
function gameLoop(timestamp) {
if (!state.isPlaying) return;
// 清空画布
ctx.fillStyle = '#0f172a'; // 背景色覆盖,防止拖影
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 应用相机抖动
ctx.save();
ctx.translate(state.camera.x, state.camera.y);
// 绘制网格背景 (装饰)
ctx.strokeStyle = '#1e293b';
ctx.lineWidth = 1;
ctx.beginPath();
for(let x=0; x<canvas.width; x+=50) { ctx.moveTo(x,0); ctx.lineTo(x,canvas.height); }
for(let y=0; y<canvas.height; y+=50) { ctx.moveTo(0,y); ctx.lineTo(canvas.width,y); }
ctx.stroke();
// 更新实体
state.entities.player.update();
state.entities.bullets.forEach(b => b.update());
state.entities.bullets = state.entities.bullets.filter(b => b.active);
state.entities.enemies.forEach(e => e.update(state.entities.player));
state.entities.enemies = state.entities.enemies.filter(e => e.active);
state.entities.particles.forEach(p => p.update());
state.entities.particles = state.entities.particles.filter(p => p.life > 0);
state.entities.texts.forEach(t => t.update());
state.entities.texts = state.entities.texts.filter(t => t.life > 0);
checkCollisions();
updateUI();
// 绘制实体
state.entities.bullets.forEach(b => b.draw());
state.entities.enemies.forEach(e => e.draw());
state.entities.player.draw();
state.entities.particles.forEach(p => p.draw());
state.entities.texts.forEach(t => t.draw());
ctx.restore();
requestAnimationFrame(gameLoop);
}
// --- 事件绑定 ---
document.getElementById('start-btn').addEventListener('click', initGame);
document.getElementById('restart-btn').addEventListener('click', initGame);
// 加载最高分
const savedHigh = localStorage.getItem('neonStrikeHighScore');
if(savedHigh) ui.highScore.innerText = savedHigh;
</script>
</body>
</html>