🎮ArcadeLab

太阳系战术星图 - 最终逻辑修正版

by TurboKoala83
355 lines13.9 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: #02040a;
            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-panel {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
            background: rgba(15, 23, 42, 0.85);
            padding: 8px 12px;
            border-radius: 8px;
            border: 1px solid #1e3a5f;
            font-size: 11px;
            backdrop-filter: blur(5px);
            pointer-events: none;
        }
        #controls {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 100;
            background: rgba(15, 23, 42, 0.85);
            padding: 10px 15px;
            border-radius: 20px;
            border: 1px solid #1e3a5f;
            display: flex;
            gap: 10px;
            align-items: center;
            backdrop-filter: blur(5px);
        }
        button {
            background: #1e293b;
            color: #22d3ee;
            border: 1px solid #22d3ee;
            padding: 8px 16px;
            border-radius: 15px;
            cursor: pointer;
            font-family: inherit;
            font-size: 12px;
            transition: all 0.2s;
        }
        button:active { background: #22d3ee; color: #0f172a; }
        .text-cyan { color: #22d3ee; }
    </style>
</head>
<body>
    <div id="ui-panel">
        <span class="text-cyan">观测状态:</span><span id="zoom-status">战术视图 (全貌)</span><br>
        <span class="text-cyan">物理比例:</span>地球的 1/20 (最大40像素)
    </div>

    <canvas id="mapCanvas"></canvas>
    
    <div id="controls">
        <button id="btnReset">🔍 锁定地球视角</button>
    </div>

    <script>
        const canvas = document.getElementById('mapCanvas');
        const ctx = canvas.getContext('2d');
        
        let width, height;
        let isPaused = false;

        // ---- 核心相机参数 ----
        let offsetX = 0, offsetY = 0;
        let scale = 8.0; 
        const MIN_SCALE = 0.05; 
        const MAX_SCALE = 150.0; 

        // ---- 交互状态 ----
        let isDragging = false;
        let lastX = 0, lastY = 0;
        let initialPinchDist = 0, initialPinchScale = 1;

        // ---- 行星数据 ----
        const planets = [
            { name: '地球', orbitRadius: 150, speed: 0.015, color: '#3B82F6', size: 6, angle: 0, hasMoon: true },
            { name: '火星', orbitRadius: 240, speed: 0.010, color: '#EF4444', size: 5, angle: Math.random()*Math.PI*2, isBorder: true },
            { name: '木星', orbitRadius: 400, speed: 0.005, color: '#D97706', size: 14, angle: Math.random()*Math.PI*2 },
            { name: '土星', orbitRadius: 520, speed: 0.003, color: '#FCD34D', size: 11, angle: Math.random()*Math.PI*2, hasRing: true }
        ];

        function resize() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
            offsetX = width / 2;
            offsetY = height / 2;
        }
        window.addEventListener('resize', resize);
        resize();

        function drawCircle(x, y, r, fillColor, strokeColor, lineWidth) {
            ctx.beginPath();
            ctx.arc(x, y, r, 0, Math.PI * 2);
            if (fillColor) { ctx.fillStyle = fillColor; ctx.fill(); }
            if (strokeColor) { ctx.strokeStyle = strokeColor; ctx.lineWidth = lineWidth || 1; ctx.stroke(); }
        }

        // ==========================================
        // 🚀 核心绘制:祖冲之号(逻辑彻底修正)
        // ==========================================
        function drawZuchongzhi(x, y, currentScale) {
            // 1. 远观视野(缩小,scale变小):显示三角形战术图标
            if (currentScale < 2.0) {
                ctx.save();
                ctx.translate(x, y);
                // 抵消世界缩放,让三角形在屏幕上始终保持固定大小(8像素)
                ctx.scale(1/currentScale, 1/currentScale);
                
                let triSize = 8; 
                ctx.beginPath();
                ctx.moveTo(0, -triSize);
                ctx.lineTo(triSize * 0.866, triSize * 0.5);
                ctx.lineTo(-triSize * 0.866, triSize * 0.5);
                ctx.closePath();
                ctx.fillStyle = '#22D3EE';
                ctx.shadowColor = '#22D3EE';
                ctx.shadowBlur = 10;
                ctx.fill();
                ctx.restore();
                return;
            }

            // 2. 近观视野(放大,scale变大):展示全貌模型
            ctx.save();
            ctx.translate(x, y);
            
            // 💡 核心限制:无论怎么放大,战舰在屏幕上的最大尺寸锁定为 40 像素
            // 这样即使地球占满屏幕,战舰也只是旁边的一个小不点
            let screenSize = Math.min(currentScale * 2, 40); 
            let modelScale = screenSize / 300; // 基准模型尺寸为300
            ctx.scale(modelScale, modelScale);

            // 动态计算线宽,保证在极度缩小时线条依然清晰可见
            let invScale = 1 / (modelScale * currentScale);
            let lineWidth = 3 * invScale; 
            let blueLineWidth = 5 * invScale; 

            // 绘制中央脊线 (白色空间站外壳)
            ctx.fillStyle = '#F3F4F6';
            ctx.strokeStyle = '#4B5563';
            ctx.lineWidth = lineWidth;
            ctx.fillRect(-30, -140, 60, 280);
            ctx.strokeRect(-30, -140, 60, 280);

            // 绘制两侧散热巨翼 (深色面板)
            ctx.fillStyle = '#1E293B';
            ctx.strokeStyle = '#64748B';
            ctx.fillRect(-180, -80, 130, 160);
            ctx.strokeRect(-180, -80, 130, 160);
            ctx.fillRect(50, -80, 130, 160);
            ctx.strokeRect(50, -80, 130, 160);

            // 绘制蓝色散热管
            ctx.fillStyle = '#38BDF8';
            for(let i=0; i<5; i++) {
                ctx.fillRect(-160 + i*24, -60, blueLineWidth, 120);
                ctx.fillRect(70 + i*24, -60, blueLineWidth, 120);
            }

            // 绘制主炮 (舰首左侧)
            ctx.fillStyle = '#7F1D1D';
            ctx.fillRect(-80, -120, 15, 70);
            ctx.fillStyle = '#22D3EE';
            ctx.fillRect(-78, -120, 11, 20);

            // 绘制六门拦截炮 (荧光绿)
            ctx.fillStyle = '#A3E635';
            const cannons = [[0, -180], [0, 180], [-140, 0], [140, 0], [-100, 100], [100, 100]];
            cannons.forEach(c => {
                ctx.beginPath();
                ctx.arc(c[0], c[1], 10, 0, Math.PI * 2);
                ctx.fill();
            });

            ctx.restore();
        }

        // ---- 主渲染循环 ----
        function render() {
            ctx.fillStyle = '#02040a';
            ctx.fillRect(0, 0, width, height);

            // 星空背景
            for (let i = 0; i < 200; i++) {
                let sx = (i * 137.5) % width;
                let sy = (i * 73.1) % height;
                ctx.beginPath();
                ctx.arc(sx, sy, 0.8, 0, Math.PI * 2);
                ctx.fillStyle = `rgba(255, 255, 255, ${0.2 + (i % 5) * 0.1})`;
                ctx.fill();
            }

            ctx.save();
            ctx.translate(offsetX, offsetY);
            ctx.scale(scale, scale);

            // 太阳
            const sunGrad = ctx.createRadialGradient(0, 0, 5, 0, 0, 40);
            sunGrad.addColorStop(0, '#FEF08A');
            sunGrad.addColorStop(1, 'rgba(245, 158, 11, 0)');
            ctx.beginPath(); ctx.arc(0, 0, 40, 0, Math.PI * 2);
            ctx.fillStyle = sunGrad; ctx.fill();
            drawCircle(0, 0, 12, '#FBBF24');

            // 行星
            planets.forEach(planet => {
                ctx.beginPath();
                ctx.arc(0, 0, planet.orbitRadius, 0, Math.PI * 2);
                ctx.strokeStyle = planet.isBorder ? 'rgba(239, 68, 68, 0.4)' : 'rgba(71, 85, 105, 0.3)';
                ctx.lineWidth = 1 / scale;
                ctx.setLineDash(planet.isBorder ? [6, 6] : []);
                ctx.stroke();
                ctx.setLineDash([]);

                let px = Math.cos(planet.angle) * planet.orbitRadius;
                let py = Math.sin(planet.angle) * planet.orbitRadius;

                // 绘制地球及其周边
                if (planet.name === '地球') {
                    // 月球
                    let moonAngle = planet.angle * 8;
                    let mx = px + Math.cos(moonAngle) * 15;
                    let my = py + Math.sin(moonAngle) * 15;
                    drawCircle(mx, my, 2, '#E2E8F0');

                    // 祖冲之号
                    let zcAngle = planet.angle * 20; 
                    let zcDist = 18; 
                    let zcX = px + Math.cos(zcAngle) * zcDist;
                    let zcY = py + Math.sin(zcAngle) * zcDist;
                    drawZuchongzhi(zcX, zcY, scale);
                }

                // 行星本体
                drawCircle(px, py, planet.size, planet.color, 'rgba(255,255,255,0.3)');

                if (planet.hasRing) {
                    ctx.beginPath();
                    ctx.ellipse(px, py, planet.size * 2.2, planet.size * 0.8, Math.PI / 6, 0, Math.PI * 2);
                    ctx.strokeStyle = 'rgba(252, 211, 77, 0.5)';
                    ctx.lineWidth = 1 / scale;
                    ctx.stroke();
                }

                if (!isPaused) planet.angle += planet.speed * 0.02;
            });

            ctx.restore();

            // UI文字动态更新
            let statusText = "宏观观测 (光点)";
            if (scale > 2) statusText = "战术视图 (全貌,限制40像素)";
            else if (scale > 0.5) statusText = "行星轨道视图 (三角形图标)";
            else statusText = "太阳系全局";
            document.getElementById('zoom-status').innerText = statusText;

            requestAnimationFrame(render);
        }

        // ==========================================
        // 📱 手机端触摸控制逻辑
        // ==========================================
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            if (e.touches.length === 1) {
                isDragging = true;
                lastX = e.touches[0].clientX;
                lastY = e.touches[0].clientY;
            } else if (e.touches.length === 2) {
                isDragging = false;
                initialPinchDist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
                initialPinchScale = scale;
            }
        }, { passive: false });

        canvas.addEventListener('touchmove', (e) => {
            e.preventDefault();
            if (e.touches.length === 1 && isDragging) {
                let dx = e.touches[0].clientX - lastX;
                let dy = e.touches[0].clientY - lastY;
                offsetX += dx;
                offsetY += dy;
                lastX = e.touches[0].clientX;
                lastY = e.touches[0].clientY;
            } else if (e.touches.length === 2 && initialPinchDist > 0) {
                let currentDist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
                let ratio = currentDist / initialPinchDist;
                let newScale = initialPinchScale * ratio;
                
                newScale = Math.max(MIN_SCALE, Math.min(newScale, MAX_SCALE));
                
                let centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
                let centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
                
                offsetX = centerX - (centerX - offsetX) * (newScale / scale);
                offsetY = centerY - (centerY - offsetY) * (newScale / scale);
                scale = newScale;
            }
        }, { passive: false });

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

        // PC端兼容
        canvas.addEventListener('mousedown', (e) => { isDragging = true; lastX = e.clientX; lastY = e.clientY; });
        canvas.addEventListener('mousemove', (e) => {
            if (isDragging) { offsetX += e.clientX - lastX; offsetY += e.clientY - lastY; lastX = e.clientX; lastY = e.clientY; }
        });
        canvas.addEventListener('mouseup', () => { isDragging = false; });
        canvas.addEventListener('wheel', (e) => {
            e.preventDefault();
            let zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
            let newScale = Math.max(MIN_SCALE, Math.min(scale * zoomFactor, MAX_SCALE));
            offsetX = e.clientX - (e.clientX - offsetX) * (newScale / scale);
            offsetY = e.clientY - (e.clientY - offsetY) * (newScale / scale);
            scale = newScale;
        }, { passive: false });

        // 重置按钮
        document.getElementById('btnReset').addEventListener('click', () => {
            let earth = planets.find(p => p.name === '地球');
            let px = Math.cos(earth.angle) * earth.orbitRadius;
            let py = Math.sin(earth.angle) * earth.orbitRadius;

            // 设定一个能清晰看到地球,且战舰极其微小的缩放级别
            scale = 8.0; 
            offsetX = width / 2 - px * scale;
            offsetY = height / 2 - py * scale;
        });

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

Game Source: 太阳系战术星图 - 最终逻辑修正版

Creator: TurboKoala83

Libraries: none

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