🎮ArcadeLab

小游戏合集1‑5 【黑屏终极修复版】

by MysticMaker96
944 lines29.7 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, viewport-fit=cover"/>
<title>小游戏合集</title>
<style>
*{
  margin:0;padding:0;box-sizing:border-box;font-family:system-ui, sans-serif;
  -webkit-tap-highlight-color: transparent;
}
html,body{
  width:100%;
  height:100%;
  overflow:hidden;
  background:#181818;
}
#gameWrap{
  position:fixed;
  inset:0;
  width:100%;
  height:100%;
}
#score{
  position:fixed;
  top:14px;left:14px;
  color:white;
  font-size:22px;
  z-index:999;
  pointer-events:none;
}
#tip{
  position:fixed;
  top:50px;left:14px;
  color:#ffd166;
  font-size:16px;
  z-index:999;
  pointer-events:none;
}
#canvas{
  display:block;
  background:#222;
  position:fixed;
  top:0;left:0;
  z-index:10;
  touch-action: none;
}

#game4-ui{
  display:none;
  position:fixed;
  inset:0;
  z-index:9998;
  pointer-events:none;
}
.joystick-base{
  position:fixed;
  left:30px;
  bottom:30px;
  width:110px;height:110px;
  border-radius:50%;
  background:rgba(255,255,255,0.18);
  border:2px solid rgba(255,255,255,0.35);
  pointer-events:auto;
  z-index:99999;
}
.joystick-knob{
  position:absolute;
  width:46px;height:46px;
  border-radius:50%;
  background:rgba(255,255,255,0.6);
  left:32px;top:32px;
}
.btn-jump{
  position:fixed;
  right:30px;
  bottom:100px;
  width:70px;height:70px;
  border-radius:50%;
  border:none;
  background:#3498db;
  color:white;
  font-size:30px;
  pointer-events:auto;
  z-index:99999;
}
.btn-mine{
  position:fixed;
  right:30px;
  bottom:20px;
  width:70px;height:70px;
  border-radius:50%;
  border:none;
  background:#27ae60;
  color:white;
  font-size:16px;
  font-weight:bold;
  pointer-events:auto;
  z-index:99999;
}

#game5-ui{
  display:none;
  position:fixed;
  inset:0;
  z-index:9998;
  pointer-events:none;
}
.btn-brake{
  position:fixed;
  left:30px;
  bottom:30px;
  width:100px;height:60px;
  border-radius:12px;
  border:none;
  background:#d33;
  color:white;
  font-size:18px;
  pointer-events:auto;
  z-index:99999;
}
.btn-gas{
  position:fixed;
  right:30px;
  bottom:30px;
  width:100px;height:60px;
  border-radius:12px;
  border:none;
  background:#2a8;
  color:white;
  font-size:18px;
  pointer-events:auto;
  z-index:99999;
}
</style>
</head>
<body>

<div id="gameWrap">
  <div id="score">分数:0</div>
  <div id="tip">选择下面的游戏开始游玩!</div>
  <canvas id="canvas"></canvas>

  <div id="game4-ui">
    <div class="joystick-base" id="joyBase">
      <div class="joystick-knob" id="joyKnob"></div>
    </div>
    <button class="btn-jump" id="btnJump">↑</button>
    <button class="btn-mine" id="btnMine">挖矿</button>
  </div>

  <div id="game5-ui">
    <button class="btn-brake" id="btnBrake">🛑 刹车</button>
    <button class="btn-gas" id="btnGas">🚀 油门</button>
  </div>
</div>

<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const scoreDom = document.getElementById('score');
const tipDom = document.getElementById('tip');
const game4UI = document.getElementById('game4-ui');
const game5UI = document.getElementById('game5-ui');
const btnBrake = document.getElementById('btnBrake');
const btnGas = document.getElementById('btnGas');
const btnJump = document.getElementById('btnJump');
const btnMine = document.getElementById('btnMine');
const joyBase = document.getElementById("joyBase");
const joyKnob = document.getElementById("joyKnob");

let mode = 0;
let score = 0;
let gameOver = false;
let audioCtx = null;
let levelSpeed = 1;
let showGreatText = false;
let greatTimer = 0;

let holdingGas = false;
let holdingBrake = false;

const hallButtons = [
  { label: "游戏1:跳跃躲尖刺", color: "#2980b9", y: 72, h:64 },
  { label: "游戏2:飞碟身法挑战", color: "#27ae60", y: 144, h:64 },
  { label: "游戏3:蓝小人射红小人", color: "#e67e22", y: 216, h:64 },
  { label: "游戏4:洞穴挖矿探险", color: "#9c27b0", y: 288, h:64 },
  { label: "游戏5:公路骑车狂奔", color: "#d63031", y: 360, h:64 },
];
const btnW = 260;
const backBtn = { w:130, h:50 };

let touchStart = {x:0,y:0,time:0};
const TAP_MOVE_LIMIT = 20;
let dragState = {active:false, dragMode:0};
let dpr = window.devicePixelRatio || 1;

