溶洞像素跳跃
by MysticMaker96369 lines8.7 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>溶洞像素跳跃</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#181412;color:#fff;font-family:monospace;overflow:hidden}
canvas{display:block;margin:0 auto;background:#2c2420;image-rendering:pixelated;}
.info{position:absolute;top:8px;left:12px;font-size:18px;text-shadow:1px 1px #000}
</style>
</head>
<body>
<div class="info">
💰金币:<span id="coinCount">0</span> ⭐分数:<span id="score">0</span>
</div>
<canvas id="game" width="480" height="320"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const coinDom = document.getElementById('coinCount');
const scoreDom = document.getElementById('score');
function drawPixel(x,y,w,h,color){
ctx.fillStyle = color;
ctx.fillRect(Math.floor(x),Math.floor(y),w,h);
}
// 玩家:紫色像素小人,结构更清楚
const player = {
x: 60,
y: 200,
w: 14,
h: 22,
vx: 2.6,
vy: 0,
jumpPower: -16,
gravity: 0.48,
onGround: false,
pixelSize: 3,
runPhase: 0,
flip: 1,
coinNum: 0,
score: 0
};
const groundY = 260;
let spikes = [];
let coins = [];
// 尖刺:加高,保证高于小人
function spawnSpike(){
const h = 44 + Math.random() * 16;
spikes.push({
x: canvas.width,
y: groundY - h,
w: 14,
h: h,
scored: false
});
}
// 普通金币
function spawnCoin(){
coins.push({
x: canvas.width,
y: groundY - 36 - Math.random()*55,
r: 5,
collected: false
});
}
// 大片金币区域
function spawnCoinWave(){
const count = 5 + Math.floor(Math.random()*4);
const startY = groundY - 40 - Math.random()*40;
for(let i=0;i<count;i++){
coins.push({
x: canvas.width + i*22,
y: startY + Math.sin(i*0.6)*10,
r: 5,
collected: false
});
}
}
// 点击画布任意位置跳跃
canvas.addEventListener('click', ()=>{
if(player.onGround){
player.vy = player.jumpPower;
player.onGround = false;
}
});
canvas.addEventListener('touchstart',e=>{
e.preventDefault();
if(player.onGround){
player.vy = player.jumpPower;
player.onGround = false;
}
});
let spawnTimer = 0;
const spawnRate = 110;
const moveSpeed = 3;
let coinWaveTimer = 0;
const coinWaveRate = 320;
function resetGame(){
player.x = 60;
player.y = 200;
player.vy = 0;
player.onGround = false;
player.coinNum = 0;
player.score = 0;
player.runPhase = 0;
spikes = [];
coins = [];
spawnTimer = 0;
coinWaveTimer = 0;
updateUI();
}
function updateUI(){
coinDom.textContent = player.coinNum;
scoreDom.textContent = Math.floor(player.score);
}
function rectOverlap(a,b){
return a.x < b.x + b.w && a.x+a.w > b.x && a.y < b.y + b.h && a.y+a.h > b.y;
}
function circleRect(c, r, rect){
let cx = Math.max(rect.x, Math.min(c.x, rect.x+rect.w));
let cy = Math.max(rect.y, Math.min(c.y, rect.y+rect.h));
let dx = c.x - cx;
let dy = c.y - cy;
return dx*dx + dy*dy < r*r;
}
function update(){
spawnTimer++;
coinWaveTimer++;
if(spawnTimer >= spawnRate){
spawnTimer = 0;
if(Math.random()<0.8) spawnSpike();
if(Math.random()<0.28) spawnCoin();
}
if(coinWaveTimer >= coinWaveRate){
coinWaveTimer = 0;
spawnCoinWave();
}
player.vy += player.gravity;
player.y += player.vy;
if(player.y + player.h*player.pixelSize >= groundY){
player.y = groundY - player.h*player.pixelSize;
player.vy = 0;
player.onGround = true;
}else{
player.onGround = false;
}
// 跑步相位只在地面累积
if(player.onGround){
player.runPhase += 0.35;
}
// 移动尖刺和金币
for(let i=spikes.length-1;i>=0;i--){
spikes[i].x -= moveSpeed;
if(spikes[i].x + spikes[i].w < 0) spikes.splice(i,1);
}
for(let i=coins.length-1;i>=0;i--){
coins[i].x -= moveSpeed;
if(coins[i].x + coins[i].r < 0) coins.splice(i,1);
}
// 碰撞尖刺
const playerBox = {
x: player.x+2,
y: player.y+2,
w: player.w*player.pixelSize-4,
h: player.h*player.pixelSize-4
};
for(let s of spikes){
if(rectOverlap(playerBox, s)){
resetGame();
return;
}
if(!s.scored && s.x + s.w/2 < player.x){
s.scored = true;
player.score += 1;
updateUI();
}
}
// 吃金币
for(let i=coins.length-1;i>=0;i--){
const c = coins[i];
if(!c.collected && circleRect(c, c.r, playerBox)){
c.collected = true;
player.coinNum++;
player.score += 10;
coins.splice(i,1);
updateUI();
}
}
player.score += 0.02;
}
function drawPlayer(){
const px = player.pixelSize;
const cx = player.x + player.w*px/2;
const cy = player.y;
ctx.save();
ctx.translate(cx, cy);
ctx.scale(player.flip,1);
ctx.translate(-player.w*px/2,0);
let leftLegAngle, rightLegAngle;
let leftArmAngle, rightArmAngle;
if(player.onGround){
leftLegAngle = Math.sin(player.runPhase) * 0.35;
rightLegAngle = Math.sin(player.runPhase + Math.PI) * 0.35;
leftArmAngle = Math.sin(player.runPhase + Math.PI) * 0.3;
rightArmAngle = Math.sin(player.runPhase) * 0.3;
}else{
const t = Math.min(1, -player.vy / 16);
leftLegAngle = -0.4 + t * 0.2;
rightLegAngle = -0.4 + t * 0.2;
leftArmAngle = 0.9 - t * 0.3;
rightArmAngle = 0.9 - t * 0.3;
}
// 头
drawPixel(4*px, 0, 6*px, 4*px, '#ffd0b0');
drawPixel(5*px, 1*px, 2*px, 1*px, '#000000');
drawPixel(7*px, 1*px, 2*px, 1*px, '#000000');
drawPixel(4*px, 2*px, 1*px, 1*px, '#ffffff');
drawPixel(9*px, 2*px, 1*px, 1*px, '#ffffff');
// 身子
drawPixel(4*px, 4*px, 6*px, 5*px, '#9b59b6');
drawPixel(4*px, 7*px, 6*px, 2*px, '#8e44ad');
drawPixel(5*px, 5*px, 2*px, 1*px, '#b066ff');
// 左腿
ctx.save();
ctx.translate(5*px, 9*px);
ctx.rotate(leftLegAngle);
drawPixel(0, 0, 2*px, 4*px, '#7d3fbf');
ctx.restore();
// 右腿
ctx.save();
ctx.translate(7*px, 9*px);
ctx.rotate(rightLegAngle);
drawPixel(0, 0, 2*px, 4*px, '#7d3fbf');
ctx.restore();
// 左臂
ctx.save();
ctx.translate(3*px, 5*px);
ctx.rotate(leftArmAngle);
drawPixel(0, 0, 2*px, 3*px, '#8e44ad');
ctx.restore();
// 右臂
ctx.save();
ctx.translate(10*px, 5*px);
ctx.rotate(rightArmAngle);
drawPixel(0, 0, 2*px, 3*px, '#8e44ad');
ctx.restore();
ctx.restore();
// ===== 筋斗云,画在小人脚下 =====
const feetY = player.y + player.h * px;
const cloudWobble = Math.sin(player.runPhase * 0.6) * 1.2;
const cloudX = cx;
const cloudY = feetY + 2;
ctx.fillStyle = '#ffffff';
// 云朵轮廓
drawPixel(cloudX - 21, cloudY + cloudWobble, 10, 5, '#fff');
drawPixel(cloudX - 12, cloudY - 2 + cloudWobble, 24, 7, '#fff');
drawPixel(cloudX + 9, cloudY + cloudWobble, 10, 5, '#fff');
// 浅蓝光晕
ctx.fillStyle = 'rgba(200, 230, 255, 0.45)';
drawPixel(cloudX - 16, cloudY + 4 + cloudWobble, 32, 3, '#c8e6ff');
// 跑动尾迹
if (player.onGround) {
ctx.fillStyle = 'rgba(255,255,255,0.35)';
drawPixel(cloudX - 30, cloudY + 2 + cloudWobble, 5, 2, '#fff');
drawPixel(cloudX - 36, cloudY + 4 + cloudWobble, 3, 2, '#fff');
}
}
function drawSpikes(){
for(let s of spikes){
ctx.fillStyle='#70482c';
ctx.fillRect(s.x, s.y, s.w, s.h);
ctx.beginPath();
ctx.moveTo(s.x, s.y);
ctx.lineTo(s.x + s.w/2, s.y - 10);
ctx.lineTo(s.x + s.w, s.y);
ctx.fill();
ctx.fillStyle='#543420';
ctx.fillRect(s.x+2, s.y+4, 3, s.h-4);
ctx.fillRect(s.x+8, s.y+10, 3, s.h-10);
}
}
function drawCoins(){
for(let c of coins){
ctx.fillStyle='#ffd700';
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle='#ffb800';
ctx.beginPath();
ctx.arc(c.x-1, c.y-1, c.r*0.45, 0, Math.PI*2);
ctx.fill();
}
}
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle='#4b382b';
ctx.fillRect(0, groundY, canvas.width, canvas.height-groundY);
ctx.fillStyle='#3a2a20';
for(let i=0;i<canvas.width;i+=16){
ctx.fillRect(i, groundY, 4, 4);
}
drawSpikes();
drawCoins();
drawPlayer();
}
function loop(){
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
Game Source: 溶洞像素跳跃
Creator: MysticMaker96
Libraries: none
Complexity: complex (369 lines, 8.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: game-mysticmaker96-mw4uefdt" to link back to the original. Then publish at arcadelab.ai/publish.