🎮ArcadeLab

太阳系防御战 - 祖冲之号

by TurboKoala83
718 lines27.3 KB
▶ Play
<!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.8);
            padding: 12px;
            border-radius: 8px;
            border: 1px solid #1e3a5f;
            backdrop-filter: blur(4px);
            width: 220px;
            border-left: 3px solid #22d3ee;
        }
        .bar-container { width: 100%; }
        .bar-label { font-size: 11px; color: #94a3b8; 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; }
        #heat-fill.overheat { background: #facc15; }

        /* 底部控制区 */
        .hud-bottom {
            display: flex;
            justify-content: space-between;
            align-items: flex-end;
            pointer-events: auto;
            padding-bottom: 10px;
        }
        #btn-fire {
            width: 90px;
            height: 90px;
            border-radius: 50%;
            background: rgba(34, 211, 238, 0.15);
            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.2);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            user-select: none;
            transition: all 0.1s;
        }
        #btn-fire:active, #btn-fire.active {
            background: rgba(34, 211, 238, 0.7);
            color: #fff;
            box-shadow: 0 0 40px rgba(34, 211, 238, 0.8);
            transform: scale(0.95);
        }

        #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">
        <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; display: flex; justify-content: space-between;">
                <span>战绩: <span id="score" style="color:#22d3ee; font-weight:bold;">0</span></span>
                <span>友军: <span id="ally-count" style="color:#10b981;">2</span></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 camera = {
            x: 0, y: 0,
            scale: 0.8,
            minScale: 0.5,
            maxScale: 1.5
        };

        // --- 实体数据 ---
        const player = {
            type: 'player',
            x: 0, y: 0,
            radius: 15, 
            hp: 100,
            heat: 0,
            maxHeat: 100,
            isOverheated: false,
            angle: -Math.PI / 2,
            targetAngle: -Math.PI / 2,
            cannonCooldown: 0,
            interceptorCooldown: 0
        };

        // AI 友军:昆仑山号
        const ally1 = {
            type: 'ally',
            name: '昆仑山号',
            x: -100, y: 0,
            radius: 20,
            hp: 200, maxHp: 200,
            angle: -Math.PI / 2,
            fireCooldown: 0,
            color: '#3b82f6' // 友军蓝色调
        };

        // AI 友军:抽屉导弹舰
        const ally2 = {
            type: 'ally',
            name: '抽屉导弹舰',
            x: 100, y: 0,
            radius: 18,
            hp: 150, maxHp: 150,
            angle: -Math.PI / 2,
            fireCooldown: 0,
            volleyCooldown: 0,
            color: '#10b981' // 友军绿色调
        };

        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 = 0;
            player.y = 100;
            touchTarget.x = player.x;
            touchTarget.y = player.y;
            
            ally1.x = -120; ally1.y = 200;
            ally2.x = 120; ally2.y = 250;
            
            camera.x = 0;
            camera.y = 0;
        }
        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 < 10; i++) {
                let angle = Math.random() * Math.PI * 2;
                let speed = Math.random() * 4 + 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.8)';
                ctx.beginPath();
                ctx.moveTo(-5, 25); ctx.lineTo(0, 40); ctx.lineTo(5, 25);
                ctx.fill();
            }

            // 1. 两侧散热巨翼 (深色模块,带红色栅格) - 按照原图,横跨在中央脊线两侧
            ctx.fillStyle = '#1E293B';
            ctx.strokeStyle = '#64748B';
            ctx.lineWidth = 1.5;
            // 左翼
            ctx.fillRect(-45, -10, 30, 20);
            ctx.strokeRect(-45, -10, 30, 20);
            // 右翼
            ctx.fillRect(15, -10, 30, 20);
            ctx.strokeRect(15, -10, 30, 20);
            
            // 翼上红色散热栅格 (模拟原图红线)
            ctx.fillStyle = '#ef4444';
            for(let i=0; i<4; i++) {
                ctx.fillRect(-40 + i*8, -8, 2, 16); // 左翼
                ctx.fillRect(20 + i*8, -8, 2, 16);   // 右翼
            }

            // 2. 中央脊线 (修长,白色+蓝色AI核心)
            // 顶部
            ctx.fillStyle = '#F3F4F6';
            ctx.fillRect(-6, -35, 12, 10); 
            // 中部白色装甲
            ctx.fillRect(-6, -25, 12, 15); 
            // 蓝色AI核心
            ctx.fillStyle = '#1E3A8A';
            ctx.fillRect(-6, -10, 12, 15);
            // 生活舱 (灰色)
            ctx.fillStyle = '#9CA3AF';
            ctx.fillRect(-6, 5, 12, 10);
            // 底部装甲
            ctx.fillStyle = '#F3F4F6';
            ctx.fillRect(-6, 15, 12, 15);

            // 3. 主炮 (位于左前侧,红底青口)
            ctx.fillStyle = '#7F1D1D';
            ctx.fillRect(-12, -40, 6, 25);
            ctx.fillStyle = '#22D3EE'; // 青色炮口
            ctx.fillRect(-12, -40, 6, 8);

            // 4. 六门拦截炮 (灰色底座小移动圆)
            ctx.fillStyle = '#6B7280';
            const cannons = [[-18, -5], [ (18, -5], [-18, 10], [18, 10], [-10, -15], [10, -15]];
            cannons.forEach(c => {
在世界                ctx.beginPath();
                ctx.arc(c[0], c[1], 2.5, 0, Math.PI * 2);
                ctx.fill();
            });

            ctx.restore();
        }

        function drawAlly(ally) {
            ctx.save();
            ctx.translate(ally.x, ally.y);
            ctx.rotate(ally.angle);

            // 友军外壳
            ctx.fillStyle = '#1e3a8a'; // 深蓝色
            ctx.strokeStyle = ally.color;
            ctx.lineWidth = 2;
            
            // 简单绘制友军轮廓(区别于玩家)
            ctx.fillRect(-15, -20, 30, 40);
            ctx.strokeRect(-15, -20, 30, 40);
            
            // 友军武器
            ctx.fillStyle = '#38bdf8';
            ctx.fillRect(-10, -30, 20, 10);

            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 = 8; 
                    let bulletX = player.x + Math.cos(player.angle) * 40;
                    let bulletY = player.y + Math.sin(player.angle) * 40;
                    bullets.push({
                        x: bulletX, y: bulletY,
                        vx: Math.cos(player.angle) * 18,
                        vy: Math.sin(player.angle) * 18,
                        damage: 15, isPlayer: true, color: '#22D3EE'
                    });
                }
            } else {
                heatChange -= 1.8; 
            }

            // 拦截炮自动开火
            if (player.interceptorCooldown <= 0 && enemies.length > 0) {
                player.interceptorCooldown = 25; 
                heatChange += 0.3; 
                let nearestEnemy = null, 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 < 300) {
                    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: 4, isPlayer: true, color: '#A3E635'
                    });
                }
            }
            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. 友军AI逻辑
            [ally1, ally2].forEach(ally => {
                // 跟随玩家
                let targetX = player.x + (ally === ally1 ? -120 : 120);
                let targetY = player.y + (ally === ally1 ? 100 : 150);
                let dx = targetX - ally.x;
                let dy = targetY - ally.y;
                let dist = Math.hypot(dx, dy);
                if (dist > 10) {
                    ally.x += (dx / dist) * 2;
                    ally.y += (dy / dist) * 2;
                }

                // 寻找敌人开火
                ally.fireCooldown--;
                if (ally.fireCooldown <= 0 && enemies.length > 0) {
                    let nearest = null, minD = Infinity;
                    enemies.forEach(e => {
                        let d = Math.hypot(e.x - ally.x, e.y - ally.y);
                        if (d < minD) { minD = d; nearest = e; }
                    });
                    if (nearest) {
                        ally.angle = Math.atan2(nearest.y - ally.y, nearest.x - ally.x);
                        ally.fireCooldown = 20;
                        bullets.push({
                            x: ally.x + Math.cos(ally.angle) * 20, y: ally.y + Math.sin(ally.angle) * 20,
                            vx: Math.cos(ally.angle) * 10, vy: Math.sin(ally.angle) * 10,
                            damage: 8, isPlayer: true, color: ally.color
                        });
                    }
                }
            });

            // 抽屉导弹舰特殊能力 (发射大量导弹)
            ally2.volleyCooldown--;
            if (ally2.volleyCooldown <= 0 && enemies.length > 0) {
                ally2.volleyCooldown = 200;
                for(let i=0; i<6; i++) {
                    let angle = ally2.angle + (Math.random() - 0.5) * 0.8;
                    bullets.push({
                        x: ally2.x, y: ally2.y,
                        vx: Math.cos(angle) * 8, vy: Math.sin(angle) * 8,
                        damage: 6, isPlayer: true, color: '#f97316' // 橙色导弹
                    });
                }
            }

            // 4. 自动瞄准转向
            let nearest = null, 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;
            }

            // 5. 生成敌人 (攻击星)
            if (Math.random() < 0.025) {
                let angle = Math.random() * Math.PI * 2;
                let dist = Math.max(width, height);
                let ex = player.x + Math.cos(angle) * dist;
                let ey = player.y + Math.sin(angle) * dist;
                enemies.push({
                    x: ex, y: ey, radius: 8, hp: 20, speed: 1.2,
                    angle: 0, color: '#EF4444', fireCooldown: 60
                });
            }

            // 6. 更新子弹
            for (let i = bullets.length - 1; i >= 0; i--) {
                let b = bullets[i];
                b.x += b.vx; b.y += b.vy;
                if (b.x < -2000 || b.x > 2000 || b.y < -2000 || b.y > 2000) { bullets.splice(i, 1); continue; }
                
                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 + 4) {
                            e.hp -= b.damage;
                            spawnExplosion(b.x, b.y, b.color);
                            bullets.splice(i, 1);
                            if (e.hp <= 0) { enemies.splice(j, 1); score += 10; }
                            break;
                        }
                    }
                }
            }

            // 7. 更新敌人
            for (let i = enemies.length - 1; i >= 0; i--) {
                let e = enemies[i];
                // 敌人AI:优先攻击玩家,如果太远就撞地球
                let targetX = player.x;
                let targetY = player.y;
                // 如果玩家很近,就追玩家
                if (Math.hypot(player.x - e.x, player.y - e.y) > 400) {
                    targetX = 0; targetY = 300; // 假设地球在玩家下方
                }
                
                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: 5, isPlayer: false, color: '#EF4444'
                    });
                }

                // 攻击玩家
                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);
                }
                // 攻击友军
                [ally1, ally2].forEach(ally => {
                    if (Math.hypot(e.x - ally.x, e.y - ally.y) < e.radius + ally.radius) {
                        ally.hp -= 10; spawnExplosion(e.x, e.y, '#EF4444'); enemies.splice(i, 1);
                    }
                });
            }

            // 8. 更新敌方子弹
            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);
                    }
                }
            }

            // 9. 更新粒子
            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);
            }

            // 相机跟随玩家 (限制视野范围,不无限拉远)
            camera.x = player.x;
            camera.y = player.y;

            // 检查游戏结束
            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('overheat');
                document.getElementById('heat-text').innerText = "过热停机!";
            } else {
                heatFill.classList.remove('overheat');
            }

            document.getElementById('score').innerText = score;
            document.getElementById('ally-count').innerText = (ally1.hp > 0 ? 1 : 0) + (ally2.hp > 0 ? 1 : 0);
        }

        function render() {
            ctx.fillStyle = '#050505';
            ctx.fillRect(0, 0, width, height);

            // 绘制太阳系战术星图背景 (限制视野缩放,固定在背景)
            ctx.save();
            ctx.translate(width / 2 - camera.x * camera.scale, height / 2 - camera.y * camera.scale);
            ctx.scale(camera.scale, camera.scale);

            // 星空
            for (let i = 0; i < 150; i++) {
                let sx = (i * 137.5) % 2000 - 1000;
                let sy = (i * 73.1) % 2000 - 1000;
                ctx.fillStyle = `rgba(255, 255, 255, ${0.2 + (i % 3) * 0.1})`;
                ctx.fillRect(sx, sy, 1.5, 1.5);
            }

            // 太阳系轨道线 (战术星图感觉)
            ctx.strokeStyle = 'rgba(71, 85, 105, 0.4)';
            ctx.lineWidth = 2;
            ctx.beginPath(); ctx.arc(0, 0, 600, 0, Math.PI * 2); ctx.stroke(); // 地球轨道
            ctx.beginPath(); ctx.arc(0, 0, 900, 0, Math.PI * 2); ctx.stroke(); // 火星轨道
            
            // 地球防线 (在屏幕底部)
            ctx.beginPath();
            ctx.arc(0, 600, 400, Math.PI, Math.PI * 2);
            let earthGrad = ctx.createRadialGradient(0, 600, 100, 0, 600, 400);
            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) {
                if (ally1.hp > 0) drawAlly(ally1);
                if (ally2.hp > 0) drawAlly(ally2);
                drawPlayer();

                // 主炮激光束
                if (isFiring && !player.isOverheated) {
                    ctx.beginPath();
                    ctx.moveTo(player.x + Math.cos(player.angle) * 30, player.y + Math.sin(player.angle) * 30);
                    ctx.lineTo(player.x + Math.cos(player.angle) * 1200, player.y + Math.sin(player.angle) * 1200);
                    ctx.strokeStyle = 'rgba(34, 211, 238, 0.8)';
                    ctx.lineWidth = 6; ctx.stroke();
                    ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.stroke();
                }
            }

            ctx.restore();
        }

        function loop() {
            update();
            render();
            requestAnimationFrame(loop);
        }

        // --- 输入控制逻辑 ---
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            if (!gameRunning) return;
            let touch = e.touches[0];
            isDragging = true;
            // 将屏幕触摸坐标转换为世界坐标
            touchTarget.x = (touch.clientX - width / 2) / camera.scale + camera.x;
            touchTarget.y = (touch.clientY - height / 2) / camera.scale + camera.y;
        }, { passive: false });

        canvas.addEventListener('touchmove', (e) => {
            e.preventDefault();
            if (!gameRunning || !isDragging) return;
            let touch = e.touches[0];
            touchTarget.x = (touch.clientX - width / 2) / camera.scale + camera.x;
            touchTarget.y = (touch.clientY - height / 2) / camera.scale + camera.y;
        }, { passive: false });

        canvas.addEventListener('touchend', (e) => { e.preventDefault(); isDragging = false; }, { passive: false });

        // PC端兼容
        canvas.addEventListener('mousedown', (e) => {
            if (!gameRunning) return;
            isDragging = true;
            touchTarget.x = (e.clientX - width / 2) / camera.scale + camera.x;
            touchTarget.y = (e.clientY - height / 2) / camera.scale + camera.y;
        });
        canvas.addEventListener('mousemove', (e) => {
            if (isDragging) {
                touchTarget.x = (e.clientX - width / 2) / camera.scale + camera.x;
                touchTarget.y = (e.clientY - height / 2) / camera.scale + camera.y;
            }
        });
        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 = 0; player.y = 100;
            touchTarget.x = player.x; touchTarget.y = player.y;
            ally1.hp = 200; ally2.hp = 150;
            ally1.x = -120; ally1.y = 200; ally2.x = 120; ally2.y = 250;
            enemies = []; bullets = []; particles = []; score = 0;
            document.getElementById('game-over').style.display = 'none';
        });

        loop();
    </script>
</body>
</html>

Game Source: 太阳系防御战 - 祖冲之号

Creator: TurboKoala83

Libraries: none

Complexity: complex (718 lines, 27.3 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-muquh5yt" to link back to the original. Then publish at arcadelab.ai/publish.