小游戏合集1‑5 【黑屏终极修复版】
by MysticMaker96944 lines29.7 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, 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.cliGame 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.