🎮ArcadeLab

太阳系战术星图 - 手机端

by TurboKoala83
369 lines14.2 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;
        }
        #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;
            font-size: 12px;
            backdrop-filter: blur(5px);
            box-shadow: 0 4px 15px rgba(0,0,0,0.5);
        }
        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;
            white-space: nowrap;
        }
        button:active {
            background: #22d3ee;
            color: #0f172a;
        }
    </style>
</head>
<body>
    <canvas id="mapCanvas"></canvas>
    
    <div id="controls">
        <button id="btnReset">🔍 重置视角</button>
        <div class="legend" style="display:flex; gap:10px; align-items:center; color:#9ca3af;">
            <div style="display:flex; align-items:center; gap:3px;">
                <div style="width:6px;height:6px;border-radius:50%;background:#3b82f6;"></div>地月系
            </div>
            <div style="display:flex; align-items:center; gap:3px;">
                <div style="width:6px;height:6px;border-radius:50%;background:#ef4444;"></div>火星边界
            </div>
        </div>
    </div>

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

        // ---- 相机/视角状态 ----
        let offsetX = 0;      // 视野X偏移
        let offsetY = 0;      // 视野Y偏移
        let scale = 0.8;      // 当前缩放比例
        const MIN_SCALE = 0.2;
        const MAX_SCALE = 4.0;

        // ---- 触控/鼠标状态 ----
        let isDragging = false;
        let lastX = 0, lastY = 0;       // 单指拖动记录
        let initialPinchDist = 0;       // 双指初始距离
        let initialPinchScale = 1;      // 双指初始缩放比例
        
        // ---- 配置行星数据 (相对太阳坐标,以(0,0)为原点) ----
        const planets = [
            { name: '水星', orbitRadius: 60,  speed: 0.030, color: '#9CA3AF', size: 3,  angle: Math.random()*Math.PI*2 },
            { name: '金星', orbitRadius: 100, speed: 0.022, color: '#FDE047', size: 4,  angle: Math.random()*Math.PI*2 },
            { name: '地球', orbitRadius: 150, speed: 0.015, color: '#3B82F6', size: 5,  angle: Math.random()*Math.PI*2, hasMoon: true },
            { name: '火星', orbitRadius: 210, speed: 0.010, color: '#EF4444', size: 4,  angle: Math.random()*Math.PI*2, isBorder: true },
            { name: '木星', orbitRadius: 340, speed: 0.005, color: '#D97706', size: 12, angle: Math.random()*Math.PI*2 },
            { name: '土星', orbitRadius: 460, speed: 0.003, color: '#FCD34D', size: 9,  angle: Math.random()*Math.PI*2, hasRing: true }
        ];

        // ---- 星空背景 ----
        const stars = [];
        function initStars() {
            stars.length = 0;
            for (let i = 0; i < 300; i++) {
                stars.push({
                    x: Math.random() * width,
                    y: Math.random() * height,
                    radius: Math.random() * 1.2,
                    alpha: Math.random() * 0.8 + 0.2,
                    twinkleSpeed: Math.random() * 0.02 + 0.01
                });
            }
        }

        // ---- 初始化画布和自适应 ----
        function resize() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
            initStars();
            // 初始偏移量设为屏幕中心,使太阳居中
            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 render() {
            // 1. 清空画布
            ctx.fillStyle = '#02040a';
            ctx.fillRect(0, 0, width, height);

            // 2. 绘制星空 (星空固定在屏幕背景,不跟随缩放平移,营造景深)
            stars.forEach(star => {
                star.alpha += star.twinkleSpeed;
                if (star.alpha > 1 || star.alpha < 0.1) star.twinkleSpeed *= -1;
                
                ctx.beginPath();
                ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
                ctx.fillStyle = `rgba(255, 255, 255, ${star.alpha})`;
                ctx.fill();
            });

            // 3. 保存绘图状态,应用相机变换
            ctx.save();
            ctx.translate(offsetX, offsetY);
            ctx.scale(scale, scale);

            // 4. 绘制太阳 (世界坐标原点 0,0)
            const sunGrad = ctx.createRadialGradient(0, 0, 5, 0, 0, 40);
            sunGrad.addColorStop(0, '#FEF08A');
            sunGrad.addColorStop(0.5, '#F59E0B');
            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');

            // 5. 绘制轨道和行星
            planets.forEach(planet => {
                // 轨道线
                ctx.beginPath();
                ctx.arc(0, 0, planet.orbitRadius, 0, Math.PI * 2);
                
                if (planet.isBorder) {
                    ctx.strokeStyle = 'rgba(239, 68, 68, 0.4)'; // 火星边界虚线
                    ctx.lineWidth = 1.5;
                    ctx.setLineDash([6, 6]);
                } else {
                    ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
                    ctx.lineWidth = 1;
                    ctx.setLineDash([]);
                }
                ctx.stroke();
                ctx.setLineDash([]);

                // 计算行星位置
                let px = Math.cos(planet.angle) * planet.orbitRadius;
                let py = Math.sin(planet.angle) * planet.orbitRadius;

                // 绘制行星拖尾光晕
                const trailLength = 10;
                for (let i = 0; i < trailLength; i++) {
                    let trailAngle = planet.angle - (i * 0.03 * planet.speed * 10);
                    let tx = Math.cos(trailAngle) * planet.orbitRadius;
                    let ty = Math.sin(trailAngle) * planet.orbitRadius;
                    ctx.beginPath();
                    ctx.arc(tx, ty, planet.size * (1 - i/trailLength) * 0.6, 0, Math.PI * 2);
                    ctx.fillStyle = planet.color;
                    ctx.globalAlpha = (1 - i/trailLength) * 0.15;
                    ctx.fill();
                }
                ctx.globalAlpha = 1;

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

                // 土星光环
                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 = 2;
                    ctx.stroke();
                }

                // 地球的月球
                if (planet.hasMoon) {
                    let moonDist = 12;
                    let moonAngle = planet.angle * 8;
                    let mx = px + Math.cos(moonAngle) * moonDist;
                    let my = py + Math.sin(moonAngle) * moonDist;
                    
                    ctx.beginPath();
                    ctx.arc(px, py, moonDist, 0, Math.PI * 2);
                    ctx.strokeStyle = 'rgba(255,255,255,0.1)';
                    ctx.lineWidth = 1;
                    ctx.stroke();
                    
                    drawCircle(mx, my, 2, '#E2E8F0');
                }

                // 军事战略光点(地球和火星)
                if (planet.name === '地球' || planet.name === '火星') {
                    ctx.beginPath();
                    ctx.arc(px, py + 10, 1.5, 0, Math.PI * 2);
                    ctx.fillStyle = '#22D3EE';
                    ctx.fill();
                }

                // 更新公转角度
                if (!isPaused) {
                    planet.angle += planet.speed * 0.02;
                }
            });

            // 6. 恢复绘图状态 (相机的变换不影响UI和其他画布元素)
            ctx.restore();

            // 7. 绘制人类活动边界示意(根据相机偏移和缩放动态计算的大圈)
            ctx.beginPath();
            ctx.arc(offsetX, offsetY, 260 * scale, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(239, 68, 68, 0.1)';
            ctx.lineWidth = 1;
            ctx.stroke();

            requestAnimationFrame(render);
        }

        // ==========================================
        // 📱 手机端触摸事件处理(拖拽 + 双指缩放)
        // ==========================================

        // 1. 触摸开始
        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 });

        // 2. 触摸移动
        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 });

        // 3. 触摸结束
        canvas.addEventListener('touchend', (e) => {
            e.preventDefault();
            isDragging = false;
            initialPinchDist = 0;
        }, { passive: false });

        // 4. 兼容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', () => {
            offsetX = width / 2;
            offsetY = height / 2;
            scale = 0.8;
        });

        // 启动渲染
        render();
    </script>
</body>
</html>

Game Source: 太阳系战术星图 - 手机端

Creator: TurboKoala83

Libraries: none

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