太阳系战术星图 - 手机端
by TurboKoala83369 lines14.2 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;
}
#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.