横屏跳跃大冒险
by RapidWolf741037 lines40.1 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>横屏跳跃大冒险</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: linear-gradient(180deg, #87CEEB 0%, #E0F6FF 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#gameCanvas {
display: block;
image-rendering: pixelated;
touch-action: none;
user-select: none;
}
.game-ui {
position: absolute;
pointer-events: none;
user-select: none;
}
.btn {
pointer-events: auto;
cursor: pointer;
transition: all 0.2s ease;
}
.btn:hover {
transform: scale(1.05);
}
.btn:active {
transform: scale(0.95);
}
.modal {
pointer-events: auto;
}
input {
pointer-events: auto;
outline: none;
}
</style>
</head>
<body class="flex items-center justify-center min-h-screen">
<div id="gameContainer" class="relative">
<canvas id="gameCanvas"></canvas>
<!-- 分数显示 -->
<div class="game-ui top-4 left-4 text-white font-bold text-2xl drop-shadow-lg">
分数: <span id="scoreDisplay">0</span>
</div>
<!-- 最高分 -->
<div class="game-ui top-4 right-4 text-white font-bold text-lg drop-shadow-lg">
最高分: <span id="highScoreDisplay">0</span>
</div>
<!-- 开始界面 -->
<div id="startScreen" class="game-ui inset-0 flex flex-col items-center justify-center bg-black/40 backdrop-blur-sm">
<h1 class="text-5xl font-bold text-white mb-4 drop-shadow-lg">🏃 横屏跳跃大冒险</h1>
<div class="text-white text-lg mb-6 text-center space-y-1">
<p>🟡 收集金币获得额外积分</p>
<p>⭐ 吃到星星获得无敌状态</p>
<p>500分后难度提升,挑战你的极限!</p>
</div>
<div class="flex flex-col gap-4 items-center">
<button id="startBtn" class="btn px-10 py-4 bg-gradient-to-r from-green-400 to-green-600 text-white text-xl font-bold rounded-full shadow-lg">
开始游戏
</button>
<button id="showRankBtn" class="btn px-8 py-3 bg-gradient-to-r from-yellow-400 to-orange-500 text-white font-bold rounded-full shadow-lg">
🏆 查看排行榜
</button>
</div>
<p class="text-white/80 mt-6 text-sm">按 空格键 或 点击屏幕 跳跃</p>
</div>
<!-- 游戏结束界面 -->
<div id="gameOverScreen" class="game-ui inset-0 flex flex-col items-center justify-center bg-black/50 backdrop-blur-sm hidden">
<h2 class="text-4xl font-bold text-red-400 mb-4 drop-shadow-lg">游戏结束</h2>
<p class="text-white text-2xl mb-2">本次得分: <span id="finalScore">0</span></p>
<p class="text-yellow-300 text-lg mb-6">最高分: <span id="finalHighScore">0</span></p>
<!-- 上榜输入区 -->
<div id="rankInputArea" class="mb-6 flex flex-col items-center hidden">
<p class="text-yellow-300 font-bold mb-2">🎉 恭喜上榜!请输入你的名字</p>
<input type="text" id="playerNameInput" maxlength="10" placeholder="输入昵称"
class="px-4 py-2 rounded-lg text-center mb-3 w-48 text-gray-800">
<button id="saveRankBtn" class="btn px-6 py-2 bg-yellow-500 text-white font-bold rounded-full shadow">
保存成绩
</button>
</div>
<div class="flex gap-4">
<button id="restartBtn" class="btn px-8 py-4 bg-gradient-to-r from-blue-400 to-blue-600 text-white text-xl font-bold rounded-full shadow-lg">
再来一局
</button>
<button id="gameOverRankBtn" class="btn px-6 py-4 bg-gradient-to-r from-yellow-400 to-orange-500 text-white font-bold rounded-full shadow-lg">
排行榜
</button>
</div>
</div>
<!-- 排行榜弹窗 -->
<div id="rankModal" class="game-ui inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm hidden">
<div class="modal bg-white/95 rounded-2xl p-6 w-80 shadow-2xl">
<h3 class="text-2xl font-bold text-center text-gray-800 mb-4">🏆 排行榜 TOP 10</h3>
<div id="rankList" class="space-y-2 max-h-80 overflow-y-auto mb-4">
<!-- 排行榜内容动态生成 -->
</div>
<button id="closeRankBtn" class="btn w-full py-3 bg-gray-600 text-white font-bold rounded-full">
关闭
</button>
</div>
</div>
</div>
<script>
// ===================== 游戏配置 =====================
const CONFIG = {
canvasWidth: 900,
canvasHeight: 400,
gravity: 0.6,
jumpForce: -13,
groundHeight: 60,
baseSpeed: 5,
// 难度阶段配置
phase1MaxScore: 500, // 第一阶段上限分数
phase1MaxSpeed: 10, // 第一阶段最大速度
phase1SpeedIncrement: 0.0008, // 第一阶段速度增速
phase2MaxSpeed: 13, // 第二阶段最大速度
phase2SpeedIncrement: 0.0012, // 第二阶段速度增速
phase2SpeedBoost: 0.8, // 进入第二阶段时的即时速度加成
phase2GapScale: 0.85, // 第二阶段障碍物间距缩放系数
// 障碍物时间间隔(第一阶段基准值)
obstacleTimeGap: {
tight: [0.85, 1.15],
normal: [1.2, 1.7],
loose: [1.8, 2.6],
extraLoose: [3.0, 4.0]
},
maxConsecutiveTight: 2,
coinSpawnChance: 0.01,
starSpawnChance: 0.0016,
invincibleDuration: 300,
coinScore: 20,
invincibleCrashScore: 5
};
const STORAGE_KEYS = {
highScore: 'jumpGameHighScore',
leaderboard: 'jumpGameLeaderboard'
};
// ===================== 工具函数 =====================
function randomRange(min, max) {
return min + Math.random() * (max - min);
}
// ===================== 音效管理器 =====================
const AudioManager = {
ctx: null,
bgmOscillators: [],
bgmTimer: null,
isPlaying: false,
bgmVolume: 0.15,
sfxVolume: 0.3,
init() {
if (this.ctx) return;
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
},
resume() {
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
},
playJump() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(300, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(this.sfxVolume, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.15);
},
playScore() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, this.ctx.currentTime);
gain.gain.setValueAtTime(this.sfxVolume * 0.8, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.1);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.1);
},
playCoin() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(800, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1200, this.ctx.currentTime + 0.08);
gain.gain.setValueAtTime(this.sfxVolume, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.12);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.12);
},
playInvincible() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(400, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.1);
osc.frequency.exponentialRampToValueAtTime(1000, this.ctx.currentTime + 0.2);
gain.gain.setValueAtTime(this.sfxVolume, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.3);
},
playLevelUp() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(440, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(660, this.ctx.currentTime + 0.15);
osc.frequency.exponentialRampToValueAtTime(880, this.ctx.currentTime + 0.3);
gain.gain.setValueAtTime(this.sfxVolume * 0.9, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.4);
},
playGameOver() {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(400, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, this.ctx.currentTime + 0.4);
gain.gain.setValueAtTime(this.sfxVolume, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.4);
},
startBGM() {
if (!this.ctx || this.isPlaying) return;
this.isPlaying = true;
const notes = [261.63, 329.63, 392.00, 329.63, 261.63, 329.63, 392.00, 523.25];
let noteIndex = 0;
const noteDuration = 0.25;
const playNextNote = () => {
if (!this.isPlaying) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'square';
osc.frequency.value = notes[noteIndex];
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(this.bgmVolume, this.ctx.currentTime + 0.02);
gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + noteDuration - 0.02);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + noteDuration);
this.bgmOscillators.push(osc);
noteIndex = (noteIndex + 1) % notes.length;
this.bgmTimer = setTimeout(playNextNote, noteDuration * 1000);
};
playNextNote();
},
stopBGM() {
this.isPlaying = false;
if (this.bgmTimer) {
clearTimeout(this.bgmTimer);
this.bgmTimer = null;
}
this.bgmOscillators.forEach(osc => {
try { osc.stop(); } catch(e) {}
});
this.bgmOscillators = [];
}
};
// ===================== 排行榜管理器 =====================
const Leaderboard = {
getList() {
const data = localStorage.getItem(STORAGE_KEYS.leaderboard);
return data ? JSON.parse(data) : [];
},
saveList(list) {
localStorage.setItem(STORAGE_KEYS.leaderboard, JSON.stringify(list));
},
canRank(score) {
const list = this.getList();
if (list.length < 10) return true;
return score > list[list.length - 1].score;
},
addScore(name, score) {
const list = this.getList();
list.push({ name: name || '匿名玩家', score, time: Date.now() });
list.sort((a, b) => b.score - a.score);
if (list.length > 10) list.length = 10;
this.saveList(list);
return list;
},
render(containerId) {
const container = document.getElementById(containerId);
const list = this.getList();
if (list.length === 0) {
container.innerHTML = '<p class="text-center text-gray-500 py-8">暂无记录,快去挑战吧!</p>';
return;
}
const medalColors = ['text-yellow-500', 'text-gray-400', 'text-amber-600'];
const medalIcons = ['🥇', '🥈', '🥉'];
container.innerHTML = list.map((item, index) => {
const rankClass = index < 3 ? medalColors[index] : 'text-gray-700';
const icon = index < 3 ? medalIcons[index] : `${index + 1}.`;
return `
<div class="flex justify-between items-center px-3 py-2 rounded-lg ${index < 3 ? 'bg-yellow-50' : 'bg-gray-50'}">
<span class="font-bold ${rankClass} w-8">${icon}</span>
<span class="flex-1 text-gray-800 truncate mx-2">${item.name}</span>
<span class="font-bold text-gray-700">${item.score}</span>
</div>
`;
}).join('');
}
};
// ===================== DOM 元素 =====================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('startScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const scoreDisplay = document.getElementById('scoreDisplay');
const highScoreDisplay = document.getElementById('highScoreDisplay');
const finalScore = document.getElementById('finalScore');
const finalHighScore = document.getElementById('finalHighScore');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const rankModal = document.getElementById('rankModal');
const showRankBtn = document.getElementById('showRankBtn');
const closeRankBtn = document.getElementById('closeRankBtn');
const gameOverRankBtn = document.getElementById('gameOverRankBtn');
const rankInputArea = document.getElementById('rankInputArea');
const playerNameInput = document.getElementById('playerNameInput');
const saveRankBtn = document.getElementById('saveRankBtn');
canvas.width = CONFIG.canvasWidth;
canvas.height = CONFIG.canvasHeight;
// ===================== 游戏状态 =====================
let gameState = 'start';
let score = 0;
let highScore = parseInt(localStorage.getItem(STORAGE_KEYS.highScore)) || 0;
let gameSpeed = CONFIG.baseSpeed;
let frameCount = 0;
let items = [];
let obstacles = [];
let nextObstacleDistance = 0;
let consecutiveTightCount = 0;
// 难度阶段状态
let phase2Triggered = false;
let levelUpNoticeTimer = 0;
highScoreDisplay.textContent = highScore;
// ===================== 辅助函数:画五角星 =====================
function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
let rot = Math.PI / 2 * 3;
let x = cx;
let y = cy;
const step = Math.PI / spikes;
ctx.beginPath();
ctx.moveTo(cx, cy - outerRadius);
for (let i = 0; i < spikes; i++) {
x = cx + Math.cos(rot) * outerRadius;
y = cy + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = cx + Math.cos(rot) * innerRadius;
y = cy + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(cx, cy - outerRadius);
ctx.closePath();
}
// ===================== 玩家对象 =====================
const player = {
x: 80,
y: 0,
width: 40,
height: 50,
velocityY: 0,
isJumping: false,
isInvincible: false,
invincibleTimer: 0,
color: '#FF6B6B',
reset() {
this.y = CONFIG.canvasHeight - CONFIG.groundHeight - this.height;
this.velocityY = 0;
this.isJumping = false;
this.isInvincible = false;
this.invincibleTimer = 0;
},
jump() {
if (!this.isJumping) {
this.velocityY = CONFIG.jumpForce;
this.isJumping = true;
AudioManager.playJump();
}
},
update() {
this.velocityY += CONFIG.gravity;
this.y += this.velocityY;
const groundY = CONFIG.canvasHeight - CONFIG.groundHeight - this.height;
if (this.y >= groundY) {
this.y = groundY;
this.velocityY = 0;
this.isJumping = false;
}
if (this.isInvincible) {
this.invincibleTimer--;
if (this.invincibleTimer <= 0) {
this.isInvincible = false;
}
}
},
draw() {
ctx.save();
if (this.isInvincible) {
if (Math.floor(frameCount / 5) % 2 === 0) {
ctx.globalAlpha = 0.7;
}
ctx.shadowColor = '#FFD700';
ctx.shadowBlur = 18;
}
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.fillStyle = 'white';
ctx.fillRect(this.x + 25, this.y + 12, 10, 10);
ctx.fillStyle = '#333';
ctx.fillRect(this.x + 28, this.y + 15, 5, 5);
ctx.fillStyle = 'rgba(255, 150, 150, 0.6)';
ctx.fillRect(this.x + 22, this.y + 25, 8, 4);
ctx.restore();
}
};
// ===================== 障碍物类 =====================
class Obstacle {
constructor() {
this.width = 30 + Math.random() * 20;
this.height = 40 + Math.random() * 50;
this.x = CONFIG.canvasWidth;
this.y = CONFIG.canvasHeight - CONFIG.groundHeight - this.height;
this.color = '#4ECDC4';
this.passed = false;
}
update() {
this.x -= gameSpeed;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(this.x, this.y, 5, this.height);
}
isOffScreen() {
return this.x + this.width < 0;
}
}
// ===================== 道具类 =====================
class Item {
constructor(type, x) {
this.type = type;
this.x = x;
this.width = 30;
this.height = 30;
this.baseY = CONFIG.canvasHeight - CONFIG.groundHeight - 70 - Math.random() * 110;
this.y = this.baseY;
this.floatOffset = 0;
}
update() {
this.x -= gameSpeed;
this.floatOffset = Math.sin(Date.now() * 0.005) * 6;
this.y = this.baseY + this.floatOffset;
}
draw() {
if (this.type === 'coin') {
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFA500';
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('$', this.x + this.width/2, this.y + this.height/2);
} else if (this.type === 'invincible') {
ctx.save();
ctx.shadowColor = '#FF69B4';
ctx.shadowBlur = 12;
ctx.fillStyle = '#FF69B4';
drawStar(ctx, this.x + this.width/2, this.y + this.height/2,
5, this.width/2, this.width/4);
ctx.fill();
ctx.restore();
}
}
isOffScreen() {
return this.x + this.width < 0;
}
}
// ===================== 背景云朵 =====================
let clouds = [];
class Cloud {
constructor() {
this.x = CONFIG.canvasWidth + Math.random() * 200;
this.y = 30 + Math.random() * 100;
this.width = 60 + Math.random() * 40;
this.speed = 0.5 + Math.random() * 1;
}
update() {
this.x -= this.speed;
if (this.x + this.width < 0) {
this.x = CONFIG.canvasWidth + Math.random() * 100;
this.y = 30 + Math.random() * 100;
}
}
draw() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.beginPath();
ctx.arc(this.x, this.y, 20, 0, Math.PI * 2);
ctx.arc(this.x + 25, this.y - 10, 25, 0, Math.PI * 2);
ctx.arc(this.x + 50, this.y, 20, 0, Math.PI * 2);
ctx.arc(this.x + 25, this.y + 5, 18, 0, Math.PI * 2);
ctx.fill();
}
}
function initClouds() {
clouds = [];
for (let i = 0; i < 5; i++) {
const cloud = new Cloud();
cloud.x = Math.random() * CONFIG.canvasWidth;
clouds.push(cloud);
}
}
// ===================== 生成逻辑 =====================
let groundOffset = 0;
function spawnObstacle() {
if (nextObstacleDistance <= 0) {
obstacles.push(new Obstacle());
let gapTime;
const gap = CONFIG.obstacleTimeGap;
// 连续紧凑达到上限,强制插入大间距喘息
if (consecutiveTightCount >= CONFIG.maxConsecutiveTight) {
if (Math.random() < 0.3) {
gapTime = randomRange(gap.extraLoose[0], gap.extraLoose[1]);
} else {
gapTime = randomRange(gap.loose[0], gap.loose[1]);
}
consecutiveTightCount = 0;
} else {
// 加权随机生成不同档位间距
const rand = Math.random();
if (rand < 0.15) {
gapTime = randomRange(gap.tight[0], gap.tight[1]);
consecutiveTightCount++;
} else if (rand < 0.70) {
gapTime = randomRange(gap.normal[0], gap.normal[1]);
consecutiveTightCount++;
} else if (rand < 0.95) {
gapTime = randomRange(gap.loose[0], gap.loose[1]);
consecutiveTightCount = 0;
} else {
gapTime = randomRange(gap.extraLoose[0], gap.extraLoose[1]);
consecutiveTightCount = 0;
}
}
// 第二难度阶段:缩放障碍物间距
if (phase2Triggered) {
gapTime *= CONFIG.phase2GapScale;
}
// 时间换算为像素距离
nextObstacleDistance = gapTime * 60 * gameSpeed;
// 额外±10%随机波动
nextObstacleDistance *= 0.9 + Math.random() * 0.2;
}
}
function spawnItem() {
if (Math.random() < CONFIG.coinSpawnChance) {
items.push(new Item('coin', CONFIG.canvasWidth));
}
if (Math.random() < CONFIG.starSpawnChance) {
items.push(new Item('invincible', CONFIG.canvasWidth));
}
}
// 碰撞检测
function checkCollision(rect1, rect2) {
const padding = 5;
return rect1.x + padding < rect2.x + rect2.width &&
rect1.x + rect1.width - padding > rect2.x &&
rect1.y + padding < rect2.y + rect2.height &&
rect1.y + rect1.height - padding > rect2.y;
}
// 绘制背景
function drawBackground() {
const gradient = ctx.createLinearGradient(0, 0, 0, CONFIG.canvasHeight);
gradient.addColorStop(0, '#87CEEB');
gradient.addColorStop(1, '#E0F6FF');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, CONFIG.canvasWidth, CONFIG.canvasHeight);
clouds.forEach(cloud => cloud.draw());
ctx.fillStyle = '#A8D8B9';
ctx.beginPath();
ctx.moveTo(0, CONFIG.canvasHeight - CONFIG.groundHeight);
for (let x = 0; x <= CONFIG.canvasWidth; x += 100) {
ctx.lineTo(x, CONFIG.canvasHeight - CONFIG.groundHeight - 30 - Math.sin(x * 0.01) * 20);
}
ctx.lineTo(CONFIG.canvasWidth, CONFIG.canvasHeight - CONFIG.groundHeight);
ctx.closePath();
ctx.fill();
}
// 绘制地面
function drawGround() {
ctx.fillStyle = '#7BC47F';
ctx.fillRect(0, CONFIG.canvasHeight - CONFIG.groundHeight,
CONFIG.canvasWidth, CONFIG.groundHeight);
ctx.fillStyle = '#8B6914';
ctx.fillRect(0, CONFIG.canvasHeight - CONFIG.groundHeight + 15,
CONFIG.canvasWidth, CONFIG.groundHeight - 15);
ctx.fillStyle = '#5A9E5E';
for (let i = -1; i < CONFIG.canvasWidth / 40 + 1; i++) {
const x = (i * 40) - (groundOffset % 40);
ctx.fillRect(x, CONFIG.canvasHeight - CONFIG.groundHeight + 5, 20, 3);
}
}
// 绘制无敌状态进度条
function drawInvincibleBar() {
if (!player.isInvincible) return;
const barWidth = 200;
const barHeight = 8;
const barX = (CONFIG.canvasWidth - barWidth) / 2;
const barY = 20;
const progress = player.invincibleTimer / CONFIG.invincibleDuration;
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fillRect(barX, barY, barWidth, barHeight);
const gradient = ctx.createLinearGradient(barX, barY, barX + barWidth, barY);
gradient.addColorStop(0, '#FFD700');
gradient.addColorStop(1, '#FF69B4');
ctx.fillStyle = gradient;
ctx.fillRect(barX, barY, barWidth * progress, barHeight);
ctx.fillStyle = 'white';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.fillText('无敌状态', barX + barWidth/2, barY - 5);
}
// 绘制难度提升提示
function drawLevelUpNotice() {
if (levelUpNoticeTimer <= 0) return;
const alpha = Math.min(1, levelUpNoticeTimer / 30);
const scale = 1 + (90 - levelUpNoticeTimer) / 180;
ctx.save();
ctx.globalAlpha = alpha;
ctx.translate(CONFIG.canvasWidth / 2, CONFIG.canvasHeight / 2 - 40);
ctx.scale(scale, scale);
ctx.fillStyle = '#FFD700';
ctx.strokeStyle = '#fff';
ctx.lineWidth = 4;
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.strokeText('⚡ 难度提升!', 0, 0);
ctx.fillText('⚡ 难度提升!', 0, 0);
ctx.restore();
}
// ===================== 游戏主逻辑 =====================
function update() {
if (gameState !== 'playing') return;
frameCount++;
// 检测是否进入第二难度阶段
if (!phase2Triggered && score >= CONFIG.phase1MaxScore) {
phase2Triggered = true;
gameSpeed += CONFIG.phase2SpeedBoost;
levelUpNoticeTimer = 90; // 显示1.5秒
AudioManager.playLevelUp();
}
// 分阶段速度递增
const currentMaxSpeed = phase2Triggered ? CONFIG.phase2MaxSpeed : CONFIG.phase1MaxSpeed;
const currentIncrement = phase2Triggered ? CONFIG.phase2SpeedIncrement : CONFIG.phase1SpeedIncrement;
if (gameSpeed < currentMaxSpeed) {
gameSpeed += currentIncrement;
}
groundOffset += gameSpeed;
clouds.forEach(cloud => cloud.update());
player.update();
nextObstacleDistance -= gameSpeed;
spawnObstacle();
spawnItem();
// 难度提示计时递减
if (levelUpNoticeTimer > 0) {
levelUpNoticeTimer--;
}
// 更新障碍物
obstacles.forEach((obstacle, index) => {
obstacle.update();
if (!obstacle.passed && obstacle.x + obstacle.width < player.x) {
obstacle.passed = true;
score += 10;
scoreDisplay.textContent = score;
AudioManager.playScore();
}
if (obstacle.isOffScreen()) {
obstacles.splice(index, 1);
}
if (checkCollision(player, obstacle)) {
if (player.isInvincible) {
score += CONFIG.invincibleCrashScore;
scoreDisplay.textContent = score;
obstacles.splice(index, 1);
} else {
gameOver();
}
}
});
// 更新道具
items.forEach((item, index) => {
item.update();
if (checkCollision(player, item)) {
if (item.type === 'coin') {
score += CONFIG.coinScore;
scoreDisplay.textContent = score;
AudioManager.playCoin();
} else if (item.type === 'invincible') {
player.isInvincible = true;
player.invincibleTimer = CONFIG.invincibleDuration;
AudioManager.playInvincible();
}
items.splice(index, 1);
return;
}
if (item.isOffScreen()) {
items.splice(index, 1);
}
});
if (frameCount % 10 === 0) {
score++;
scoreDisplay.textContent = score;
}
}
function render() {
ctx.clearRect(0, 0, CONFIG.canvasWidth, CONFIG.canvasHeight);
drawBackground();
drawGround();
obstacles.forEach(obstacle => obstacle.draw());
items.forEach(item => item.draw());
player.draw();
drawInvincibleBar();
drawLevelUpNotice();
}
function gameLoop() {
update();
render();
requestAnimationFrame(gameLoop);
}
// ===================== 游戏状态控制 =====================
function startGame() {
AudioManager.init();
AudioManager.resume();
AudioManager.startBGM();
gameState = 'playing';
score = 0;
gameSpeed = CONFIG.baseSpeed;
obstacles = [];
items = [];
frameCount = 0;
consecutiveTightCount = 0;
phase2Triggered = false;
levelUpNoticeTimer = 0;
// 初始1.5秒友好间距
nextObstacleDistance = 1.5 * 60 * CONFIG.baseSpeed;
player.reset();
scoreDisplay.textContent = '0';
rankInputArea.classList.add('hidden');
playerNameInput.value = '';
startScreen.classList.add('hidden');
gameOverScreen.classList.add('hidden');
rankModal.classList.add('hidden');
}
function gameOver() {
gameState = 'over';
AudioManager.stopBGM();
AudioManager.playGameOver();
if (score > highScore) {
highScore = score;
localStorage.setItem(STORAGE_KEYS.highScore, highScore);
highScoreDisplay.textContent = highScore;
}
finalScore.textContent = score;
finalHighScore.textContent = highScore;
if (Leaderboard.canRank(score) && score > 0) {
rankInputArea.classList.remove('hidden');
} else {
rankInputArea.classList.add('hidden');
}
gameOverScreen.classList.remove('hidden');
}
// ===================== 事件监听 =====================
function handleJump() {
if (gameState === 'playing') {
player.jump();
}
}
// 键盘控制
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
if (gameState === 'start') {
startGame();
} else if (gameState === 'over') {
startGame();
} else {
handleJump();
}
}
if (e.code === 'Escape') {
rankModal.classList.add('hidden');
}
});
// 低延迟点击/触摸跳跃
function handleInputDown(e) {
e.preventDefault();
if (gameState === 'playing') {
handleJump();
}
}
canvas.addEventListener('mousedown', handleInputDown);
canvas.addEventListener('touchstart', handleInputDown, { passive: false });
// 按钮事件
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
showRankBtn.addEventListener('click', () => {
Leaderboard.render('rankList');
rankModal.classList.remove('hidden');
});
gameOverRankBtn.addEventListener('click', () => {
Leaderboard.render('rankList');
rankModal.classList.remove('hidden');
});
closeRankBtn.addEventListener('click', () => {
rankModal.classList.add('hidden');
});
saveRankBtn.addEventListener('click', () => {
const name = playerNameInput.value.trim() || '匿名玩家';
Leaderboard.addScore(name, score);
rankInputArea.classList.add('hidden');
Leaderboard.render('rankList');
rankModal.classList.remove('hidden');
});
playerNameInput.addEventListener('keydown', (e) => {
if (e.code === 'Enter') {
saveRankBtn.click();
}
});
// ===================== 初始化 =====================
initClouds();
player.reset();
gameLoop();
</script>
</body>
</html>Game Source: 横屏跳跃大冒险
Creator: RapidWolf74
Libraries: none
Complexity: complex (1037 lines, 40.1 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-rapidwolf74" to link back to the original. Then publish at arcadelab.ai/publish.