太阳系战术星图 - 逻辑修正版
by TurboKoala83357 lines14.0 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: #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>最大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 = 1.0;
const MIN_SCALE = 0.05;
const MAX_SCALE = 200.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) {
// 💡 逻辑修正:
// 当视野缩小(拉远,scale变小)时 -> 显示三角形战术图标
// 当视野放大(拉近,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();
} else {
// 【战术拉近模式】显示详细模型,严格限制最大屏幕尺寸
ctx.save();
ctx.translate(x, y);
// 锁定模型的最大屏幕像素为 40px,确保它看起来永远比地球小得多
let screenSize = Math.min(currentScale * 2, 40);
let modelScale = screenSize / 300;
ctx.scale(modelScale, modelScale);
// 动态计算线宽,保证手机屏幕上线条清晰
let invScale = 1 / modelScale;
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 = 15.0;
offsetX = width / 2 - px * scale;
offsetY = height / 2 - py * scale;
});
render();
</script>
</body>
</html>Game Source: 太阳系战术星图 - 逻辑修正版
Creator: TurboKoala83
Libraries: none
Complexity: complex (357 lines, 14.0 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-muqu4ond" to link back to the original. Then publish at arcadelab.ai/publish.