🎮ArcadeLab

横屏跳跃大冒险

by RapidWolf74
1037 lines40.1 KB
▶ Play
<!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.