小游戏合集
by MysticMaker961197 lines29.5 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"/>
<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-mw4h515w" to link back to the original. Then publish at arcadelab.ai/publish.