🎮ArcadeLab

轨道防御:祖冲之号

by TurboKoala83
655 lines23.4 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.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.