基于HTML的重力翻转
首页文章工具搜索

基于HTML的重力翻转

2026.09.16 [HTML] [小游戏] 85 浏览

上水课闲的无聊至极的时候拿DeepSeek生成的一个小游戏,觉得蛮好玩的就挂上了。
玩法操作:点击屏幕或按空格控制重力方向,躲开红色障碍挑战高分。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> <title>重力翻转</title> <style> *{margin:0;padding:0;box-sizing:border-box;} html,body{ width:100%;height:100%;overflow:hidden; background:#080810; touch-action:none; -webkit-user-select:none;user-select:none; -webkit-tap-highlight-color:transparent; } canvas{display:block;position:fixed;inset:0;} </style> </head> <body> <canvas id="game"></canvas> <script> (() => { "use strict"; const cvs = document.getElementById('game'); const ctx = cvs.getContext('2d'); let W = 0, H = 0, DPR = 1; let ceilY = 80, groundY = 700; const player = { x: 0, y: 0, vy: 0, size: 26 }; let state = 'menu'; // menu | playing | dead let grav = 1; // 1 = 向下, -1 = 向上 let obstacles = []; let particles = []; let score = 0; let best = 0; let elapsed = 0; let spawnTimer = 0; let speed = 380; let shake = 0; let gridOffset = 0; let lastSide = 'floor'; let deadTime = 0; try { best = parseInt(localStorage.getItem('gravityFlipBest') || '0', 10) || 0; } catch (e) { best = 0; } /* ---------------- 尺寸 ---------------- */ function resize() { DPR = Math.min(window.devicePixelRatio || 1, 2); W = window.innerWidth; H = window.innerHeight; cvs.width = Math.floor(W * DPR); cvs.height = Math.floor(H * DPR); cvs.style.width = W + 'px'; cvs.style.height = H + 'px'; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); const pad = Math.max(60, Math.min(H * 0.13, 110)); ceilY = pad; groundY = H - pad; player.size = Math.max(18, Math.min(30, (groundY - ceilY) * 0.055)); player.x = Math.min(W * 0.25, 200); if (state === 'menu') { player.y = groundY - player.size; } else { player.y = Math.max(ceilY, Math.min(player.y, groundY - player.size)); } } /* ---------------- 工具 ---------------- */ function rrect(x, y, w, h, r) { r = Math.min(r, w / 2, h / 2); ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); } function centerText(text, y, size, color, weight, glow) { ctx.save(); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = (weight || '700') + ' ' + size + 'px system-ui,-apple-system,"PingFang SC","Microsoft YaHei",sans-serif'; if (glow) { ctx.shadowColor = glow; ctx.shadowBlur = 24; } ctx.fillStyle = color; ctx.fillText(text, W / 2, y); ctx.restore(); } /* ---------------- 游戏逻辑 ---------------- */ function reset() { player.x = Math.min(W * 0.25, 200); player.y = groundY - player.size; player.vy = 0; grav = 1; obstacles.length = 0; particles.length = 0; score = 0; elapsed = 0; speed = 380; spawnTimer = 1.15; shake = 0; lastSide = 'floor'; deadTime = 0; state = 'playing'; } function spawnObstacle() { const corridor = groundY - ceilY; const h = corridor * 0.52; const w = Math.max(30, Math.min(52, W * 0.04)); let side; if (Math.random() < 0.65) { side = lastSide === 'floor' ? 'ceil' : 'floor'; } else { side = lastSide; } lastSide = side; obstacles.push({ x: W + 30, w: w, h: h, side: side, scored: false }); } function collide(o) { const ps = player.size; let oy, oh = o.h; if (o.side === 'floor') oy = groundY - oh; else oy = ceilY; return player.x < o.x + o.w && player.x + ps > o.x && player.y < oy + oh && player.y + ps > oy; } function burst(x, y, n, power, colors) { for (let i = 0; i < n; i++) { const a = Math.random() * Math.PI * 2; const sp = power * (0.3 + Math.random()); const life = 0.4 + Math.random() * 0.5; particles.push({ x: x, y: y, vx: Math.cos(a) * sp, vy: Math.sin(a) * sp, life: life, maxLife: life, r: 1.2 + Math.random() * 3, color: colors[(Math.random() * colors.length) | 0] }); } } function die() { state = 'dead'; deadTime = 0; shake = 20; if (score > best) { best = score; try { localStorage.setItem('gravityFlipBest', String(best)); } catch (e) {} } burst(player.x + player.size / 2, player.y + player.size / 2, 46, 380, ['#00f0ff', '#ff2e63', '#ffffff']); } function flip() { if (state === 'playing') { grav *= -1; player.vy = 0; shake = Math.max(shake, 7); burst(player.x + player.size / 2, player.y + player.size / 2, 12, 220, ['#00f0ff', '#9dfcff']); } else if (state === 'menu') { reset(); } else if (state === 'dead' && deadTime > 0.4) { reset(); } } /* ---------------- 更新 ---------------- */ function update(dt) { gridOffset = (gridOffset + (state === 'playing' ? speed : 150) * dt) % 100000; if (shake > 0) shake = Math.max(0, shake - dt * 48); if (state === 'dead') deadTime += dt; // 粒子 for (let i = particles.length - 1; i >= 0; i--) { const p = particles[i]; p.x += p.vx * dt; p.y += p.vy * dt; p.vx *= 0.96; p.vy *= 0.96; p.vy += 220 * dt * 0.15; p.life -= dt; if (p.life <= 0) particles.splice(i, 1); } if (state !== 'playing') return; elapsed += dt; speed = 380 + Math.min(400, elapsed * 13); /* --- 玩家物理 --- */ const G = 7200; player.vy += G * grav * dt; player.vy = Math.max(-3600, Math.min(3600, player.vy)); player.y += player.vy * dt; if (player.y < ceilY) { player.y = ceilY; player.vy = 0; } const botLim = groundY - player.size; if (player.y > botLim) { player.y = botLim; player.vy = 0; } /* --- 障碍移动 --- */ for (let i = 0; i < obstacles.length; i++) obstacles[i].x -= speed * dt; /* --- 计分 / 回收 --- */ for (let i = obstacles.length - 1; i >= 0; i--) { const o = obstacles[i]; if (!o.scored && o.x + o.w < player.x) { o.scored = true; score++; } if (o.x + o.w < -60) obstacles.splice(i, 1); } /* --- 生成 --- */ spawnTimer -= dt; if (spawnTimer <= 0) { spawnObstacle(); spawnTimer = Math.max(0.52, 0.95 - elapsed * 0.009); } /* --- 拖尾粒子 --- */ const cx = player.x + player.size / 2; const cy = player.y + player.size / 2; for (let i = 0; i < 2; i++) { const life = 0.35 + Math.random() * 0.2; particles.push({ x: cx + (Math.random() - 0.5) * player.size, y: cy + (Math.random() - 0.5) * player.size, vx: -speed * 0.22 + (Math.random() - 0.5) * 70, vy: (Math.random() - 0.5) * 70 - grav * 40, life: life, maxLife: life, r: 0.8 + Math.random() * 2.2, color: '#00f0ff' }); } /* --- 碰撞 --- */ for (let i = 0; i < obstacles.length; i++) { if (collide(obstacles[i])) { die(); break; } } } /* ---------------- 绘制 ---------------- */ function drawBg() { const g = ctx.createLinearGradient(0, 0, 0, H); g.addColorStop(0, '#080810'); g.addColorStop(0.5, '#0d0d1c'); g.addColorStop(1, '#080810'); ctx.fillStyle = g; ctx.fillRect(0, 0, W, H); const gs = 64; const off = gridOffset % gs; ctx.strokeStyle = 'rgba(90,120,255,0.075)'; ctx.lineWidth = 1; ctx.beginPath(); for (let x = -off; x < W + gs; x += gs) { ctx.moveTo(x, ceilY); ctx.lineTo(x, groundY); } ctx.stroke(); } function drawWalls() { // 天花板 let g = ctx.createLinearGradient(0, 0, 0, ceilY); g.addColorStop(0, 'rgba(70,100,255,0.22)'); g.addColorStop(1, 'rgba(70,100,255,0.015)'); ctx.fillStyle = g; ctx.fillRect(0, 0, W, ceilY); ctx.save(); ctx.shadowColor = '#4d6bff'; ctx.shadowBlur = 16; ctx.fillStyle = '#5f7dff'; ctx.fillRect(0, ceilY - 2, W, 2); ctx.restore(); // 地面 g = ctx.createLinearGradient(0, groundY, 0, H); g.addColorStop(0, 'rgba(70,100,255,0.22)'); g.addColorStop(1, 'rgba(70,100,255,0.015)'); ctx.fillStyle = g; ctx.fillRect(0, groundY, W, H - groundY); ctx.save(); ctx.shadowColor = '#4d6bff'; ctx.shadowBlur = 16; ctx.fillStyle = '#5f7dff'; ctx.fillRect(0, groundY, W, 2); ctx.restore(); } function drawObstacles() { for (let i = 0; i < obstacles.length; i++) { const o = obstacles[i]; let oy; if (o.side === 'floor') oy = groundY - o.h; else oy = ceilY; ctx.save(); ctx.shadowColor = '#ff2e63'; ctx.shadowBlur = 22; const g = ctx.createLinearGradient(0, oy, 0, oy + o.h); if (o.side === 'floor') { g.addColorStop(0, '#ff2e63'); g.addColorStop(1, '#3d0b1c'); } else { g.addColorStop(0, '#3d0b1c'); g.addColorStop(1, '#ff2e63'); } ctx.fillStyle = g; rrect(o.x, oy, o.w, o.h, 6); ctx.fill(); // 尖端高光 ctx.shadowBlur = 0; ctx.fillStyle = 'rgba(255,225,240,0.9)'; if (o.side === 'floor') ctx.fillRect(o.x + 4, oy, o.w - 8, 3); else ctx.fillRect(o.x + 4, oy + o.h - 3, o.w - 8, 3); ctx.restore(); } } function drawPlayer() { const p = player; ctx.save(); ctx.shadowColor = '#00f0ff'; ctx.shadowBlur = 26; const g = ctx.createLinearGradient(p.x, p.y, p.x + p.size, p.y + p.size); g.addColorStop(0, '#9ffcff'); g.addColorStop(1, '#00b8d4'); ctx.fillStyle = g; rrect(p.x, p.y, p.size, p.size, 7); ctx.fill(); ctx.shadowBlur = 0; ctx.fillStyle = 'rgba(255,255,255,0.85)'; rrect(p.x + p.size * 0.3, p.y + p.size * 0.3, p.size * 0.4, p.size * 0.4, 3); ctx.fill(); ctx.restore(); } function drawParticles() { for (let i = 0; i < particles.length; i++) { const p = particles[i]; const a = Math.max(0, p.life / p.maxLife); ctx.globalAlpha = a * 0.85; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.r * (0.4 + a * 0.6), 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = 1; } function drawHud() { if (state !== 'playing') return; ctx.save(); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = '700 ' + Math.min(44, W * 0.08) + 'px system-ui,-apple-system,"PingFang SC",sans-serif'; ctx.shadowColor = 'rgba(0,240,255,0.85)'; ctx.shadowBlur = 20; ctx.fillStyle = 'rgba(255,255,255,0.95)'; ctx.fillText(String(score), W / 2, ceilY / 2 + 2); ctx.restore(); } function drawMenu() { ctx.fillStyle = 'rgba(6,6,14,0.7)'; ctx.fillRect(0, 0, W, H); const t = performance.now() / 1000; const pulse = 0.55 + 0.45 * Math.sin(t * 3); centerText('重 力 翻 转', H * 0.34, Math.min(52, W * 0.1), '#ffffff', '800', '#00f0ff'); centerText('点击屏幕 / 空格 → 翻转重力', H * 0.46, Math.min(19, W * 0.045), 'rgba(200,220,255,0.9)', '500'); centerText('躲开红色障碍,活得越久越好', H * 0.52, Math.min(15, W * 0.037), 'rgba(150,170,210,0.65)', '400'); ctx.save(); ctx.globalAlpha = pulse; centerText('点 击 开 始', H * 0.68, Math.min(24, W * 0.052), '#00f0ff', '700', '#00f0ff'); ctx.restore(); } function drawDead() { ctx.fillStyle = 'rgba(255,46,99,' + Math.max(0, 0.28 - deadTime * 0.6) + ')'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = 'rgba(6,6,14,0.72)'; ctx.fillRect(0, 0, W, H); centerText('撞 到 了', H * 0.33, Math.min(46, W * 0.09), '#ff2e63', '800', '#ff2e63'); centerText('得分 ' + score, H * 0.45, Math.min(30, W * 0.062), '#ffffff', '700'); centerText('最高 ' + best, H * 0.52, Math.min(18, W * 0.042), 'rgba(160,180,220,0.85)', '500'); if (deadTime > 0.4) { const pulse = 0.55 + 0.45 * Math.sin(performance.now() / 200); ctx.save(); ctx.globalAlpha = pulse; centerText('点击重新开始', H * 0.68, Math.min(22, W * 0.05), '#00f0ff', '600', '#00f0ff'); ctx.restore(); } } function draw() { ctx.setTransform(DPR, 0, 0, DPR, 0, 0); ctx.clearRect(0, 0, W, H); ctx.save(); if (shake > 0.3) { ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake); } drawBg(); drawWalls(); drawObstacles(); drawParticles(); if (state !== 'dead') drawPlayer(); ctx.restore(); drawHud(); if (state === 'menu') drawMenu(); else if (state === 'dead') drawDead(); } /* ---------------- 主循环 ---------------- */ let lastT = 0; function loop(t) { if (!lastT) lastT = t; let dt = (t - lastT) / 1000; lastT = t; if (dt > 0.05) dt = 0.05; if (dt < 0) dt = 0; update(dt); draw(); requestAnimationFrame(loop); } /* ---------------- 输入 ---------------- */ window.addEventListener('pointerdown', function (e) { e.preventDefault(); flip(); }, { passive: false }); window.addEventListener('keydown', function (e) { if (e.repeat) return; if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Enter') { e.preventDefault(); flip(); } }); window.addEventListener('contextmenu', function (e) { e.preventDefault(); }); let rzTimer = null; window.addEventListener('resize', function () { clearTimeout(rzTimer); rzTimer = setTimeout(resize, 80); }); /* ---------------- 启动 ---------------- */ resize(); requestAnimationFrame(loop); })(); </script> </body> </html>
相关推荐