🎮ArcadeLab

小游戏合集

by MysticMaker96
1197 lines29.5 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>
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui, sans-serif;}
html,body{height:100%;background:#181818;overflow:hidden;}

#hall{
  width:100%;height:100%;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:16px;
}
.title{
  color:#fff;
  font-size:32px;
  margin-bottom:12px;
}
.gbtn{
  width:220px;
  padding:18px 0;
  border:none;
  border-radius:16px;
  font-size:20px;
  color:white;
  cursor:pointer;
}
#g1{background:#2980b9;}
#g2{background:#27ae60;}
#g3{background:#e67e22;}
#g4{background:#9c27b0;}
#g5{background:#d63031;}

#gameWrap{
  display:none;
  width:100%;
  height:100%;
  position:relative;
}
#score{
  position:absolute;
  top:14px;left:14px;
  color:white;
  font-size:22px;
  z-index:10;
}
#tip{
  position:absolute;
  top:50px;left:14px;
  color:#ffd166;
  font-size:16px;
  z-index:10;
}
#back{
  position:absolute;
  top:14px;right:14px;
  padding:10px 16px;
  border:none;
  border-radius:10px;
  background:#c0392b;
  color:white;
  font-size:16px;
  z-index:10;
  cursor:pointer;
}
#canvas{
  display:block;
  background:#222;
  position:absolute;
  top:0;left:0;
  touch-action: none;
}

/* ===== 游戏4 UI:摇杆、跳跃、挖矿按钮 ===== */
#game4-ui{
  display:none;
  position:absolute;
  width:100%;height:100%;
  z-index:5;
  pointer-events:none;
}
.joystick-base{
  position:absolute;
  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;
}
.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:absolute;
  right:30px;bottom:100px;
  width:70px;height:70px;
  border-radius:50%;
  border:none;
  background:#3498db;
  color:white;
  font-size:30px;
  pointer-events:auto;
}
.btn-mine{
  position:absolute;
  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;
}

/* ===== 游戏5 UI:刹车油门 ===== */
#game5-ui{
  display:none;
  position:absolute;
  width:100%;height:100%;
  z-index:5;
  pointer-events:none;
}
.btn-brake{
  position:absolute;
  left:30px;bottom:30px;
  width:100px;height:60px;
  border-radius:12px;
  border:none;
  background:#d33;
  color:white;
  font-size:18px;
  pointer-events:auto;
}
.btn-gas{
  position:absolute;
  right:30px;bottom:30px;
  width:100px;height:60px;
  border-radius:12px;
  border:none;
  background:#2a8;
  color:white;
  font-size:18px;
  pointer-events:auto;
}
</style>
</head>
<body>

<div id="hall">
  <div class="title">小游戏合集</div>
  <button class="gbtn" id="g1">游戏1:跳跃躲尖刺</button>
  <button class="gbtn" id="g2">飞碟身法挑战</button>
  <button class="gbtn" id="g3">蓝小人射红小人</button>
  <button class="gbtn" id="g4">游戏4:洞穴挖矿探险</button>
  <button class="gbtn" id="g5">游戏5:公路骑车狂奔</button>
</div>

<div id="gameWrap">
  <div id="score">分数:0</div>
  <div id="tip">按住向后拖动弹弓,松开发射蓝色小人!</div>
  <button id="back">返回大厅</button>
  <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>