// 视口:增加兜底,并且过滤掉 0 尺寸
function getViewport(){
  let w, h;
  if(window.visualViewport && window.visualViewport.width > 20 && window.visualViewport.height > 20){
    w = window.visualViewport.width;
    h = window.visualViewport.height;
  }else{
    w = window.innerWidth;
    h = window.innerHeight;
  }
  return {w, h};
}

function resize(){
  try{
    const vp = getViewport();
    if(vp.w <= 20 || vp.h <= 20) return;
    dpr = window.devicePixelRatio || 1;
    canvas.style.width = vp.w + "px";
    canvas.style.height = vp.h + "px";
    canvas.width = vp.w * dpr;
    canvas.height = vp.h * dpr;
    ctx.resetTransform();
    ctx.scale(dpr, dpr);
  }catch(e){}
}

function initAudio(){
  if(audioCtx) return;
  try{
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  }catch(e){}
}

function playJumpSound(){
  if(!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  osc.type="sine";
  osc.frequency.setValueAtTime(320, audioCtx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(520, audioCtx.currentTime + 0.12);
  gain.gain.setValueAtTime(0.22, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.12);
  osc.start(); osc.stop(audioCtx.currentTime + 0.12);
}
function playFailSound(){
  if(!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  osc.type="sawtooth";
  osc.frequency.setValueAtTime(240, audioCtx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.35);
  gain.gain.setValueAtTime(0.28, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.35);
  osc.start(); osc.stop(audioCtx.currentTime + 0.35);
}
function playSlingshotSound(){
  if(!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  osc.type="triangle";
  osc.frequency.setValueAtTime(180, audioCtx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(650, audioCtx.currentTime + 0.04);
  gain.gain.setValueAtTime(0.35, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.08);
  osc.start(); osc.stop(audioCtx.currentTime + 0.08);
}
function playHitSound(){
  if(!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  osc.type="square";
  osc.frequency.setValueAtTime(440, audioCtx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(880, audioCtx.currentTime + 0.1);
  gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.12);
  osc.start(); osc.stop(audioCtx.currentTime + 0.12);
}
function playMineSound(){
  if(!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  osc.type="sawtooth";
  osc.frequency.setValueAtTime(160, audioCtx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(220, audioCtx.currentTime + 0.08);
  gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.1);
  osc.start(); osc.stop(audioCtx.currentTime + 0.1);
}
function playGreatSound(){
  if(!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  osc.type="sine";
  osc.frequency.setValueAtTime(400, audioCtx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(700, audioCtx.currentTime + 0.2);
  gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.22);
  osc.start(); osc.stop(audioCtx.currentTime + 0.22);
}

function drawBackButton(){
  const vp = getViewport();
  const x = vp.w - backBtn.w - 12;
  const y = 12;
  ctx.fillStyle="#c83838";
  ctx.beginPath();
  ctx.roundRect(x,y,backBtn.w,backBtn.h,14);
  ctx.fill();
  ctx.fillStyle="#ffffff";
  ctx.font="bold 19px sans-serif";
  ctx.textAlign="center";
  ctx.fillText("返回大厅",x+backBtn.w/2, y+backBtn.h/2+7);
}
function checkHitBack(px,py){
  const vp = getViewport();
  const x = vp.w - backBtn.w -12;
  const y =12;
  return px >=x && px <=x+backBtn.w && py >=y && py <= y+backBtn.h;
}

function drawMan(x,y,color,frame){
  const cx = x + 18;
  const headR = 11;
  const armA = Math.sin(frame)*0.4;
  const legA = Math.cos(frame)*0.4;

  ctx.fillStyle=color;
  ctx.fillRect(cx - 8, y + headR + 1, 16,14);

  ctx.fillStyle="#ffd9a8";
  ctx.beginPath();
  ctx.arc(cx, y + headR, headR, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle="#111";
  ctx.lineWidth=2;
  ctx.stroke();

  ctx.fillStyle="#111";
  ctx.beginPath();
  ctx.arc(cx - 3, y + headR - 1, 1.8,0,Math.PI*2);
  ctx.arc(cx + 4, y + headR - 1, 1.8,0,Math.PI*2);
  ctx.fill();

  ctx.beginPath();
  ctx.arc(cx + 1, y + headR + 3, 2, 0, Math.PI);
  ctx.stroke();

  ctx.strokeStyle=color;
  ctx.lineWidth=5;
  ctx.lineCap="round";

  ctx.beginPath();
  ctx.moveTo(cx - 8, y + headR + 4);
  ctx.lineTo(cx -14 + Math.sin(armA)*6, y + headR + 10 + Math.cos(armA)*6);
  ctx.stroke();

  ctx.beginPath();
  ctx.moveTo(cx + 8, y + headR + 4);
  ctx.lineTo(cx +14 + Math.sin(-armA)*6, y + headR + 10 + Math.cos(-armA)*6);
  ctx.stroke();

  ctx.beginPath();
  ctx.moveTo(cx -5, y + headR + 15);
  ctx.lineTo(cx -7 + Math.sin(legA)*5, y + headR + 22 + Math.cos(legA)*5);
  ctx.stroke();

  ctx.beginPath();
  ctx.moveTo(cx +5, y + headR + 15);
  ctx.lineTo(cx +7 + Math.sin(-legA)*5, y + headR + 22 + Math.cos(-legA)*5);
  ctx.stroke();
}
// 修复:碰撞函数支持自定义宽高
function hit(a,b){
  const aw = a.w ?? 36;
  const ah = a.h ?? 36;
  const bw = b.w ?? 36;
  const bh = b.h ?? 36;
  return a.x < b.x + bw && a.x + aw > b.x && a.y < b.y + bh && a.y + ah > b.y;
}

function drawHall(){
  const vp = getViewport();
  ctx.fillStyle="#181818";
  ctx.fillRect(0,0,vp.w,vp.h);
  ctx.fillStyle="#ffffff";
  ctx.font="bold 34px sans-serif";
  ctx.textAlign="center";
  ctx.fillText("小游戏合集", vp.w/2, 56);

  const ox = (vp.w - btnW)/2;
  for(const b of hallButtons){
    ctx.fillStyle = b.color;
    ctx.beginPath();
    ctx.roundRect(ox, b.y, btnW, b.h,14);
    ctx.fill();
    ctx.fillStyle="#fff";
    ctx.font="21px sans-serif";
    ctx.textAlign="center";
    ctx.fillText(b.label, vp.w/2, b.y + b.h/2 +7);
  }
}
function checkHallTap(px,py){
  const vp = getViewport();
  const ox = (vp.w - btnW)/2;
  for(let i=0;i<hallButtons.length;i++){
    const b = hallButtons[i];
    if(px >= ox && px <= ox+btnW && py >= b.y && py <= b.y + b.h){
      initAudio();
      startGame(i+1);
      return true;
    }
  }
  return false;
}

//====游戏1====
const p1 = {x:80,y:300,w:36,h:44,vy:0,jumpPower:-20,gravity:0.65,onGround:true,frame:0};
let spikes1 = [];
let speed1 = 4;
let spawn1 = 0;
let lastTenMark = 0;

function reset1(){
  const vp = getViewport();
  p1.y = vp.h - 120; p1.vy =0;
  spikes1=[]; speed1=4; score=0; gameOver=false; spawn1=0; lastTenMark=0;showGreatText=false;greatTimer=0;
  scoreDom.textContent="分数:"+score;
  tipDom.innerText="点击屏幕跳跃,躲开尖刺!";
}
function jump(){
  if(gameOver){reset1();return;}
  if(p1.onGround){
    p1.vy = p1.jumpPower;
    p1.onGround=false;
    playJumpSound();
  }
}
function newSpike1(){
  const vp = getViewport();
  const h =40+Math.random()*70;
  spikes1.push({x:vp.w+10,y:vp.h-h,w:28,h:h});
}
function tick1(){
  const vp = getViewport();
  const groundY = vp.h -12;
  if(!gameOver){
    p1.vy += p1.gravity;
    p1.y += p1.vy;
    if(p1.y+p1.h >= groundY){
      p1.y = groundY-p1.h;
      p1.vy=0;
      p1.onGround=true;
      p1.frame +=0.25;
    }
    spawn1++;
    if(spawn1>90+Math.random()*60){newSpike1();spawn1=0;}
    for(let i=spikes1.length-1;i>=0;i--){
      const s = spikes1[i];
      s.x -= speed1;
      if(hit(p1,s)){gameOver=true;playFailSound();}
      if(s.x+s.w <0){
        spikes1.splice(i,1);
        score++; scoreDom.textContent="分数:"+score;
        if(score%8===0)speed1+=0.3;
        if(score-lastTenMark>=10){lastTenMark=score;showGreatText=true;greatTimer=70;playGreatSound();}
      }
    }
  }
  ctx.clearRect(0,0,vp.w,vp.h);
  drawBackButton();
  ctx.fillStyle="#555"; ctx.fillRect(0,groundY,vp.w,12);
  ctx.fillStyle="#ef4444";
  for(const sp of spikes1){
    ctx.beginPath();
    ctx.moveTo(sp.x,sp.y+sp.h);
    ctx.lineTo(sp.x+sp.w/2,sp.y);
    ctx.lineTo(sp.x+sp.w,sp.y+sp.h);
    ctx.closePath();ctx.fill();
  }
  drawMan(p1.x,p1.y,"#42a5f5",p1.frame);
  if(showGreatText){
    ctx.fillStyle="#ffdd00";
    ctx.font="bold 36px sans-serif"; ctx.textAlign="center";
    ctx.fillText("太棒了!",vp.w/2,vp.h/2-60);
    greatTimer--; if(greatTimer<=0)showGreatText=false;
  }
  if(gameOver){
    ctx.fillStyle="#fff";
    ctx.font="bold 28px sans-serif"; ctx.textAlign="center";
    ctx.fillText("游戏结束!点击画布重开",vp.w/2,vp.h/2);
  }
}

//====游戏2飞碟====
const saucer = {x:70,y:300,w:90,h:24,targetY:300,frame:0};
const p2 = {x:0,y:0,w:24,h:24,frame:0};
let spikes2 = [];
let speed2 =4;
let spawn2=0;

function reset2(){
  const vp = getViewport();
  saucer.y = vp.h/2; saucer.targetY = saucer.y; saucer.x=70;
  spikes2=[]; speed2=4; score=0;gameOver=false;spawn2=0;
  scoreDom.textContent="分数:"+score;
  tipDom.innerText="拖动飞碟上下躲避尖刺";
}
function newSpike2(){
  const vp = getViewport();
  const topSide = Math.random()>0.5;
  const w=30+Math.random()*40;
  if(topSide){
    spikes2.push({x:vp.w+10,y:0,w:w,h:50+Math.random()*80,top:true});
  }else{
    const h=50+Math.random()*80;
    spikes2.push({x:vp.w+10,y:vp.h-h,w:w,h:h,top:false});
  }
}
function drawSaucer(x,y,frame){
  const glow = 0.5 + Math.sin(frame*0.1)*0.2;
  ctx.save(); ctx.globalAlpha=glow;
  ctx.fillStyle="#00ffff";
  ctx.beginPath(); ctx.ellipse(x+saucer.w/2,y+saucer.h+8,saucer.w*0.55,10,0,0,Math.PI*2); ctx.fill();
  ctx.restore();
  const grad = ctx.createLinearGradient(x,y,x,y+saucer.h);
  grad.addColorStop(0,"#cfd8dc");grad.addColorStop(1,"#546e7a");
  ctx.fillStyle=grad;
  ctx.beginPath(); ctx.ellipse(x+saucer.w/2,y+saucer.h/2,saucer.w/2,saucer.h/2,0,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#1a1a1a"; ctx.lineWidth=2; ctx.stroke();
  ctx.fillStyle="rgba(128,222,234,0.85)";
  ctx.beginPath(); ctx.ellipse(x+saucer.w/2,y+2,22,10,0,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#1a1a1a"; ctx.lineWidth=1.5; ctx.stroke();
  ctx.fillStyle="#37474f";
  ctx.beginPath(); ctx.ellipse(x+saucer.w/2,y+6,24,12,0,0,Math.PI*2); ctx.fill();
  ctx.fillStyle="#ffeb3b";
  for(let i=0;i<4;i++){
    ctx.beginPath(); ctx.arc(x+16+i*18,y+saucer.h-5,2,0,Math.PI*2); ctx.fill();
  }
}
function drawP2Head(x,y){
  const cx = x+p2.w/2;
  const headR=10;
  ctx.fillStyle="#ffd9a8";
  ctx.beginPath(); ctx.arc(cx,y+headR,headR,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#111"; ctx.lineWidth=2; ctx.stroke();
  ctx.fillStyle="#42a5f5";
  ctx.beginPath(); ctx.arc(cx,y+headR-2,headR,Math.PI,0); ctx.fill();
  ctx.fillStyle="#111";
  ctx.beginPath(); ctx.arc(cx-3,y+headR,1.6,0,Math.PI*2); ctx.arc(cx+3,y+headR,1.6,0,Math.PI*2); ctx.fill();
  ctx.beginPath(); ctx.arc(cx,y+headR+2,2.5,0,Math.PI); ctx.stroke();
  ctx.fillStyle="#42a5f5"; ctx.fillRect(cx-6,y+headR*2-2,12,4);
}
function tick2(){
  const vp = getViewport();
  if(!gameOver){
    saucer.y += (saucer.targetY-saucer.y)*0.25; saucer.frame++;
    const headOffsetY = -18;
    p2.x = saucer.x+saucer.w/2-p2.w/2;
    p2.y = saucer.y+headOffsetY;
    spawn2++;
    if(spawn2>38+Math.random()*27){newSpike2();spawn2=0;}
    for(let i=spikes2.length-1;i>=0;i--){
      const s=spikes2[i];
      s.x -= speed2;
      if(hit(p2,s)){gameOver=true;playFailSound();}
      if(s.x+s.w<0){spikes2.splice(i,1);score++;scoreDom.textContent="分数:"+score;if(score%10===0)speed2+=0.3;}
    }
  }
  ctx.clearRect(0,0,vp.w,vp.h);
  drawBackButton();
  ctx.fillStyle="#ef4444";
  for(const sp of spikes2){
    ctx.beginPath();
    if(sp.top){ctx.moveTo(sp.x,sp.y);ctx.lineTo(sp.x+sp.w/2,sp.y+sp.h);ctx.lineTo(sp.x+sp.w,sp.y);}
    else{ctx.moveTo(sp.x,sp.y+sp.h);ctx.lineTo(sp.x+sp.w/2,sp.y);ctx.lineTo(sp.x+sp.w,sp.y+sp.h);}
    ctx.closePath(); ctx.fill();
  }
  drawSaucer(saucer.x,saucer.y,saucer.frame);
  drawP2Head(p2.x,p2.y);
  if(gameOver){
    ctx.fillStyle="#fff"; ctx.font="bold 28px sans-serif"; ctx.textAlign="center";
    ctx.fillText("游戏结束!点击画布重开",vp.w/2,vp.h/2);
  }
}

//====游戏3弹弓====
let slingshot = {pullX:0,pullY:0,active:false};
let flyMen = [];
let enemies = [];
let spawnEnemyTimer = 0;

function reset3(){
  score=0;gameOver=false;levelSpeed=1;flyMen=[];enemies=[];spawnEnemyTimer=0;
  scoreDom.textContent="分数:"+score;
  tipDom.innerText="按住向后拖动弹弓,松开发射蓝色小人!";
}
function spawnEnemy(){
  const vp = getViewport();
  const margin=60;
  const ex=margin+Math.random()*(vp.w-margin*2-40);
  const ey=margin+Math.random()*(vp.h-140);
  enemies.push({x:ex,y:ey,vx:(Math.random()-0.5)*1.2*levelSpeed,vy:(Math.random()-0.5)*1.2*levelSpeed,frame:0,w:36,h:44});
}
function tick3(){
  const vp = getViewport();
  ctx.clearRect(0,0,vp.w,vp.h);
  drawBackButton();
  ctx.fillStyle="#2c2c2c"; ctx.fillRect(0,vp.h-40,vp.w,40);
  const ox=80; const oy=vp.h-80;
  ctx.strokeStyle="#8b4513"; ctx.lineWidth=7;
  ctx.beginPath(); ctx.moveTo(ox-22,oy-35);ctx.lineTo(ox,oy);ctx.lineTo(ox+22,oy-35); ctx.stroke();
  ctx.strokeStyle="#bb2222"; ctx.lineWidth=3;
  if(slingshot.active){
    ctx.beginPath(); ctx.moveTo(ox-22,oy-35);ctx.lineTo(slingshot.pullX,slingshot.pullY);ctx.lineTo(ox+22,oy-35); ctx.stroke();
  }else{
    ctx.beginPath(); ctx.moveTo(ox-22,oy-35);ctx.lineTo(ox+22,oy-35); ctx.stroke();
  }
  if(!slingshot.active && !gameOver) drawMan(ox-18,oy-40,"#42a5f5",0);
  for(let i=flyMen.length-1;i>=0;i--){
    const m=flyMen[i];
    m.x+=m.vx; m.y+=m.vy; m.vy+=0.18; m.frame+=0.2;
    drawMan(m.x,m.y,"#42a5f5",m.frame);
    if(m.x<-20||m.x>vp.w+20||m.y>vp.h+20) flyMen.splice(i,1);
  }
  const bound=30;
  for(let i=enemies.length-1;i>=0;i--){
    const e=enemies[i];
    e.x+=e.vx; e.y+=e.vy; e.frame+=0.18;
    if(e.x<bound||e.x>vp.w-bound-36){e.vx*=-1;e.x=Math.max(bound,Math.min(vp.w-bound-36,e.x));}
    if(e.y<bound||e.y>vp.h-80){e.vy*=-1;e.y=Math.max(bound,Math.min(vp.h-80,e.y));}
    drawMan(e.x,e.y,"#ef4444",e.frame);
    for(let j=flyMen.length-1;j>=0;j--){
      const fm=flyMen[j];
      if(hit(fm,e)){
        playHitSound(); enemies.splice(i,1); flyMen.splice(j,1);
        score+=10; scoreDom.textContent="分数:"+score; levelSpeed+=0.04;
        break;
      }
    }
  }
  spawnEnemyTimer++;
  const spawnGap = Math.max(60,140-levelSpeed*12);
  if(spawnEnemyTimer>spawnGap && enemies.length<8){spawnEnemy();spawnEnemyTimer=0;}
}

//====游戏4挖矿====
let player4 = {x:60,y:60,w:36,h:44,vx:0,vy:0,speed:3.2,gravity:0.28,jumpPower:-9,onGround:false,frame:0,mineCool:0};
let joy = {active:false,dx:0,dy:0};
let tiles = []; let ores = []; let lights = [];
const TILE = 24;

function generateCave(){
  const vp = getViewport();
  tiles=[];ores=[];lights=[];
  const w=Math.ceil(vp.w/TILE);
  const h=Math.ceil(vp.h/TILE);
  for(let y=0;y<h;y++){
    for(let x=0;x<w;x++){
      let solid=true;
      if(y<3)solid=false;
      else if(y<6)solid = Math.random()>0.62;
      else solid = Math.random()>0.32;
      tiles.push({x:x*TILE,y:y*TILE,solid});
      if(solid && y>=12 && Math.random()<0.07){
        ores.push({x:x*TILE,y:y*TILE,mined:false});
      }
    }
  }
  for(let i=0;i<5;i++){
    lights.push({x:80+Math.random()*(vp.w-160),y:100+Math.random()*(vp.h-220),radius:130});
  }
}
function reset4(){
  score=0;
  player4.x=60;player4.y=60;player4.vx=0;player4.vy=0;player4.onGround=false;player4.mineCool=0;
  generateCave();
  scoreDom.textContent="矿石:0";
  tipDom.innerText="摇杆移动|↑跳跃|挖矿按钮开采蓝矿石";
}
// 修复:瓦片索引实时计算,不再缓存一行数量
function tileAt(px,py){
  const tx=Math.floor(px/TILE);
  const ty=Math.floor(py/TILE);
  const vp = getViewport();
  const rowCnt = Math.ceil(vp.w / TILE);
  const idx = ty * rowCnt + tx;
  if(tiles[idx]) return tiles[idx];
  return {solid:false};
}
function collideBox(x,y,w,h){
  const pts = [{x:x+2,y:y+2},{x:x+w-2,y:y+2},{x:x+2,y:y+h-2},{x:x+w-2,y:y+h-2}];
  for(const p of pts){
    const t = tileAt(p.x,p.y);
    if(t.solid) return true;
  }
  return false;
}
function mineAction(){
  if(player4.mineCool>0) return;
  player4.mineCool=25; playMineSound();
  const mx=player4.x+player4.w/2;
  const my=player4.y+player4.h/2;
  for(const ore of ores){
    if(ore.mined) continue;
    const d=Math.hypot(mx-(ore.x+TILE/2),my-(ore.y+TILE/2));
    if(d<42){
      ore.mined=true; score+=1; scoreDom.textContent="矿石:"+score;
      const tx=Math.floor(ore.x/TILE); const ty=Math.floor(ore.y/TILE);
      const vp = getViewport();
      const rowCnt = Math.ceil(vp.w / TILE);
      const idx=ty*rowCnt+tx;
      if(tiles[idx]) tiles[idx].solid=false;
      break;
    }
  }
}

joyBase.addEventListener("touchstart",e=>{e.preventDefault();joy.active=true;});
joyBase.addEventListener("touchmove",e=>{
  e.preventDefault();
  if(!joy.active) return;
  const rect = joyBase.getBoundingClientRect();
  const cx=rect.left+rect.width/2;
  const cy=rect.top+rect.height/2;
  const t = e.touches[0];
  let dx = t.clientX - cx;
  let dy = t.clientY - cy;
  const r=40;
  const len=Math.hypot(dx,dy);
  if(len>r){ dx=dx/len*r; dy=dy/len*r; }
  joyKnob.style.left=(rect.width/2-23+dx)+"px";
  joyKnob.style.top=(rect.height/2-23+dy)+"px";
  joy.dx=dx/r; joy.dy=dy/r;
});
joyBase.addEventListener("touchend",e=>{e.preventDefault();joy.active=false;joy.dx=0;joy.dy=0;joyKnob.style.left="32px";joyKnob.style.top="32px";});
joyBase.addEventListener("touchcancel",e=>{e.preventDefault();joy.active=false;joy.dx=0;joy.dy=0;joyKnob.style.left="32px";joyKnob.style.top="32px";});

btnJump.addEventListener("touchend",e=>{
  e.preventDefault();
  initAudio();
  if(mode===4 && player4.onGround){player4.vy=player4.jumpPower;player4.onGround=false;playJumpSound();}
});
btnMine.addEventListener("touchend",e=>{
  e.preventDefault();
  initAudio();
  if(mode===4){mineAction();}
});

function tick4(){
  const vp = getViewport();
  ctx.fillStyle="#0c0c0c"; ctx.fillRect(0,0,vp.w,vp.h);
  drawBackButton();
  player4.vx = joy.dx * player4.speed;
  player4.vy += player4.gravity;
  if(player4.mineCool>0) player4.mineCool--;

  let nx = player4.x + player4.vx;
  if(!collideBox(nx,player4.y,player4.w,player4.h)) player4.x=nx; else player4.vx=0;
  let ny = player4.y + player4.vy;
  player4.onGround=false;
  if(!collideBox(player4.x,ny,player4.w,player4.h)) player4.y=ny; else { if(player4.vy>0)player4.onGround=true; player4.vy=0; }
  if(Math.abs(player4.vx)>0.1) player4.frame+=0.18;

  for(const t of tiles){ if(!t.solid) continue; ctx.fillStyle="#444"; ctx.fillRect(t.x,t.y,TILE-1,TILE-1); }
  for(const ore of ores){
    if(ore.mined) continue;
    ctx.fillStyle="#2196f3"; ctx.fillRect(ore.x+4,ore.y+4,TILE-8,TILE-8);
    ctx.fillStyle="#90caf9"; ctx.fillRect(ore.x+8,ore.y+8,6,6);
  }
  drawMan(player4.x,player4.y,"#c9c9c9",player4.frame);
  ctx.save();
  ctx.translate(player4.x+32,player4.y+16);
  ctx.rotate(player4.mineCool>15 ? -0.8 :0);
  ctx.fillStyle="#888"; ctx.fillRect(0,-4,26,7);
  ctx.fillStyle="#666"; ctx.fillRect(22,-10,8,16);
  ctx.restore();

  ctx.save();
  ctx.globalCompositeOperation="source-over";
  ctx.fillStyle="rgba(0,0,0,0.75)"; ctx.fillRect(0,0,vp.w,vp.h);
  ctx.globalCompositeOperation="destination-out";
  for(const lt of lights){
    const grad = ctx.createRadialGradient(lt.x,lt.y,0,lt.x,lt.y,lt.radius);
    grad.addColorStop(0,"rgba(0,0,0,1)"); grad.addColorStop(1,"rgba(0,0,0,0)");
    ctx.fillStyle=grad; ctx.beginPath(); ctx.arc(lt.x,lt.y,lt.radius,0,Math.PI*2); ctx.fill();
  }
  const px = player4.x+player4.w/2;
  const py = player4.y+player4.h/2;
  const pGrad = ctx.createRadialGradient(px,py,0,px,py,160);
  pGrad.addColorStop(0,"rgba(0,0,0,1)"); pGrad.addColorStop(1,"rgba(0,0,0,0)");
  ctx.fillStyle=pGrad; ctx.beginPath(); ctx.arc(px,py,160,Math.PI*2); ctx.fill();
  ctx.restore();
}

//====游戏5骑车====
let bike={};
let roadOffset=0;
let pedalPhase=0;
const MAX_SPEED=60;
const ACCEL=0.8;
const BRAKE_POWER=1.2;
const FRICTION=0.15;

function reset5(){
  const vp = getViewport();
  bike={speed:0,x:120,y:vp.h-140};
  roadOffset=0; pedalPhase=0; holdingGas=false; holdingBrake=false;
  scoreDom.textContent="速度:0.0 km/h";
  tipDom.innerText="油门加速|刹车减速,最高60km/h";
}

btnGas.addEventListener("touchstart",e=>{e.preventDefault();initAudio();holdingGas=true;});
btnGas.addEventListener("touchend",e=>{e.preventDefault();holdingGas=false;});
btnGas.addEventListener("touchcancel",e=>{e.preventDefault();holdingGas=false;});

btnBrake.addEventListener("touchstart",e=>{e.preventDefault();initAudio();holdingBrake=true;});
btnBrake.addEventListener("touchend",e=>{e.preventDefault();holdingBrake=false;});
btnBrake.addEventListener("touchcancel",e=>{e.preventDefault();holdingBrake=false;});

function drawFirstPersonHelmet(){
  const vp = getViewport();
  const cx = vp.w/2;
  const cy = vp.h/2;
  ctx.fillStyle="#2c7be5";
  ctx.beginPath(); ctx.ellipse(cx,cy+10,110,130,0,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#111"; ctx.lineWidth=4; ctx.stroke();
  ctx.fillStyle="rgba(140,200,255,0.35)";
  ctx.beginPath(); ctx.ellipse(cx,cy-10,80,70,0,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#111"; ctx.lineWidth=3; ctx.stroke();
  ctx.fillStyle="rgba(255,255,255,0.25)";
  ctx.beginPath(); ctx.ellipse(cx-20,cy-28,28,14,-0.4,0,Math.PI*2); ctx.fill();
  ctx.fillStyle="#fff";
  ctx.beginPath(); ctx.ellipse(cx-30,cy-14,14,10,0,0,Math.PI*2); ctx.ellipse(cx+30,cy-14,14,10,0,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#111"; ctx.lineWidth=2; ctx.stroke();
  ctx.fillStyle="#111";
  ctx.beginPath(); ctx.arc(cx-30,cy-12,4,0,Math.PI*2); ctx.arc(cx+30,cy-12,4,0,Math.PI*2); ctx.fill();
  ctx.fillStyle="#d9a777";
  ctx.beginPath(); ctx.moveTo(cx-6,cy); ctx.lineTo(cx+6,cy); ctx.lineTo(cx,cy+18); ctx.closePath(); ctx.fill();
  ctx.strokeStyle="#111"; ctx.lineWidth=1.5; ctx.stroke();
  ctx.strokeStyle="#111"; ctx.lineWidth=2;
  ctx.beginPath(); ctx.arc(cx,cy+30,10,0,Math.PI); ctx.stroke();
  ctx.strokeStyle="#111"; ctx.lineWidth=3;
  ctx.beginPath(); ctx.moveTo(cx-90,cy+110); ctx.lineTo(cx+90,cy+110); ctx.stroke();
}
function drawPedals(){
  const vp = getViewport();
  const cx = vp.w/2;
  const cy = vp.h;
  const crankLen =42;
  const p1x = cx + Math.cos(pedalPhase)*crankLen;
  const p1y = cy -80 + Math.sin(pedalPhase)*crankLen;
  const p2x = cx + Math.cos(pedalPhase+Math.PI)*crankLen;
  const p2y = cy -80 + Math.sin(pedalPhase+Math.PI)*crankLen;
  ctx.fillStyle="#333";
  ctx.beginPath(); ctx.arc(cx,cy-80,10,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle="#666"; ctx.lineWidth=6;
  ctx.beginPath(); ctx.moveTo(cx,cy-80); ctx.lineTo(p1x,p1y); ctx.moveTo(cx,cy-80); ctx.lineTo(p2x,p2y); ctx.stroke();
  ctx.fillStyle="#222";
  ctx.fillRect(p1x-14,p1y-6,28,12);
  ctx.fillRect(p2x-14,p2y-6,28,12);
  ctx.strokeStyle="#f5c518"; ctx.lineWidth=2;
  ctx.strokeRect(p1x-14,p1y-6,28,12);
  ctx.strokeRect(p2x-14,p2y-6,28,12);
}
function tick5(){
  const vp = getViewport();
  if(holdingGas){ bike.speed += ACCEL; if(bike.speed>MAX_SPEED) bike.speed=MAX_SPEED; }
  if(holdingBrake){ bike.speed -= BRAKE_POWER; if(bike.speed<0) bike.speed=0; }
  bike.speed -= FRICTION; if(bike.speed<0) bike.speed=0;
  roadOffset += bike.speed*1.8; if(roadOffset>80) roadOffset=0;
  pedalPhase += bike.speed*0.012;

  const sky = ctx.createLinearGradient(0,0,0,vp.h*0.55);
  sky.addColorStop(0,"#9fd8ff"); sky.addColorStop(1,"#c9f0ff");
  ctx.fillStyle=sky; ctx.fillRect(0,0,vp.w,vp.h*0.55);

  ctx.fillStyle="#7fa8a8";
  ctx.beginPath();
  ctx.moveTo(0,vp.h*0.5);
  ctx.lineTo(vp.w*0.25,vp.h*0.38);
  ctx.lineTo(vp.w*0.5,vp.h*0.48);
  ctx.lineTo(vp.w*0.75,vp.h*0.36);
  ctx.lineTo(vp.w,vp.h*0.5);
  ctx.lineTo(vp.w,vp.h*0.55);
  ctx.lineTo(0,vp.h*0.55);
  ctx.closePath(); ctx.fill();

  ctx.fillStyle="#2f6b4f";
  ctx.beginPath();
  ctx.moveTo(0,vp.h*0.55);
  ctx.lineTo(vp.w*0.2,vp.h*0.44);
  ctx.lineTo(vp.w*0.4,vp.h*0.52);
  ctx.lineTo(vp.w*0.6,vp.h*0.42);
  ctx.lineTo(vp.w*0.85,vp.h*0.5);
  ctx.lineTo(vp.w,vp.h*0.45);
  ctx.lineTo(vp.w,vp.h*0.58);
  ctx.lineTo(0,vp.h*0.58);
  ctx.closePath(); ctx.fill();

  ctx.fillStyle="#3a993a"; ctx.fillRect(0,vp.h*0.58,vp.w,40);
  ctx.fillStyle="#444444"; ctx.fillRect(0,vp.h*0.62,vp.w,140);
  ctx.strokeStyle="#ffffff"; ctx.lineWidth=4;
  ctx.beginPath(); ctx.moveTo(0,vp.h*0.62); ctx.lineTo(vp.w,vp.h*0.62); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(0,vp.h*0.78); ctx.lineTo(vp.w,vp.h*0.78); ctx.stroke();

  ctx.fillStyle="#ffffff";
  for(let x=-80+roadOffset;x<vp.w+80;x+=80){
    ctx.fillRect(x,vp.h*0.69,40,6);
  }
  drawBackButton();
  drawPedals();
  drawFirstPersonHelmet();

  ctx.fillStyle="rgba(0,0,0,0.5)"; ctx.fillRect(10,10,200,40);
  ctx.fillStyle="#fff"; ctx.font="bold 22px sans-serif";
  ctx.fillText(`速度:${bike.speed.toFixed(1)} km/h`,22,38);
}

function startGame(num){
  mode=num;
  game4UI.style.display="none"; game5UI.style.display="none";
  holdingGas=false; holdingBrake=false;
  if(num===1) reset1();
  if(num===2) reset2();
  if(num===3) reset3();
  if(num===4){game4UI.style.display="block";reset4();}
  if(num===5){game5UI.style.display="block";reset5();}
}
function backToHall(){
  mode=0;
  game4UI.style.display="none"; game5UI.style.display="none";
  holdingGas=false; holdingBrake=false;
  tipDom.innerText="选择下面的游戏开始游玩!";
}

function getCanvasPoint(e){
  const vp = getViewport();
  if(e.changedTouches){
    const t = e.changedTouches[0];
    return {x: t.cli

Game Source: 小游戏合集1‑5 【黑屏终极修复版】

Creator: MysticMaker96

Libraries: none

Complexity: complex (944 lines, 29.7 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: 1-5-mysticmaker96-mw4fo21k" to link back to the original. Then publish at arcadelab.ai/publish.