Files
micro_servers/static/public/games/mobile5.html
T
2026-07-23 11:30:26 +08:00

459 lines
13 KiB
HTML

<!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, viewport-fit=cover, shrink-to-fit=yes">
<title>像素守卫 · 射击游戏</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
body {
background: linear-gradient(180deg, #1a3a1a 0%, #0d260d 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', system-ui, sans-serif;
padding: 10px;
margin: 0;
}
.game-container {
width: 100%;
max-width: 400px;
background: radial-gradient(circle at 50% 30%, #3a6b3a, #1a331a);
border-radius: 48px;
box-shadow: 0 25px 50px rgba(0,0,0,0.7), inset 0 0 25px rgba(150,255,150,0.15);
padding: 16px 12px 20px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid #5a8a5a;
}
canvas {
display: block;
width: 100%;
height: auto;
border-radius: 32px;
background: radial-gradient(ellipse at 50% 20%, #4a7a4a 0%, #1a2e1a 85%);
box-shadow: inset 0 -8px 25px rgba(0,0,0,0.8), 0 12px 28px black;
touch-action: none;
}
.info-panel {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 14px;
padding: 0 10px;
color: #c0ffc0;
font-weight: bold;
text-shadow: 0 2px 8px black;
font-size: 1.3rem;
}
.score-board {
background: rgba(0,20,0,0.8);
backdrop-filter: blur(12px);
border-radius: 50px;
padding: 8px 22px;
letter-spacing: 1px;
box-shadow: 0 8px 18px rgba(0,0,0,0.6);
border: 1px solid #6aaa6a;
}
.restart-btn {
background: #4caf50;
border: none;
color: white;
font-weight: bold;
font-size: 1.2rem;
padding: 10px 25px;
border-radius: 40px;
background: linear-gradient(180deg, #6fbf6f 0%, #2e7d32 100%);
box-shadow: 0 8px 0 #1b5e20, 0 12px 22px rgba(0,0,0,0.5);
transition: all 0.08s ease;
text-transform: uppercase;
letter-spacing: 1px;
cursor: pointer;
border: 1px solid #a5d6a7;
touch-action: manipulation;
}
.restart-btn:active {
transform: translateY(4px);
box-shadow: 0 4px 0 #1b5e20, 0 8px 14px rgba(0,0,0,0.6);
background: linear-gradient(180deg, #5aa55a 0%, #1f5a22 100%);
}
.hint {
color: #a0d0a0;
font-size: 0.8rem;
margin-top: 6px;
text-align: center;
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="gameCanvas" width="360" height="600" aria-label="像素守卫射击游戏"></canvas>
<div class="info-panel">
<div class="score-board" id="scoreDisplay">🗡️ 0</div>
<button class="restart-btn" id="restartButton">🏰 守城</button>
</div>
<div class="hint">👈 滑动瞄准 | 点击射击敌人</div>
</div>
<script>
(function() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreSpan = document.getElementById('scoreDisplay');
const CW = 360;
const CH = 600;
canvas.width = CW;
canvas.height = CH;
// ----- 炮台设置 -----
const TOWER_X = CW / 2;
const TOWER_Y = CH - 55;
let aimAngle = -Math.PI / 2; // 默认向上
// 游戏状态
let gameActive = true;
let score = 0;
let health = 100;
const MAX_HEALTH = 100;
// 敌人数组
let enemies = [];
// 子弹数组
let bullets = [];
// 粒子效果
let particles = [];
// 生成控制
const ENEMY_SPAWN_INTERVAL = 40;
let frameCounter = 0;
let difficultyTimer = 0;
// ----- 辅助函数 -----
function spawnEnemy() {
const side = Math.random() < 0.5 ? 'left' : 'right';
const x = side === 'left' ? -20 : CW + 20;
const y = 40 + Math.random() * 250;
const speedX = side === 'left' ? 1.2 + Math.random() * 1.5 : -(1.2 + Math.random() * 1.5);
const speedY = (Math.random() - 0.5) * 1.2;
enemies.push({
x: x,
y: y,
vx: speedX,
vy: speedY,
radius: 14,
health: 1,
color: '#ff5555',
});
}
function restartGame() {
gameActive = true;
score = 0;
health = MAX_HEALTH;
enemies = [];
bullets = [];
particles = [];
frameCounter = 0;
difficultyTimer = 0;
aimAngle = -Math.PI / 2;
updateScoreDisplay();
}
function endGame() {
if (!gameActive) return;
gameActive = false;
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate(60);
}
}
function updateScoreDisplay() {
scoreSpan.textContent = `🗡️ ${score} ❤️ ${health}`;
}
function damagePlayer(amount) {
health -= amount;
if (health <= 0) {
health = 0;
endGame();
}
updateScoreDisplay();
}
// 发射子弹
function shootBullet() {
if (!gameActive) return;
const bulletSpeed = 8;
const vx = Math.cos(aimAngle) * bulletSpeed;
const vy = Math.sin(aimAngle) * bulletSpeed;
bullets.push({
x: TOWER_X,
y: TOWER_Y,
vx: vx,
vy: vy,
radius: 5,
life: 80,
});
}
// ----- 更新逻辑 -----
function update() {
if (!gameActive) return;
// 更新子弹
for (let i = bullets.length - 1; i >= 0; i--) {
const b = bullets[i];
b.x += b.vx;
b.y += b.vy;
b.life--;
if (b.life <= 0 || b.x < -30 || b.x > CW + 30 || b.y < -30 || b.y > CH + 30) {
bullets.splice(i, 1);
continue;
}
// 子弹与敌人碰撞
let bulletRemoved = false;
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
const dist = Math.hypot(b.x - enemy.x, b.y - enemy.y);
if (dist < enemy.radius + b.radius) {
// 消灭敌人
enemies.splice(j, 1);
bulletRemoved = true;
score += 20;
updateScoreDisplay();
spawnParticles(enemy.x, enemy.y, '#ffaa00', 8);
break;
}
}
if (bulletRemoved) {
bullets.splice(i, 1);
}
}
// 更新敌人
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
enemy.x += enemy.vx;
enemy.y += enemy.vy;
// 敌人与炮台碰撞
const distToTower = Math.hypot(enemy.x - TOWER_X, enemy.y - TOWER_Y);
if (distToTower < 35) {
enemies.splice(i, 1);
damagePlayer(12);
spawnParticles(TOWER_X, TOWER_Y, '#ff3333', 12);
continue;
}
// 移除离开屏幕的敌人
if (enemy.x < -60 || enemy.x > CW + 60 || enemy.y < -60 || enemy.y > CH + 60) {
enemies.splice(i, 1);
}
}
// 更新粒子
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= 0.03;
if (p.life <= 0) particles.splice(i, 1);
}
// 生成敌人 (随难度增加)
if (gameActive) {
frameCounter++;
difficultyTimer++;
const adjustedInterval = Math.max(18, ENEMY_SPAWN_INTERVAL - Math.floor(difficultyTimer / 300));
if (frameCounter >= adjustedInterval) {
spawnEnemy();
frameCounter = 0;
if (Math.random() < 0.3) spawnEnemy(); // 偶尔双倍
}
}
}
function spawnParticles(x, y, color, count) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 5;
particles.push({
x: x, y: y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1.0,
color: color,
size: 3 + Math.random() * 4,
});
}
}
// ----- 绘制图形 -----
function draw() {
ctx.clearRect(0, 0, CW, CH);
// 绘制地面网格
ctx.strokeStyle = '#2a4a2a';
ctx.lineWidth = 0.8;
for (let i = 0; i < CW; i += 30) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, CH);
ctx.stroke();
}
// 绘制敌人
for (let enemy of enemies) {
ctx.save();
ctx.shadowColor = '#ff0000aa';
ctx.shadowBlur = 14;
ctx.fillStyle = enemy.color;
ctx.beginPath();
ctx.arc(enemy.x, enemy.y, enemy.radius, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(enemy.x-3, enemy.y-4, 4, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
// 绘制子弹
for (let b of bullets) {
ctx.fillStyle = '#ffff66';
ctx.shadowColor = '#ffaa00';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI*2);
ctx.fill();
}
// 绘制粒子
for (let p of particles) {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
// 绘制炮台
ctx.save();
ctx.translate(TOWER_X, TOWER_Y);
// 基座
ctx.fillStyle = '#5a5a7a';
ctx.shadowColor = '#000000';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(0, 0, 18, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#7a7aaa';
ctx.beginPath();
ctx.arc(0, 0, 12, 0, Math.PI*2);
ctx.fill();
// 炮管
ctx.rotate(aimAngle);
ctx.fillStyle = '#ccccdd';
ctx.fillRect(12, -5, 24, 10);
ctx.fillStyle = '#aaaacc';
ctx.fillRect(30, -3, 10, 6);
ctx.restore();
// 生命条
ctx.fillStyle = '#331111';
ctx.fillRect(15, CH-18, CW-30, 14);
const healthPercent = health / MAX_HEALTH;
const healthColor = healthPercent > 0.5 ? '#44cc44' : '#ff6644';
ctx.fillStyle = healthColor;
ctx.fillRect(15, CH-18, (CW-30) * healthPercent, 14);
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.strokeRect(15, CH-18, CW-30, 14);
// 游戏结束
if (!gameActive) {
ctx.globalAlpha = 0.8;
ctx.fillStyle = '#0a0f0a';
ctx.fillRect(0, 0, CW, CH);
ctx.globalAlpha = 1;
ctx.fillStyle = '#ffcccc';
ctx.font = 'bold 28px "Segoe UI"';
ctx.textAlign = 'center';
ctx.fillText('💀 失守', CW/2, CH/2-20);
}
}
// ----- 游戏循环 -----
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// ----- 事件处理 -----
function handleAim(e) {
e.preventDefault();
if (!gameActive) return;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
const rect = canvas.getBoundingClientRect();
const scaleX = CW / rect.width;
const scaleY = CH / rect.height;
const canvasX = (clientX - rect.left) * scaleX;
const canvasY = (clientY - rect.top) * scaleY;
aimAngle = Math.atan2(canvasY - TOWER_Y, canvasX - TOWER_X);
}
function handleShoot(e) {
e.preventDefault();
if (!gameActive) return;
shootBullet();
}
function bindEvents() {
canvas.addEventListener('touchmove', handleAim, { passive: false });
canvas.addEventListener('touchstart', (e) => {
handleAim(e);
handleShoot(e);
});
canvas.addEventListener('mousemove', (e) => {
if (e.buttons === 1) handleAim(e);
});
canvas.addEventListener('mousedown', (e) => {
handleAim(e);
handleShoot(e);
});
document.getElementById('restartButton').addEventListener('click', restartGame);
document.getElementById('restartButton').addEventListener('touchstart', (e) => {
e.preventDefault();
restartGame();
});
}
bindEvents();
restartGame();
gameLoop();
})();
</script>
</body>
</html>