🎮ArcadeLab

小游戏合集【微信触摸终极修复】

by MysticMaker96
1178 lines30.0 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;
  -webkit-tap-highlight-color: transparent;
}
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;
  touch-action: manipulation;
  position:relative;
  z-index:9999;
  pointer-events:auto !important;
  cursor: pointer !important;
}
#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:999;
  pointer-events:none;
}
#tip{
  position:absolute;
  top:50px;left:14px;
  color:#ffd166;
  font-size:16px;
  z-index:999;
  pointer-events:none;
}
#back{
  position:absolute;
  top:14px;right:14px;
  padding:10px 16px;
  border:none;
  border-radius:10px;
  background:#c0392b;
  color:white;
  font-size:16px;
  z-index:9999;
  cursor:pointer;
  touch-action: manipulation;
  pointer-events:auto !important;
  cursor: pointer !important;
}
#canvas{
  display:block;
  background:#222;
  position:absolute;
  top:0;left:0;
  z-index:1;
  touch-action: none;
  transform: translate3d(0,0,0);
  -webkit-transform: translate3d(0,0,0);
}

/* ===== 游戏4 UI:摇杆、跳跃、挖矿按钮 ===== */
#game4-ui{
  display:none;
  position:absolute;
  inset:0;
  z-index:9998;
  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 !important;
  touch-action: none;
  z-index:9999;
}
.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 !important;
  touch-action: manipulation;
  z-index:9999;
  cursor: pointer !important;
}
.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 !important;
  touch-action: manipulation;
  z-index:9999;
  cursor: pointer !important;
}

/* ===== 游戏5 UI:刹车油门 ===== */
#game5-ui{
  display:none;
  position:absolute;
  inset:0;
  z-index:9998;
  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 !important;
  touch-action: manipulation;
  z-index:9999;
  cursor: pointer !important;
}
.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 !important;
  touch-action: manipulation;
  z-index:9999;
  cursor: pointer !important;
}
</style>
</head>
<body>

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

<div id="gameWrap">
  <div id="score">分数:0</div>
  <div id="tip">按住向后拖动弹弓,松开发射蓝色小人!</div>
  <button id="back" onclick="">返回大厅</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" onclick="">↑</button>
    <button class="btn-mine" id="btnMine" onclick="">挖矿</button>
  </div>

  <div id="game5-ui">
    <button class="btn-brake" id="btnBrake" onclick="">🛑 刹车</button>
    <button class="btn-gas" id="btnGas" onclick="">🚀 油门</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');
const btnJump = document.getElementById('btnJump');
const btnMine = document.getElementById('btnMine');
const joyBase = document.getElementById("joyBase");
const joyKnob = document.getElementById("joyKnob");
const backBtn = document.getElementById("back");

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;

// ========== 纯净 Tap 封装(微信最稳方案,只用 touch) ==========
function makeTap(el, callback) {
  let startX, startY, startTime;
  const MOVE_THRESHOLD = 12;
  let moved = false;

  el.addEventListener("touchstart", (e) => {
    startX = e.touches[0].clientX;
    startY = e.touches[0].clientY;
    startTime = Date.now();
    moved = false;
  }, { passive:true });

  el.addEventListener("touchmove", (e) => {
    const dx = e.touches[0].clientX - startX;
    const dy = e.touches[0].clientY - startY;
    if (Math.hypot(dx, dy) > MOVE_THRESHOLD) moved = true;
  }, { passive:true });

  el.addEventListener("touchend", (e) => {
    if (moved) return;
    if (Date.now() - startTime > 250) return;
    initAudio();
    callback();
  }, { passive:true });

  // 兜底 click,给电脑 / 备用
  el.addEventListener("click", ()=>{
    initAudio();
    callback();
  });
}

// 按住持续型(油门、刹车)
function makeHold(el, onDown, onUp) {
  let pressed = false;
  el.addEventListener("touchstart", ()=>{ pressed=true; initAudio(); onDown(); }, {passive:true});
  el.addEventListener("touchend", ()=>{ pressed=false; onUp(); }, {passive:true});
  el.addEventListener("touchcancel", ()=>{ pressed=false; onUp(); }, {passive:true});
  el.addEventListener("mousedown", ()=>{ pressed=true; initAudio(); onDown(); });
  el.addEventListener("mouseup", ()=>{ pressed=false; onUp(); });
}

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画布拖拽(纯touch) ===
function getTouchPos(e){
  const t = e.touches ? e.touches[0] : e;
  return {x: t.clientX, y: t.clientY};
}
function dragStart(e){
  if(mode !== 3) return;
  e.preventDefault();
  initAudio();
  const p = getTouchPos(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 = getTouchPos(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.addEventListener('touchstart', dragStart, {passive:false});
canvas.addEventListener('touchmove', dragMove, {passive:false});
canvas.addEventListener('touchend', dragEnd, {passive:false});
canvas.addEventListener('touchcancel', dragEnd, {passive:false});

// 画布点击(游戏1点击、游戏2拖动)
function canvasTapStart(e){
  if(mode === 1){
    initAudio();
    if(gameOver) reset1();
    else jump();
  }
}
function canvasTrackMove(e){
  if(mode === 2){
    e.preventDefault();
    const p = getTouchPos(e);
    saucer.targetY = p.y;
  }
}
canvas.addEventListener('touchend', canvasTapStart, {passive:true});
canvas.addEventListener('touchmove', canvasTrackMove, {passive:false});

// ====================== 游戏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(){
  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;
    }
  }
}

// 摇杆纯touch
joyBase.addEventListener("touchstart", e=>{ e.preventDefault(); joy.active=true; }, {passive:false});
joyBase.addEventListener("touchmove", e=>{
  if(!joy.active) return; e.preventDefault();
  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.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;
},{passive:false});
joyBase.addEventListener("touchend", e=>{
  joy.active = false; joy.dx = 0; joy.dy = 0;
  joyKnob.style.left = "32px"; joyKnob.style.top = "32px";
},{passive:true});
joyBase.addEventListener("touchcancel", e=>{
  joy.active = false; joy.dx = 0; joy.dy = 0;
  joyKnob.style.left = "32px"; joyKnob.style.top = "32px";
},{passive:true});

makeTap(btnJump, ()=>{
  if(mode!==4) return;
  if(player4.onGround){
    player4.vy = player4.jumpPower;
    player4.onGround = false;
    playJumpSound();
  }
});
makeTap(btnMine, ()=>{
  if(mode!==4) return;
  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;
let pedalPhase = 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;
  pedalPhase = 0;
  holdingGas = false;
  holdingBrake = false;
  scoreDom.textContent = "速度:0.0 km/h";
  tipDom.innerText = "油门加速|刹车减速,最高60km/h";
}

function drawFirstPersonHelmet(){
  const cx = canvas.width / 2;
  const cy = canvas.height / 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

Game Source: 小游戏合集【微信触摸终极修复】

Creator: MysticMaker96

Libraries: none

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