轨道防御:祖冲之号
by TurboKoala83655 lines23.4 KB
<!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>轨道防御:祖冲之号</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #050505;
font-family: 'Courier New', monospace;
color: #e2e8f0;
touch-action: none;
user-select: none;
-webkit-user-select: none;
}
canvas { display: block; width: 100vw; height: 100vh; }
#ui-layer {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 20px;
box-sizing: border-box;
}
/* 顶部状态栏 */
.hud-top {
display: flex;
flex-direction: column;
gap: 8px;
background: rgba(15, 23, 42, 0.7);
padding: 12px;
border-radius: 8px;
border: 1px solid #1e3a5f;
backdrop-filter: blur(4px);
width: 200px;
}
.bar-container { width: 100%; }
.bar-label { font-size: 11px; color: #22d3ee; margin-bottom: 2px; display: flex; justify-content: space-between; }
.bar-bg { width: 100%; height: 8px; background: #1e293b; border-radius: 4px; overflow: hidden; }
.bar-fill { height: 100%; transition: width 0.1s; }
#hp-fill { background: #3b82f6; width: 100%; }
#heat-fill { background: #ef4444; width: 0%; }
#heat-fill.cooling { background: #10b981; }
/* 底部控制区 */
.hud-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
pointer-events: auto;
}
#btn-fire {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(34, 211, 238, 0.2);
border: 2px solid #22d3ee;
color: #22d3ee;
font-size: 14px;
font-weight: bold;
pointer-events: auto;
touch-action: none;
box-shadow: 0 0 20px rgba(34, 211, 238, 0.3);
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
#btn-fire:active, #btn-fire.active {
background: rgba(34, 211, 238, 0.6);
color: #fff;
box-shadow: 0 0 30px rgba(34, 211, 238, 0.8);
}
#game-over {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.9);
border: 2px solid #ef4444;
padding: 30px;
border-radius: 10px;
text-align: center;
display: none;
flex-direction: column;
gap: 15px;
pointer-events: auto;
}
#game-over h1 { color: #ef4444; margin: 0; font-size: 24px; }
#restart-btn {
background: #22d3ee; color: #000; border: none; padding: 10px 20px;
font-family: inherit; font-weight: bold; cursor: pointer; border-radius: 5px;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-layer">
<!-- 顶部 HUD -->
<div class="hud-top">
<div class="bar-container">
<div class="bar-label"><span>地球护盾</span><span id="hp-text">100%</span></div>
<div class="bar-bg"><div class="bar-fill" id="hp-fill"></div></div>
</div>
<div class="bar-container">
<div class="bar-label"><span>舰体热量</span><span id="heat-text">0%</span></div>
<div class="bar-bg"><div class="bar-fill" id="heat-fill"></div></div>
</div>
<div style="font-size: 12px; color: #94a3b8;">战绩: <span id="score">0</span></div>
</div>
<!-- 底部控制 -->
<div class="hud-bottom">
<div></div> <!-- 占位符,把按钮挤到右边 -->
<div id="btn-fire">主炮<br>开火</div>
</div>
</div>
<div id="game-over">
<h1>地球失守</h1>
<p>祖冲之号已被摧毁</p>
<button id="restart-btn">重新部署</button>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let width, height;
let gameRunning = true;
let score = 0;
// --- 实体数据 ---
const player = {
x: 0, y: 0,
radius: 12, // 视觉大小
hp: 100,
heat: 0,
maxHeat: 100,
isOverheated: false,
angle: 0,
targetAngle: 0,
// 武器状态
cannonCooldown: 0,
interceptorCooldown: 0
};
let enemies = [];
let bullets = [];
let particles = [];
// --- 触摸/输入状态 ---
let isDragging = false;
let touchTarget = { x: 0, y: 0 };
let isFiring = false;
// 初始化
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
// 玩家初始位置:地球上方
player.x = width / 2;
player.y = height - 150;
touchTarget.x = player.x;
touchTarget.y = player.y;
}
window.addEventListener('resize', resize);
resize();
// --- 辅助绘图 ---
function drawCircle(x, y, r, color) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
// 产生爆炸粒子
function spawnExplosion(x, y, color) {
for (let i = 0; i < 8; i++) {
let angle = Math.random() * Math.PI * 2;
let speed = Math.random() * 3 + 1;
particles.push({
x: x, y: y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1.0,
color: color
});
}
}
// --- 核心绘制:祖冲之号 ---
function drawPlayer() {
ctx.save();
ctx.translate(player.x, player.y);
ctx.rotate(player.angle);
// 主推进器尾焰
if (isDragging) {
ctx.fillStyle = 'rgba(251, 146, 60, 0.6)';
ctx.beginPath();
ctx.moveTo(-8, 15); ctx.lineTo(0, 25); ctx.lineTo(8, 15);
ctx.fill();
}
// 两侧散热巨翼 (深色)
ctx.fillStyle = '#1E293B';
ctx.strokeStyle = '#64748B';
ctx.lineWidth = 1;
ctx.fillRect(-30, -15, 20, 30);
ctx.strokeRect(-30, -15, 20, 30);
ctx.fillRect(10, -15, 20, 30);
ctx.strokeRect(10, -15, 20, 30);
// 翼上蓝色散热管
ctx.fillStyle = '#38BDF8';
ctx.fillRect(-26, -10, 12, 3);
ctx.fillRect(-26, 0, 12, 3);
ctx.fillRect(14, -10, 12, 3);
ctx.fillRect(14, 0, 12, 3);
// 中央脊线 (白色)
ctx.fillStyle = '#F3F4F6';
ctx.strokeStyle = '#9CA3AF';
ctx.fillRect(-8, -20, 16, 40);
ctx.strokeRect(-8, -20, 16, 40);
// 主炮口 (青色)
ctx.fillStyle = '#22D3EE';
ctx.fillRect(-3, -25, 6, 10);
// 六门拦截炮 (绿色小圆)
ctx.fillStyle = '#A3E635';
const cannons = [[-20, -12], [20, -12], [-20, 12], [20, 12], [0, -15], [0, 15]];
cannons.forEach(c => {
ctx.beginPath();
ctx.arc(c[0], c[1], 2.5, 0, Math.PI * 2);
ctx.fill();
});
ctx.restore();
}
// --- 游戏主循环 ---
function update() {
if (!gameRunning) return;
// 1. 玩家移动逻辑 (平滑跟随手指)
if (isDragging) {
let dx = touchTarget.x - player.x;
let dy = touchTarget.y - player.y;
let dist = Math.hypot(dx, dy);
if (dist > 5) {
player.x += (dx / dist) * 6;
player.y += (dy / dist) * 6;
}
}
// 2. 热量与武器逻辑
let heatChange = 0;
if (isFiring && !player.isOverheated) {
// 主炮开火:巨量发热
heatChange += 2.5;
// 发射主炮激光(视觉上用一条长长的射线表示)
if (player.cannonCooldown <= 0) {
player.cannonCooldown = 10; // 每10帧发射一次伤害判定
// 假设主炮打向准心方向(当前朝向)
let bulletX = player.x + Math.cos(player.angle) * 30;
let bulletY = player.y + Math.sin(player.angle) * 30;
bullets.push({
x: bulletX, y: bulletY,
vx: Math.cos(player.angle) * 15,
vy: Math.sin(player.angle) * 15,
damage: 15,
isPlayer: true,
color: '#22D3EE',
life: 1 // 存活帧数
});
}
} else {
// 不开火时,散热
heatChange -= 1.5;
}
// 拦截炮自动开火(温和发热)
if (player.interceptorCooldown <= 0 && enemies.length > 0) {
player.interceptorCooldown = 20;
heatChange += 0.5; // 拦截炮也会慢慢积热
// 寻找最近的敌人
let nearestEnemy = null;
let minDist = Infinity;
enemies.forEach(e => {
let d = Math.hypot(e.x - player.x, e.y - player.y);
if (d < minDist) { minDist = d; nearestEnemy = e; }
});
if (nearestEnemy && minDist < 400) { // 拦截范围
let angle = Math.atan2(nearestEnemy.y - player.y, nearestEnemy.x - player.x);
bullets.push({
x: player.x + Math.cos(angle) * 20,
y: player.y + Math.sin(angle) * 20,
vx: Math.cos(angle) * 12,
vy: Math.sin(angle) * 12,
damage: 5, // 伤害较小
isPlayer: true,
color: '#A3E635',
life: 1
});
}
}
// 更新冷却
player.cannonCooldown--;
player.interceptorCooldown--;
// 热量结算
player.heat += heatChange;
if (player.heat >= player.maxHeat) {
player.heat = player.maxHeat;
player.isOverheated = true;
} else if (player.heat <= 0) {
player.heat = 0;
}
// 过热恢复
if (player.isOverheated && player.heat < 30) {
player.isOverheated = false;
}
// 3. 自动瞄准转向
let nearest = null;
let nearestDist = Infinity;
enemies.forEach(e => {
let d = Math.hypot(e.x - player.x, e.y - player.y);
if (d < nearestDist) { nearestDist = d; nearest = e; }
});
if (nearest) {
player.targetAngle = Math.atan2(nearest.y - player.y, nearest.x - player.x);
// 平滑旋转
let diff = player.targetAngle - player.angle;
while (diff < -Math.PI) diff += Math.PI * 2;
while (diff > Math.PI) diff -= Math.PI * 2;
player.angle += diff * 0.15;
}
// 4. 生成敌人 (攻击星)
if (Math.random() < 0.02) { // 生成速率
let spawnSide = Math.floor(Math.random() * 3);
let ex, ey;
if (spawnSide === 0) { ex = Math.random() * width; ey = -50; }
else if (spawnSide === 1) { ex = -50; ey = Math.random() * height; }
else { ex = width + 50; ey = Math.random() * height; }
enemies.push({
x: ex, y: ey,
radius: 8,
hp: 20,
speed: 0.8,
angle: 0,
color: '#EF4444', // 红色攻击星
fireCooldown: Math.random() * 60 + 60
});
}
// 5. 更新子弹
for (let i = bullets.length - 1; i >= 0; i--) {
let b = bullets[i];
b.x += b.vx;
b.y += b.vy;
// 超出屏幕删除
if (b.x < -100 || b.x > width + 100 || b.y < -100 || b.y > height + 100) {
bullets.splice(i, 1);
continue;
}
// 碰撞检测 (玩家子弹 vs 敌人)
if (b.isPlayer) {
for (let j = enemies.length - 1; j >= 0; j--) {
let e = enemies[j];
if (Math.hypot(b.x - e.x, b.y - e.y) < e.radius + 3) {
e.hp -= b.damage;
spawnExplosion(b.x, b.y, '#22D3EE');
bullets.splice(i, 1);
if (e.hp <= 0) {
enemies.splice(j, 1);
score += 10;
}
break;
}
}
}
}
// 6. 更新敌人
for (let i = enemies.length - 1; i >= 0; i--) {
let e = enemies[i];
// 移向地球(屏幕底部中心)
let targetX = width / 2;
let targetY = height - 50;
let angle = Math.atan2(targetY - e.y, targetX - e.x);
e.x += Math.cos(angle) * e.speed;
e.y += Math.sin(angle) * e.speed;
e.angle = angle;
// 敌人开火
e.fireCooldown--;
if (e.fireCooldown <= 0) {
e.fireCooldown = 80;
let shootAngle = Math.atan2(player.y - e.y, player.x - e.x);
bullets.push({
x: e.x, y: e.y,
vx: Math.cos(shootAngle) * 5,
vy: Math.sin(shootAngle) * 5,
damage: 10,
isPlayer: false,
color: '#EF4444',
life: 1
});
}
// 碰撞地球
if (e.y > height - 100) {
player.hp -= 20;
spawnExplosion(e.x, e.y, '#EF4444');
enemies.splice(i, 1);
if (player.hp < 0) player.hp = 0;
}
// 碰撞玩家
if (Math.hypot(e.x - player.x, e.y - player.y) < e.radius + player.radius) {
player.hp -= 15;
spawnExplosion(e.x, e.y, '#EF4444');
enemies.splice(i, 1);
if (player.hp < 0) player.hp = 0;
}
}
// 7. 更新敌方子弹
for (let i = bullets.length - 1; i >= 0; i--) {
let b = bullets[i];
if (!b.isPlayer) {
b.x += b.vx;
b.y += b.vy;
if (Math.hypot(b.x - player.x, b.y - player.y) < player.radius + 3) {
player.hp -= b.damage;
spawnExplosion(b.x, b.y, '#EF4444');
bullets.splice(i, 1);
if (player.hp < 0) player.hp = 0;
}
}
}
// 8. 更新粒子
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= 0.05;
if (p.life <= 0) particles.splice(i, 1);
}
// 检查游戏结束
if (player.hp <= 0) {
gameRunning = false;
document.getElementById('game-over').style.display = 'flex';
}
// 更新UI
document.getElementById('hp-fill').style.width = Math.max(0, player.hp) + '%';
document.getElementById('hp-text').innerText = Math.max(0, Math.floor(player.hp)) + '%';
let heatPercent = (player.heat / player.maxHeat) * 100;
let heatFill = document.getElementById('heat-fill');
heatFill.style.width = heatPercent + '%';
document.getElementById('heat-text').innerText = Math.floor(heatPercent) + '%';
if (player.isOverheated) {
heatFill.classList.add('cooling');
document.getElementById('heat-text').innerText = "过热停机!";
document.getElementById('heat-text').style.color = "#facc15";
} else {
heatFill.classList.remove('cooling');
document.getElementById('heat-text').style.color = "#e2e8f0";
}
document.getElementById('score').innerText = score;
}
function render() {
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, width, height);
// 绘制星空
for (let i = 0; i < 100; i++) {
let sx = (i * 137.5) % width;
let sy = (i * 73.1) % height;
ctx.fillStyle = `rgba(255, 255, 255, ${0.1 + (i % 3) * 0.1})`;
ctx.fillRect(sx, sy, 1.5, 1.5);
}
// 绘制地球(在屏幕底部作为防线)
ctx.beginPath();
ctx.arc(width / 2, height + 200, 300, Math.PI, Math.PI * 2);
let earthGrad = ctx.createRadialGradient(width/2, height - 100, 50, width/2, height + 200, 300);
earthGrad.addColorStop(0, '#3B82F6');
earthGrad.addColorStop(1, '#1E3A8A');
ctx.fillStyle = earthGrad;
ctx.fill();
// 绘制子弹
bullets.forEach(b => {
ctx.beginPath();
ctx.arc(b.x, b.y, b.isPlayer ? 3 : 4, 0, Math.PI * 2);
ctx.fillStyle = b.color;
ctx.shadowColor = b.color;
ctx.shadowBlur = 10;
ctx.fill();
ctx.shadowBlur = 0;
});
// 绘制敌人
enemies.forEach(e => {
ctx.save();
ctx.translate(e.x, e.y);
ctx.rotate(e.angle);
ctx.beginPath();
ctx.moveTo(-8, -6);
ctx.lineTo(8, 0);
ctx.lineTo(-8, 6);
ctx.closePath();
ctx.fillStyle = e.color;
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.stroke();
ctx.restore();
});
// 绘制粒子
particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
});
ctx.globalAlpha = 1;
// 绘制玩家
if (gameRunning || player.hp > 0) {
drawPlayer();
}
// 绘制主炮激光束(如果开火)
if (isFiring && !player.isOverheated && gameRunning) {
ctx.beginPath();
ctx.moveTo(player.x + Math.cos(player.angle) * 25, player.y + Math.sin(player.angle) * 25);
ctx.lineTo(player.x + Math.cos(player.angle) * 800, player.y + Math.sin(player.angle) * 800);
ctx.strokeStyle = 'rgba(34, 211, 238, 0.8)';
ctx.lineWidth = 6;
ctx.stroke();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.stroke();
}
}
// 游戏主循环
function loop() {
update();
render();
requestAnimationFrame(loop);
}
// --- 输入控制逻辑 ---
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
if (!gameRunning) return;
let touch = e.touches[0];
let rect = canvas.getBoundingClientRect();
isDragging = true;
touchTarget.x = touch.clientX - rect.left;
touchTarget.y = touch.clientY - rect.top;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!gameRunning || !isDragging) return;
let touch = e.touches[0];
let rect = canvas.getBoundingClientRect();
touchTarget.x = touch.clientX - rect.left;
touchTarget.y = touch.clientY - rect.top;
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
isDragging = false;
}, { passive: false });
// 桌面端鼠标兼容
canvas.addEventListener('mousedown', (e) => {
if (!gameRunning) return;
isDragging = true;
touchTarget.x = e.clientX;
touchTarget.y = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
touchTarget.x = e.clientX;
touchTarget.y = e.clientY;
}
});
canvas.addEventListener('mouseup', () => { isDragging = false; });
// 主炮开火按钮
const fireBtn = document.getElementById('btn-fire');
fireBtn.addEventListener('touchstart', (e) => { e.preventDefault(); isFiring = true; fireBtn.classList.add('active'); }, { passive: false });
fireBtn.addEventListener('touchend', (e) => { e.preventDefault(); isFiring = false; fireBtn.classList.remove('active'); }, { passive: false });
fireBtn.addEventListener('mousedown', () => { isFiring = true; fireBtn.classList.add('active'); });
fireBtn.addEventListener('mouseup', () => { isFiring = false; fireBtn.classList.remove('active'); });
fireBtn.addEventListener('mouseleave', () => { isFiring = false; fireBtn.classList.remove('active'); });
// 重新开始
document.getElementById('restart-btn').addEventListener('click', () => {
gameRunning = true;
player.hp = 100;
player.heat = 0;
player.isOverheated = false;
player.x = width / 2;
player.y = height - 150;
touchTarget.x = player.x;
touchTarget.y = player.y;
enemies = [];
bullets = [];
particles = [];
score = 0;
document.getElementById('game-over').style.display = 'none';
});
// 启动
loop();
</script>
</body>
</html>Game Source: 轨道防御:祖冲之号
Creator: TurboKoala83
Libraries: none
Complexity: complex (655 lines, 23.4 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: game-turbokoala83-muqudid8" to link back to the original. Then publish at arcadelab.ai/publish.