document.addEventListener('DOMContentLoaded', function(){
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const hall = document.getElementById('hall');
const gameWrap = document.getElementById('gameWrap');
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');

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

function initAudio(){
  if(audioCtx) return;
  audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
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 resize(){
  canvas.width = Math.min(window.innerWidth, 480);
  canvas.height = Math.min(window.innerHeight, 720);
}
resize();
window.addEventListener('resize', resize);

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){
  return a.x < b.x + 36 && a.x + 36 > b.x && a.y < b.y + 36 && a.y + 36 > b.y;
}

// ===== 游戏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(){
  p1.y = 300; 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 h = 40 + Math.random() * 70;
  spikes1.push({
    x: canvas.width + 10,
    y: canvas.height - h,
    w:28, h:h
  })
}
function tick1(){
  const groundY = canvas.height - 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,canvas.width,canvas.height);
  ctx.fillStyle="#555";
  ctx.fillRect(0, groundY, canvas.width, 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("太棒了!", canvas.width / 2, canvas.height / 2 - 60);
    greatTimer--;
    if(greatTimer <= 0){
      showGreatText = false;
    }
  }

  if(gameOver){
    ctx.fillStyle="#fff";
    ctx.font="bold 28px sans-serif";
    ctx.textAlign="center";
    ctx.fillText("游戏结束!点击画布重开", canvas.width/2, canvas.height/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(){
  saucer.y = canvas.height / 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 topSide = Math.random() > 0.5;
  const w = 30 + Math.random() * 40;
  if(topSide){
    spikes2.push({x:canvas.width+10, y:0, w:w, h:50+Math.random()*80, top:true});
  }else{
    const h = 50 + Math.random()*80;
    spikes2.push({x:canvas.width+10, y:canvas.height - 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(){
  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,canvas.width,canvas.height);
  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("游戏结束!点击画布重开", canvas.width/2, canvas.height/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 margin = 60;
  const ex = margin + Math.random() * (canvas.width - margin*2 - 40);
  const ey = margin + Math.random() * (canvas.height - 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
  })
}

function tick3(){
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.fillStyle="#2c2c2c";
  ctx.fillRect(0, canvas.height-40, canvas.width,40);

  const ox = 80;
  const oy = canvas.height - 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 > canvas.width + 20 || m.y > canvas.height + 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 > canvas.width - bound - 36){
      e.vx *= -1;
      e.x = Math.max(bound, Math.min(canvas.width-bound-36, e.x));
    }
    if(e.y < bound || e.y > canvas.height - 80){
      e.vy *= -1;
      e.y = Math.max(bound, Math.min(canvas.height - 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;
  }
}

// === 游戏3输入 ===
function getPos(e){
  const t = e.touches ? e.touches[0] : e;
  return {x: t.clientX, y: t.clientY};
}
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('touchstart', dragStart);
canvas.addEventListener('mousemove', dragMove);
canvas.addEventListener('touchmove', dragMove);
canvas.addEventListener('mouseup', dragEnd);
canvas.addEventListener('touchend', dragEnd);

function dragStart(e){
  if(mode !== 3) return;
  e.preventDefault();
  initAudio();
  const p = getPos(e);
  slingshot.active = true;
  slingshot.pullX = p.x;
  slingshot.pullY = p.y;
}
function dragMove(e){
  if(mode !==3 || !slingshot.active) return;
  e.preventDefault();
  const p = getPos(e);
  slingshot.pullX = p.x;
  slingshot.pullY = p.y;
}
function dragEnd(e){
  if(mode !==3 || !slingshot.active) return;
  e.preventDefault();
  const ox = 80;
  const oy = canvas.height - 80;
  const dx = ox - slingshot.pullX;
  const dy = (oy - 35) - slingshot.pullY;
  const power = Math.min(14, Math.sqrt(dx*dx + dy*dy)/9);
  if(power > 1.5){
    flyMen.push({
      x: ox - 18,
      y: oy - 40,
      vx: dx / 12 * power,
      vy: dy / 12 * power,
      frame:0
    })
    playSlingshotSound();
  }
  slingshot.active = false;
}

canvas.onclick = function(){
  if(mode === 1){
    if(gameOver) reset1();
    else jump();
  }else if(mode ===2){
    if(gameOver) reset2();
  }
}
canvas.addEventListener('touchmove', function(e){
  if(mode === 2){
    const p = getPos(e);
    saucer.targetY = p.y;
  }
}, {passive:false})
canvas.addEventListener('mousemove', function(e){
  if(mode ===2){
    const p = getPos(e);
    saucer.targetY = p.y;
  }
})

// ====================== 游戏4:洞穴挖矿探险 ======================
const joyBase = document.getElementById("joyBase");
const joyKnob = document.getElementById("joyKnob");
const btnJump = document.getElementById("btnJump");
const btnMine = document.getElementById("btnMine");

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(){
  tiles = [];
  ores = [];
  lights = [];
  const w = Math.ceil(canvas.width / TILE);
  const h = Math.ceil(canvas.height / 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()*(canvas.width-160),
      y: 100 + Math.random()*(canvas.height-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 idx = ty * Math.ceil(canvas.width/TILE) + tx;
  if(tiles[idx]) return tiles[idx];
  return {solid:false};
}
function collideBox(x,y,w,h){
  const p = [
    {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 pt of p){
    const t = tileAt(pt.x, pt.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 tidx = ty * Math.ceil(canvas.width/TILE) + tx;
      if(tiles[tidx]) tiles[tidx].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;
  let dx = e.touches[0].clientX - cx;
  let dy = e.touches[0].clientY - cy;
  const r = 40;
  const len = Math.sqrt(dx*dx+dy*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=>{
  joy.active = false;
  joy.dx = 0; joy.dy = 0;
  joyKnob.style.left = "32px";
  joyKnob.style.top = "32px";
})
btnJump.onclick = ()=>{
  if(mode!==4) return;
  initAudio();
  if(player4.onGround){
    player4.vy = player4.jumpPower;
    player4.onGround = false;
    playJumpSound();
  }
}
btnMine.onclick = ()=>{
  if(mode!==4) return;
  initAudio();
  mineAction();
}

function tick4(){
  ctx.fillStyle="#0c0c0c";
  ctx.fillRect(0,0,canvas.width,canvas.height);

  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,canvas.width,canvas.height);

  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, 0, Math.PI*2);
  ctx.fill();
  ctx.restore();
}

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

function reset5(){
  bike = {
    speed: 0,
    x: 120,
    y: 360
  };
  roadOffset = 0;
  scoreDom.textContent = "速度:0.0 km/h";
  tipDom.innerText = "油门加速|刹车减速,最高60km/h";
}
function tick5(){
  bike.speed -= FRICTION;
  if(bike.speed < 0) bike.speed = 0;
  roadOffset += bike.speed * 1.8;
  if(roadOffset > 80) roadOffset = 0;

  ctx.clearRect(0,0,canvas.width,canvas.height);
  // 天空
  ctx.fillStyle="#87ceeb";
  ctx.fillRect(0,0,canvas.width,canvas.height*0.55);
  // 草地
  ctx.fillStyle="#3a993a";
  ctx.fillRect(0,canvas.height*0.55,canvas.width,60);
  // 公路
  ctx.fillStyle="#444444";
  ctx.fillRect(0,canvas.height*0.62,canvas.width,140);
  // 道路虚线
  ctx.fillStyle="#ffffff";
  for(let x = -80 + roadOffset; x < canvas.width + 80; x += 80){
    ctx.fillRect(x, canvas.height*0.69, 40, 6);
  }

  const cx = bike.x;
  const cy = bike.y;
  // 蓝色小人
  ctx.fillStyle="#3388ff";
  ctx.beginPath();
  ctx.arc(cx, cy - 22, 12, 0, Math.PI*2);
  ctx.fill();
  ctx.fillRect(cx - 8, cy - 10, 16, 24);
  ctx.fillRect(cx -10, cy + 12, 7, 14);
  ctx.fillRect(cx +3, cy +12, 7, 14);

  // 自行车
  ctx.strokeStyle="#222";
  ctx.lineWidth=3;
  ctx.beginPath();
  ctx.arc(cx - 24, cy + 22, 16, 0, Math.PI*2);
  ctx.stroke();
  ctx.beginPath();
  ctx.arc(cx + 24, cy + 22, 16, 0, Math.PI*2);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(cx-24, cy+22);
  ctx.lineTo(cx+24, cy+22);
  ctx.lineTo(cx, cy);
  ctx.lineTo(cx-24, cy+22);
  ctx.stroke();

  ctx.fillStyle="#fff";
  ctx.font="bold 22px sans-serif";
  ctx.fillText(`速度:${bike.speed.toFixed(1)} km/h`, 15, 32);
}
btnGas.onmousedown = ()=>{
  if(mode === 5){
    bike.speed += ACCEL;
    if(bike.speed > MAX_SPEED) bike.speed = MAX_SPEED;
  }
};
btnGas.ontouchstart = btnGas.onmousedown;

btnBrake.onmousedown = ()=>{
  if(mode === 5){
    bike.speed -= BRAKE_POWER;
    if(bike.speed < 0) bike.speed = 0;
  }
};
btnBrake.ontouchstart = btnBrake.onmousedown;

// ===== 大厅按钮 =====
document.getElementById('g1').onclick = function(){
  initAudio();
  hall.style.display = "none";
  gameWrap.style.display = "block";
  game4UI.style.display = "none";
  game5UI.style.display = "none";
  mode = 1;
  reset1();
}
document.getElementById('g2').onclick = function(){
  initAudio();
  hall.style.display = "none";
  gameWrap.style.display = "block";
  game4UI.style.display = "none";
  game5UI.style.display = "none";
  mode = 2;
  reset2();
}
document.getElementById('g3').onclick = function(){
  initAudio();
  hall.style.display = "none";
  gameWrap.style.display = "block";
  game4UI.style.display = "none";
  game5UI.style.display = "none";
  mode = 3;
  reset3();
}
document.getElementById('g4').onclick = function(){
  initAudio();
  hall.style.display = "none";
  gameWrap.style.display = "block";
  game4UI.style.display = "block";
  game5UI.style.display = "none";
  mode = 4;
  reset4();
}
document.getElementById('g5').onclick = function(){
  initAudio();
  hall.style.display = "none";
  gameWrap.style.display = "block";
  game4UI.style.display = "none";
  game5UI.style.display = "block";
  mode = 5;
  reset5();
}
document.getElementById('back').onclick = function(){
  hall.style.display = "flex";
  gameWrap.style.display = "none";
  game4UI.style.display = "none";
  game5UI.style.display = "none";
  mode = 0;
}

//主循环
function loop(){
  if(mode === 1) tick1();
  else if(mode === 2) tick2();
  else if(mode === 3) tick3();
  else if(mode === 4) tick4();
  else if(mode === 5) tick5();
  requestAnimationFrame(loop);
}
loop();
})
</script>
</body>
</html>

Game Source: 小游戏合集

Creator: MysticMaker96

Libraries: none

Complexity: complex (1197 lines, 29.5 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-mysticmaker96-mw4k3lvm" to link back to the original. Then publish at arcadelab.ai/publish.