闯关小游戏

3 阅读14分钟
像素冒险 - 森林秘境 body { margin: 0; background: #12131c; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; font-family: "Microsoft Yahei", sans-serif; color: #fff; overflow: hidden; touch-action: none; } .game-container { position: relative; max-width: 100%; display: flex; justify-content: center; box-shadow: 0 15px 40px rgba(0,0,0,0.8); } canvas { border: 2px solid #3c4257; image-rendering: pixelated; image-rendering: crisp-edges; background: #87ceeb; max-width: 100%; max-height: 85vh; object-fit: contain; } .tip { margin-top: 15px; font-size: 14px; color: #8892b0; display: none; } #mobile-controls { display: none; position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } .control-btn { position: absolute; background: rgba(255, 255, 255, 0.15); border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 50%; color: #fff; display: flex; justify-content: center; align-items: center; font-size: 24px; font-weight: bold; pointer-events: auto; user-select: none; -webkit-user-select: none; backdrop-filter: blur(4px); } .control-btn:active, .control-btn.active { background: rgba(255, 255, 255, 0.4); } #btn-left { bottom: 20px; left: 20px; width: 60px; height: 60px; } #btn-right { bottom: 20px; left: 100px; width: 60px; height: 60px; } #btn-jump { bottom: 30px; right: 30px; width: 80px; height: 80px; background: rgba(255, 100, 100, 0.25); border-color: rgba(255, 150, 150, 0.5); } @media (pointer: coarse) { #mobile-controls { display: block; } .tip { display: block; } canvas { border-width: 2px; } } </style>
    <div id="mobile-controls">
        <div class="control-btn" id="btn-left" data-key="ArrowLeft">◀</div>
        <div class="control-btn" id="btn-right" data-key="ArrowRight">▶</div>
        <div class="control-btn" id="btn-jump" data-key="Space">跳</div>
    </div>
</div>
<div class="tip">操作:← → 移动 | 空格 跳跃 | R 重新开始</div>

<script>
    const canvas = document.getElementById('game');
    const ctx = canvas.getContext('2d');
    ctx.imageSmoothingEnabled = false;

    // ========== 游戏基础配置 ==========
    const GRAVITY = 0.6;
    const MOVE_SPEED = 4;
    const JUMP_FORCE = -12;
    const COYOTE_TIME = 6;
    const JUMP_CUT = 0.45;
    let LEVEL_WIDTH = 2000;

    // ========== 输入控制 ==========
    const keys = {};
    document.addEventListener('keydown', e => {
        if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Space'].includes(e.code)) e.preventDefault();
        keys[e.code] = true;
        if (e.code === 'KeyR') restartGame();
        if (gameState === 'start' && (e.code === 'Space' || e.code === 'Enter')) {
            currentLevel = 1;
            initLevel(currentLevel);
            gameState = 'playing';
        }
        if (gameState === 'win' && (e.code === 'Space' || e.code === 'Enter' || e.code === 'KeyR')) {
            nextLevel();
        }
    });
    document.addEventListener('keyup', e => keys[e.code] = false);

    // 移动端触摸事件
    const bindTouchControls = () => {
        const handleTouch = (btnId, code) => {
            const btn = document.getElementById(btnId);
            const start = (e) => {
                e.preventDefault();
                keys[code] = true;
                btn.classList.add('active');
                if (gameState === 'start') {
                    currentLevel = 1;
                    initLevel(currentLevel);
                    gameState = 'playing';
                } else if (gameState === 'win') {
                    nextLevel();
                }
            };
            const end = (e) => {
                e.preventDefault();
                keys[code] = false;
                btn.classList.remove('active');
            };
            btn.addEventListener('touchstart', start, {passive: false});
            btn.addEventListener('touchend', end, {passive: false});
            btn.addEventListener('touchcancel', end, {passive: false});
            btn.addEventListener('mousedown', start);
            btn.addEventListener('mouseup', end);
            btn.addEventListener('mouseleave', end);
        };
        handleTouch('btn-left', 'ArrowLeft');
        handleTouch('btn-right', 'ArrowRight');
        handleTouch('btn-jump', 'Space');
    };
    bindTouchControls();

    // 屏幕点击开始/下一关
    canvas.addEventListener('touchstart', (e) => {
        if (gameState === 'start') {
            currentLevel = 1;
            initLevel(currentLevel);
            gameState = 'playing';
        } else if (gameState === 'win') {
            nextLevel();
        }
    }, {passive: true});

    // ========== 玩家对象 ==========
    let player;
    function initPlayer() {
        player = {
            x: 50, y: 280,
            w: 24, h: 30, 
            vx: 0, vy: 0,
            onGround: false,
            coyote: 0,
            hp: 3,
            invincible: 0,
            facing: 1,
            animFrame: 0,
            animTimer: 0
        };
    }

    // ========== 关卡数据 ==========
    let platforms, spikes, coins, gems, enemies, goal, decorations;
    let currentLevel = 1;
    let maxLevel = 3;

    function initLevel(level) {
        coinCount = 0;
        gemCount = 0;
        player.x = 50;
        player.y = 280;
        player.vx = 0;
        player.vy = 0;
        player.hp = 3;
        camera.x = 0;

        if (level === 1) {
            LEVEL_WIDTH = 1800;
            platforms = [
                [0, 380, 800, 70],
                [900, 380, 500, 70],
                [1500, 380, 300, 70],
                [400, 280, 120, 20],
                [1100, 260, 100, 20],
            ];
            spikes = []; 
            coins = [
                {x: 250, y: 330, collected: false},
                {x: 450, y: 230, collected: false},
                {x: 950, y: 330, collected: false},
                {x: 1150, y: 210, collected: false},
                {x: 1600, y: 330, collected: false},
            ];
            gems = [
                {x: 700, y: 330, collected: false},
                {x: 1400, y: 330, collected: false},
            ];
            enemies = [
                {x: 600, y: 350, w: 32, h: 30, vx: 0.8, minX: 400, maxX: 750, alive: true},
            ];
            decorations = [
                {type: 'bgTree', x: 200, y: 380},
                {type: 'bgTree', x: 1300, y: 380},
                {type: 'mushroom', x: 350, y: 380},
                {type: 'flower', x: 500, y: 380, color: '#ff6b6b'},
            ];
            goal = {x: 1700, y: 316, w: 16, h: 64};

        } else if (level === 2) {
            LEVEL_WIDTH = 2400;
            platforms = [
                [0, 380, 600, 70],
                [680, 380, 300, 70],
                [1060, 320, 200, 40],
                [1320, 260, 180, 40],
                [1560, 380, 400, 70],
                [2040, 320, 120, 40],
                [2220, 380, 180, 70],
                [200, 300, 100, 20],
                [400, 230, 100, 20],
                [800, 280, 100, 20],
                [1700, 270, 120, 20],
                [1900, 200, 100, 20],
            ];
            spikes = [
                [620, 360, 60, 20],
                [1000, 360, 60, 20],
                [1980, 360, 60, 20],
            ];
            coins = [
                {x: 280, y: 260, collected: false},
                {x: 620, y: 260, collected: false},
                {x: 880, y: 230, collected: false},
                {x: 1400, y: 210, collected: false},
                {x: 1950, y: 150, collected: false},
            ];
            gems = [
                {x: 380, y: 180, collected: false},
                {x: 720, y: 200, collected: false},
                {x: 1150, y: 250, collected: false},
                {x: 1750, y: 200, collected: false},
            ];
            enemies = [
                {x: 420, y: 350, w: 32, h: 30, vx: 1.2, minX: 200, maxX: 580, alive: true},
                {x: 850, y: 350, w: 32, h: 30, vx: 1.5, minX: 700, maxX: 950, alive: true},
                {x: 1650, y: 350, w: 32, h: 30, vx: 1.8, minX: 1580, maxX: 1900, alive: true},
            ];
            decorations = [
                {type: 'bgTree', x: 150, y: 380},
                {type: 'bgTree', x: 1000, y: 380},
                {type: 'bgTree', x: 1750, y: 380},
                {type: 'mushroom', x: 1100, y: 320},
            ];
            goal = {x: 2300, y: 316, w: 16, h: 64};

        } else if (level === 3) {
            LEVEL_WIDTH = 2800;
            platforms = [
                [0, 380, 400, 70],
                [480, 380, 200, 70],
                [800, 300, 120, 20],
                [1000, 220, 120, 20],
                [1200, 380, 200, 70],
                [1500, 320, 100, 20],
                [1700, 240, 100, 20],
                [1900, 380, 200, 70],
                [2200, 300, 100, 20],
                [2350, 200, 100, 20],
                [2550, 380, 250, 70],
                [400, 260, 80, 20],
                [1400, 200, 80, 20],
            ];
            spikes = [
                [420, 360, 40, 20],
                [720, 360, 60, 20],
                [1420, 360, 60, 20],
                [2100, 360, 80, 20],
            ];
            coins = [
                {x: 250, y: 260, collected: false},
                {x: 520, y: 230, collected: false},
                {x: 840, y: 240, collected: false},
                {x: 1040, y: 160, collected: false},
                {x: 1280, y: 260, collected: false},
                {x: 1540, y: 260, collected: false},
                {x: 1740, y: 180, collected: false},
                {x: 2000, y: 260, collected: false},
                {x: 2380, y: 140, collected: false},
                {x: 2650, y: 260, collected: false},
            ];
            gems = [
                {x: 420, y: 200, collected: false},
                {x: 850, y: 240, collected: false},
                {x: 1450, y: 140, collected: false},
                {x: 2250, y: 240, collected: false},
                {x: 2600, y: 160, collected: false},
            ];
            enemies = [
                {x: 300, y: 350, w: 32, h: 30, vx: 2.0, minX: 200, maxX: 380, alive: true},
                {x: 1250, y: 350, w: 32, h: 30, vx: 2.2, minX: 1200, maxX: 1380, alive: true},
                {x: 1950, y: 350, w: 32, h: 30, vx: 2.5, minX: 1900, maxX: 2080, alive: true},
                {x: 2600, y: 350, w: 32, h: 30, vx: 2.8, minX: 2550, maxX: 2780, alive: true},
            ];
            decorations = [
                {type: 'bgTree', x: 100, y: 380},
                {type: 'bgTree', x: 2450, y: 380},
                {type: 'mushroom', x: 600, y: 380},
                {type: 'flower', x: 1300, y: 380, color: '#6bcb77'},
            ];
            goal = {x: 2700, y: 316, w: 16, h: 64};
        }
    }

    // ========== 状态变量 ==========
    let camera = {x: 0};
    let gameState = 'start'; 
    let coinCount = 0;
    let gemCount = 0;
    
    // 预生成背景元素,保证不闪烁
    let bgPines = [];
    let bgTrees = [];
    let particles = [];
    
    function initBackground() {
        bgPines = [];
        bgTrees = [];
        particles = [];
        
        for(let i = 0; i < LEVEL_WIDTH; i += 250) {
            // 发光松树
            if (Math.random() > 0.3) {
                bgPines.push({ 
                    x: i + Math.random()*100, 
                    y: 380 + Math.random()*10, 
                    scale: 0.8 + Math.random()*0.4,
                    offset: Math.random() * Math.PI * 2
                });
            }
            // 远景树干
            if (Math.random() > 0.5) {
                bgTrees.push({
                    x: i + Math.random()*150,
                    w: 60 + Math.random()*40,
                    type: Math.floor(Math.random()*2)
                });
            }
        }

        // 光斑粒子
        for(let i = 0; i < 60; i++) {
            particles.push({
                x: Math.random() * LEVEL_WIDTH,
                y: Math.random() * canvas.height,
                size: Math.random() * 2.5 + 1,
                speedY: Math.random() * 0.4 + 0.1,
                speedX: (Math.random() - 0.5) * 0.6,
                alpha: Math.random() * 0.8 + 0.2,
                pulse: Math.random() * Math.PI * 2
            });
        }
    }

    // ========== 碰撞检测 ==========
    function rectCollide(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 update() {
        // 粒子更新
        for(let p of particles) {
            p.y -= p.speedY;
            p.x += p.speedX;
            p.pulse += 0.05;
            if(p.y < -10) {
                p.y = canvas.height + 10;
                p.x = Math.random() * LEVEL_WIDTH;
            }
        }

        if (gameState !== 'playing') return;

        let moveDir = 0;
        if (keys['ArrowLeft'] || keys['KeyA']) moveDir -= 1;
        if (keys['ArrowRight'] || keys['KeyD']) moveDir += 1;

        player.vx = moveDir * MOVE_SPEED;
        if (moveDir !== 0) player.facing = moveDir;

        if (player.onGround) player.coyote = COYOTE_TIME;
        else player.coyote--;

        const jumpPressed = keys['Space'] || keys['KeyW'] || keys['ArrowUp'];
        if (jumpPressed && player.coyote > 0) {
            player.vy = JUMP_FORCE;
            player.coyote = 0;
            player.onGround = false;
        }
        if (!jumpPressed && player.vy < 0) player.vy *= JUMP_CUT;

        player.vy += GRAVITY;
        if (player.vy > 15) player.vy = 15;

        player.x += player.vx;
        if (player.x < 0) player.x = 0;
        if (player.x + player.w > LEVEL_WIDTH) player.x = LEVEL_WIDTH - player.w;

        for (let p of platforms) {
            const plat = {x:p[0], y:p[1], w:p[2], h:p[3]};
            if (rectCollide(player, plat)) {
                if (player.vx > 0) player.x = plat.x - player.w;
                else if (player.vx < 0) player.x = plat.x + plat.w;
            }
        }

        player.y += player.vy;
        player.onGround = false;

        for (let p of platforms) {
            const plat = {x:p[0], y:p[1], w:p[2], h:p[3]};
            if (rectCollide(player, plat)) {
                if (player.vy > 0) {
                    player.y = plat.y - player.h;
                    player.vy = 0;
                    player.onGround = true;
                } else if (player.vy < 0) {
                    player.y = plat.y + plat.h;
                    player.vy = 0;
                }
            }
        }

        if (player.y > canvas.height + 100) player.hp = 0;

        for (let e of enemies) {
            if (!e.alive) continue;
            e.x += e.vx;
            if (e.x <= e.minX || e.x + e.w >= e.maxX) e.vx *= -1;

            const eBox = {x: e.x+4, y: e.y+4, w: e.w-8, h: e.h-4};
            if (rectCollide(player, eBox)) {
                if (player.vy > 0 && player.y + player.h - e.y < 20) {
                    e.alive = false;
                    player.vy = JUMP_FORCE * 0.6;
                } else if (player.invincible <= 0) {
                    player.hp--;
                    player.invincible = 60;
                    player.vx = player.x < e.x ? -5 : 5;
                    player.vy = -6;
                }
            }
        }

        for (let s of spikes) {
            const spike = {x:s[0], y:s[1]+10, w:s[2], h:s[3]-10};
            if (rectCollide(player, spike) && player.invincible <= 0) {
                player.hp--;
                player.invincible = 60;
                player.vy = -8;
            }
        }

        for (let c of coins) {
            if (c.collected) continue;
            const coinBox = {x:c.x-12, y:c.y-12, w:24, h:24};
            if (rectCollide(player, coinBox)) {
                c.collected = true;
                coinCount++;
            }
        }

        for (let g of gems) {
            if (g.collected) continue;
            const gemBox = {x:g.x-12, y:g.y-16, w:24, h:32};
            if (rectCollide(player, gemBox)) {
                g.collected = true;
                gemCount++;
            }
        }

        if (rectCollide(player, goal)) gameState = 'win';

        if (player.invincible > 0) player.invincible--;
        if (player.hp <= 0) gameState = 'lose';

        player.animTimer++;
        if (player.animTimer > 8) {
            player.animTimer = 0;
            player.animFrame = (player.animFrame + 1) % 4;
        }

        camera.x = player.x - canvas.width / 2 + player.w / 2;
        if (camera.x < 0) camera.x = 0;
        if (camera.x > LEVEL_WIDTH - canvas.width) camera.x = LEVEL_WIDTH - canvas.width;
    }

    // ========== 渲染主函数 ==========
    function render() {
        if (!platforms) {
            ctx.fillStyle = '#87ceeb';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            drawUI();
            return;
        }

        drawBackground();

        ctx.save();
        ctx.translate(-camera.x, 0);

        // 1. 背景发光松树
        for (let pine of bgPines) {
            drawBgPine(pine.x, pine.y, pine.scale, pine.offset);
        }

        // 2. 地面装饰(蘑菇、花)
        for(let d of decorations) {
            if(d.type === 'flower') drawFlower(d.x, d.y, d.color);
            if(d.type === 'mushroom') drawMushroom(d.x, d.y);
        }

        // 3. 平台与地形
        for (let p of platforms) drawPlatform(p[0], p[1], p[2], p[3]);
        
        // 4. 前景大树装饰
        for(let d of decorations) {
            if(d.type === 'bgTree') drawForegroundTree(d.x, d.y);
        }

        // 5. 游戏实体
        for (let s of spikes) drawSpikes(s[0], s[1], s[2], s[3]);
        for (let c of coins) if (!c.collected) drawCoin(c.x, c.y);
        for (let g of gems) if (!g.collected) drawGem(g.x, g.y);
        for (let e of enemies) if (e.alive) drawSlime(e);
        
        drawGoal(goal.x, goal.y, goal.w, goal.h);
        drawFox();

        ctx.restore();

        // 6. 光斑粒子(在世界坐标系和屏幕坐标系之间)
        drawParticles();

        // 7. 顶层 UI
        drawUI();
    }

    // ========== 极致背景渲染 ==========
    function drawBackground() {
        // 天空/远景渐变
        const skyGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
        skyGrad.addColorStop(0, '#4a7a5d');
        skyGrad.addColorStop(0.4, '#8eb36b');
        skyGrad.addColorStop(0.8, '#e5d28b');
        skyGrad.addColorStop(1, '#fceea7');
        ctx.fillStyle = skyGrad;
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // 丁达尔光效 (阳光射线)
        ctx.save();
        ctx.globalCompositeOperation = 'lighter';
        for (let i = 0; i < 5; i++) {
            ctx.beginPath();
            let offset = (i * 250) - (camera.x * 0.1) % 1000;
            ctx.moveTo(offset - 200, -50);
            ctx.lineTo(offset + 100, -50);
            ctx.lineTo(offset + 400, canvas.height);
            ctx.lineTo(offset + 100, canvas.height);
            // 交替透明度
            ctx.fillStyle = `rgba(255, 240, 180, ${0.04 + (i%2)*0.03})`;
            ctx.fill();
        }
        ctx.restore();

        // 远景山脉(视差0.1)
        ctx.save();
        ctx.translate(-camera.x * 0.1, 0);
        ctx.fillStyle = '#8fae76';
        ctx.beginPath();
        ctx.moveTo(-200, canvas.height);
        for(let i=0; i<4000; i+=200) {
            ctx.lineTo(i + 100, 200 + Math.sin(i)*30);
            ctx.lineTo(i + 200, 300);
        }
        ctx.lineTo(4000, canvas.height);
        ctx.fill();
        ctx.restore();

        // 中景巨树 (视差0.3)
        ctx.save();
        ctx.translate(-camera.x * 0.3, 0);
        for(let i=0; i<bgTrees.length; i++) {
            let t = bgTrees[i];
            // 树底色
            ctx.fillStyle = '#593d2b';
            ctx.fillRect(t.x, 0, t.w, canvas.height);
            // 树皮纹理
            ctx.fillStyle = '#3d2719';
            for(let j=0; j<t.w; j+=15) {
                ctx.fillRect(t.x + j, 0, 3, canvas.height);
            }
            // 暗部阴影
            ctx.fillStyle = 'rgba(0,0,0,0.3)';
            ctx.fillRect(t.x, 0, t.w * 0.3, canvas.height);
            // 受光面
            ctx.fillStyle = 'rgba(255, 200, 100, 0.1)';
            ctx.fillRect(t.x + t.w * 0.7, 0, t.w * 0.3, canvas.height);
        }
        ctx.restore();

        // 顶部树冠与垂藤 (视差0.8)
        ctx.save();
        ctx.translate(-camera.x * 0.8, 0);
        ctx.fillStyle = '#1e331e';
        for(let i=0; i<4000; i+=120) {
            // 树冠团
            ctx.beginPath();
            ctx.arc(i, -40, 100, 0, Math.PI*2);
            ctx.fill();
            // 粗大的垂藤
            if (i % 240 === 0) {
                ctx.fillStyle = '#152415';
                ctx.fillRect(i, 0, 10, 150 + (i%3)*50);
                ctx.fillStyle = '#1e331e';
            }
        }
        ctx.restore();
    }

    function drawBgPine(x, y, scale, offset) {
        const time = Date.now() / 500;
        const glow = Math.sin(time + offset) * 0.3 + 0.7;
        
        ctx.save();
        ctx.translate(x, y);
        ctx.scale(scale, scale);

        // 树干
        ctx.fillStyle = '#3d2515';
        ctx.fillRect(-4, -40, 8, 40);

        // 多层树叶 (从下到上)
        const layers = [
            {w: 45, h: 40, y: -40, c: '#203d1c'},
            {w: 35, h: 35, y: -70, c: '#2a4f24'},
            {w: 25, h: 30, y: -100, c: '#33612b'}
        ];

        for (let i=0; i<layers.length; i++) {
            let l = layers[i];
            ctx.fillStyle = l.c;
            ctx.beginPath();
            ctx.moveTo(0, l.y - l.h);
            ctx.lineTo(l.w, l.y);
            ctx.lineTo(-l.w, l.y);
            ctx.fill();
        }

        // 发光点
        for(let i=0; i<6; i++) {
            let lx = Math.sin(i*12) * 20;
            let ly = -50 + Math.cos(i*8) * 30;
            // 发光光晕
            ctx.fillStyle = `rgba(255, 230, 100, ${0.2 * glow})`;
            ctx.beginPath();
            ctx.arc(lx, ly, 4, 0, Math.PI*2);
            ctx.fill();
            // 核心亮点
            ctx.fillStyle = `rgba(255, 255, 200, ${glow})`;
            ctx.fillRect(lx-1, ly-1, 2, 2);
        }

        ctx.restore();
    }

    function drawParticles() {
        for(let p of particles) {
            let alpha = p.alpha + Math.sin(p.pulse) * 0.2;
            if(alpha < 0) alpha = 0;
            if(alpha > 1) alpha = 1;
            
            // 光晕
            ctx.fillStyle = `rgba(255, 250, 200, ${alpha * 0.4})`;
            ctx.beginPath();
            ctx.arc(p.x - camera.x * 0.5, p.y, p.size * 2.5, 0, Math.PI*2);
            ctx.fill();
            // 核心
            ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
            ctx.beginPath();
            ctx.arc(p.x - camera.x * 0.5, p.y, p.size, 0, Math.PI*2);
            ctx.fill();
        }
    }

    // ========== 地形与实体绘制 ==========
    function drawPlatform(x, y, w, h) {
        // 底座
        ctx.fillStyle = '#2d3326';
        ctx.fillRect(x, y, w, h);

        // 石砖纹理 (错落有致)
        for (let i = 0; i < w; i += 32) {
            for (let j = 12; j < h; j += 20) {
                const offset = ((j - 12) / 20) % 2 === 0 ? 0 : 16;
                const bx = x + i + offset;
                if (bx > x + w - 16) continue;

                // 砖块主体
                ctx.fillStyle = '#49533d';
                ctx.fillRect(bx + 2, y + j + 2, 28, 16);
                // 砖块暗部
                ctx.fillStyle = '#3b4230';
                ctx.fillRect(bx + 2, y + j + 10, 28, 8);
                
                // 高光边缘
                ctx.fillStyle = '#626e50';
                ctx.fillRect(bx + 2, y + j + 2, 28, 2);
                
                // 砖缝阴影
                ctx.fillStyle = '#1c2118';
                ctx.fillRect(bx, y + j, 32, 2);
                ctx.fillRect(bx, y + j, 2, 20);
            }
        }

        // 顶部草地
        ctx.fillStyle = '#3b6e31';
        ctx.fillRect(x, y, w, 12);
        
        // 草地锯齿与受光
        for(let i=0; i<w; i+=6) {
            let grassH = 4 + Math.sin(i * 0.5) * 6;
            // 暗绿底色
            ctx.fillStyle = '#3b6e31';
            ctx.fillRect(x+i, y, 6, grassH);
            // 亮绿高光
            ctx.fillStyle = '#5ea34e';
            ctx.fillRect(x+i, y, 4, grassH * 0.7);
        }
        
        // 垂下的藤蔓
        for (let i = 10; i < w; i += 45) {
            const vlen = 30 + (i % 4) * 25;
            for(let j=0; j<vlen; j+=6) {
                ctx.fillStyle = '#2a4a24';
                ctx.fillRect(x + i + Math.sin(j*0.3)*3, y + 12 + j, 3, 6);
                
                // 叶片
                if(j % 12 === 0 && j > 0) {
                    ctx.fillStyle = '#4a8540';
                    ctx.fillRect(x + i - 4, y + 12 + j, 6, 3);
                    ctx.fillRect(x + i + 5, y + 12 + j + 4, 6, 3);
                }
            }
        }
    }

    function drawSpikes(x, y, w, h) {
        for(let i=0; i<w; i+=16) {
            // 尖刺暗部
            ctx.fillStyle = '#4e5d6e';
            ctx.beginPath();
            ctx.moveTo(x + i, y + h);
            ctx.lineTo(x + i + 8, y);
            ctx.lineTo(x + i + 16, y + h);
            ctx.fill();
            // 亮部
            ctx.fillStyle = '#7b93a8';
            ctx.beginPath();
            ctx.moveTo(x + i + 2, y + h);
            ctx.lineTo(x + i + 8, y + 4);
            ctx.lineTo(x + i + 10, y + h);
            ctx.fill();
        }
    }

    function drawCoin(x, y) {
        const bob = Math.sin(Date.now() / 200) * 4;
        
        // 阴影
        ctx.fillStyle = 'rgba(0,0,0,0.15)';
        ctx.beginPath();
        ctx.ellipse(x, y + 16, 12, 4, 0, 0, Math.PI*2);
        ctx.fill();

        // 主体厚度
        ctx.fillStyle = '#b87d00';
        ctx.fillRect(x - 10, y - 14 + bob, 20, 28);
        // 亮面
        ctx.fillStyle = '#ffb400';
        ctx.fillRect(x - 8, y - 12 + bob, 16, 24);
        // 内圈凹槽
        ctx.fillStyle = '#ffda55';
        ctx.fillRect(x - 4, y - 8 + bob, 8, 16);
        // 高光
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(x - 6, y - 10 + bob, 4, 6);
    }

    function drawGem(x, y) {
        const bob = Math.sin(Date.now() / 250) * 4;
        
        // 阴影
        ctx.fillStyle = 'rgba(0,0,0,0.15)';
        ctx.beginPath();
        ctx.ellipse(x, y + 16, 10, 4, 0, 0, Math.PI*2);
        ctx.fill();

        // 外轮廓暗部
        ctx.fillStyle = '#0055cc';
        ctx.beginPath();
        ctx.moveTo(x, y - 16 + bob);
        ctx.lineTo(x + 12, y + bob);
        ctx.lineTo(x, y + 16 + bob);
        ctx.lineTo(x - 12, y + bob);
        ctx.fill();
        
        // 内部亮部
        ctx.fillStyle = '#33aaff';
        ctx.beginPath();
        ctx.moveTo(x, y - 12 + bob);
        ctx.lineTo(x + 8, y + bob);
        ctx.lineTo(x, y + 12 + bob);
        ctx.lineTo(x - 8, y + bob);
        ctx.fill();
        
        // 中心切面高光
        ctx.fillStyle = '#99eeff';
        ctx.beginPath();
        ctx.moveTo(x, y - 6 + bob);
        ctx.lineTo(x + 4, y + bob);
        ctx.lineTo(x, y + 4 + bob);
        ctx.lineTo(x - 4, y + bob);
        ctx.fill();
        
        // 边缘白边
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(x - 2, y - 8 + bob, 3, 3);
    }

    function drawSlime(e) {
        const time = Date.now() / 150;
        const stretch = Math.sin(time) * 3;
        
        // 投影
        ctx.fillStyle = 'rgba(0,0,0,0.25)';
        ctx.beginPath();
        ctx.ellipse(e.x + e.w/2, e.y + e.h - 2, e.w/2, 5, 0, 0, Math.PI*2);
        ctx.fill();

        // 身体底座
        ctx.fillStyle = '#39b539';
        ctx.beginPath();
        ctx.arc(e.x + e.w/2, e.y + e.h - 8 + stretch, e.w/2, Math.PI, 0);
        ctx.fill();
        ctx.fillRect(e.x, e.y + e.h - 8 + stretch, e.w, 8 - stretch);

        // 身体亮面渐变
        ctx.fillStyle = '#5eeb5e';
        ctx.beginPath();
        ctx.arc(e.x + e.w/2, e.y + e.h - 6 + stretch, e.w/2 - 2, Math.PI, 0);
        ctx.fill();
        ctx.fillRect(e.x + 2, e.y + e.h - 6 + stretch, e.w - 4, 6 - stretch);

        // 顶部高光
        ctx.fillStyle = '#a3ffa3';
        ctx.beginPath();
        ctx.arc(e.x + e.w/2 - 4, e.y + e.h - 14 + stretch, 4, 0, Math.PI*2);
        ctx.fill();

        // 眼睛
        ctx.fillStyle = '#1a331a';
        ctx.fillRect(e.x + 7, e.y + 10 + stretch, 3, 5);
        ctx.fillRect(e.x + e.w - 10, e.y + 10 + stretch, 3, 5);
        
        // 眼睛高光
        ctx.fillStyle = '#fff';
        ctx.fillRect(e.x + 7, e.y + 10 + stretch, 1.5, 1.5);
        ctx.fillRect(e.x + e.w - 10, e.y + 10 + stretch, 1.5, 1.5);

        // 腮红
        ctx.fillStyle = 'rgba(255, 80, 80, 0.7)';
        ctx.fillRect(e.x + 3, e.y + 15 + stretch, 5, 3);
        ctx.fillRect(e.x + e.w - 8, e.y + 15 + stretch, 5, 3);
    }

    function drawFox() {
        ctx.save();
        if (player.invincible > 0 && Math.floor(player.invincible / 4) % 2 === 0) {
            ctx.globalAlpha = 0.6;
        }

        ctx.translate(player.x + player.w / 2, player.y);
        if (player.facing === -1) {
            ctx.scale(-1, 1);
        }
        ctx.translate(-player.w / 2, 0);

        const w = player.w;
        const h = player.h;
        const legOffset = player.onGround && Math.abs(player.vx) > 0.1
            ? [0, 2, 0, -2][player.animFrame] : 0;

        // 尾巴
        ctx.fillStyle = '#f29244';
        ctx.fillRect(-10, 14, 12, 12);
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(-10, 20, 8, 6); // 白尾尖

        // 背包
        ctx.fillStyle = '#6b4226';
        ctx.fillRect(w - 8, 12, 10, 14);
        ctx.fillStyle = '#8c5836';
        ctx.fillRect(w - 6, 14, 6, 4);
        // 背带
        ctx.fillStyle = '#4a2e1a';
        ctx.fillRect(8, 12, 2, 10);

        // 身体
        ctx.fillStyle = '#f29244';
        ctx.fillRect(2, 14, 16, 12);
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(6, 18, 8, 8); // 白肚皮

        // 绿披风
        ctx.fillStyle = '#4d8c42';
        ctx.fillRect(0, 12, 6, 14);
        ctx.fillRect(2, 10, 12, 4);
        // 披风阴影
        ctx.fillStyle = '#386832';
        ctx.fillRect(0, 20, 6, 6);

        // 头部
        ctx.fillStyle = '#f29244';
        ctx.fillRect(4, 0, 16, 14);

        // 耳朵
        ctx.fillStyle = '#f29244';
        ctx.fillRect(2, -6, 6, 8);
        ctx.fillRect(16, -6, 6, 8);
        ctx.fillStyle = '#3d2010'; // 黑耳尖
        ctx.fillRect(4, -4, 2, 4);
        ctx.fillRect(18, -4, 2, 4);

        // 脸部细节
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(8, 6, 8, 6); // 口鼻
        ctx.fillStyle = '#111'; // 眼睛
        ctx.fillRect(10, 2, 2, 4);
        ctx.fillRect(14, 2, 2, 4);
        ctx.fillStyle = '#e85d22'; // 鼻子
        ctx.fillRect(11, 8, 2, 2);
        
        // 围巾
        ctx.fillStyle = '#5db54f';
        ctx.fillRect(6, 12, 8, 4);

        // 腿
        ctx.fillStyle = '#d96f32';
        ctx.fillRect(4, 26, 4, 4 + legOffset);
        ctx.fillRect(12, 26, 4, 4 - legOffset);

        ctx.restore();
    }

    function drawGoal(x, y, w, h) {
        // 阴影
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.fillRect(x - 4, y + h - 4, w + 8, 4);

        // 旗杆
        ctx.fillStyle = '#c0c0c0';
        ctx.fillRect(x, y, w, h);
        ctx.fillStyle = '#808080';
        ctx.fillRect(x + w - 4, y, 4, h);
        
        // 顶部圆球
        ctx.fillStyle = '#ffd700';
        ctx.beginPath();
        ctx.arc(x + w/2, y - 6, 8, 0, Math.PI*2);
        ctx.fill();
        // 高光
        ctx.fillStyle = '#fff';
        ctx.beginPath();
        ctx.arc(x + w/2 - 2, y - 8, 3, 0, Math.PI*2);
        ctx.fill();

        // 飘动旗帜
        const wave = Math.sin(Date.now()/200) * 8;
        ctx.fillStyle = '#ff4500';
        ctx.beginPath();
        ctx.moveTo(x + w, y + 10);
        ctx.quadraticCurveTo(x + w + 20, y + 10 + wave, x + w + 40, y + 20);
        ctx.lineTo(x + w, y + 35);
        ctx.fill();
    }

    // ========== 前景装饰 ==========
    function drawForegroundTree(x, y) {
        // 树干
        ctx.fillStyle = '#4a3426';
        ctx.fillRect(x - 8, y - 80, 16, 80);
        // 树皮纹理
        ctx.fillStyle = '#332216';
        ctx.fillRect(x - 4, y - 80, 4, 80);
        ctx.fillRect(x + 2, y - 80, 2, 80);

        // 树冠
        ctx.fillStyle = '#2d5a27';
        ctx.beginPath();
        ctx.arc(x, y - 90, 35, 0, Math.PI*2);
        ctx.arc(x - 25, y - 70, 25, 0, Math.PI*2);
        ctx.arc(x + 25, y - 70, 25, 0, Math.PI*2);
        ctx.fill();

        // 树冠高光
        ctx.fillStyle = '#44883c';
        ctx.beginPath();
        ctx.arc(x - 5, y - 95, 18, 0, Math.PI*2);
        ctx.fill();
    }

    function drawMushroom(x, y) {
        // 杆
        ctx.fillStyle = '#e0d8c8';
        ctx.fillRect(x - 5, y - 14, 10, 14);
        // 杆暗部
        ctx.fillStyle = '#b8b0a0';
        ctx.fillRect(x + 1, y - 14, 4, 14);
        // 伞盖
        ctx.fillStyle = '#ff4d4d';
        ctx.beginPath();
        ctx.arc(x, y - 14, 12, Math.PI, 0);
        ctx.fill();
        // 伞盖阴影
        ctx.fillStyle = '#cc3333';
        ctx.fillRect(x, y - 14, 12, 4);
        // 白点
        ctx.fillStyle = '#fff';
        ctx.fillRect(x - 8, y - 20, 5, 5);
        ctx.fillRect(x + 3, y - 18, 4, 4);
    }

    function drawFlower(x, y, color) {
        // 茎
        ctx.fillStyle = '#3cb371';
        ctx.fillRect(x, y - 12, 2, 12);
        // 叶片
        ctx.fillRect(x - 4, y - 8, 4, 2);
        ctx.fillRect(x + 2, y - 5, 4, 2);
        // 花瓣
        ctx.fillStyle = color;
        ctx.fillRect(x - 3, y - 16, 8, 6);
        // 花蕊
        ctx.fillStyle = '#ffd700';
        ctx.fillRect(x, y - 15, 2, 2);
    }

    // ========== UI绘制 ==========
    function drawUI() {
        if (gameState === 'playing') {
            // 左上角关卡
            ctx.fillStyle = 'rgba(0,0,0,0.4)';
            ctx.fillRect(10, 10, 105, 36);
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 22px "Microsoft Yahei", sans-serif';
            ctx.textAlign = 'left';
            const levelNames = ['一', '二', '三'];
            ctx.fillText('关卡' + levelNames[currentLevel - 1], 20, 36);

            // 右上角金币 UI
            ctx.fillStyle = 'rgba(0,0,0,0.4)';
            ctx.fillRect(canvas.width - 120, 10, 110, 36);
            
            // 绘制UI金币图标
            const coinX = canvas.width - 95;
            const coinY = 28;
            ctx.fillStyle = '#ffc800';
            ctx.beginPath();
            ctx.arc(coinX, coinY, 12, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = '#fff';
            ctx.fillRect(coinX - 4, coinY - 6, 8, 12);
            ctx.fillStyle = '#ffc800';
            ctx.fillRect(coinX - 2, coinY - 4, 4, 8);

            ctx.fillStyle = '#fff';
            ctx.font = 'bold 24px monospace';
            ctx.fillText('x' + coinCount, coinX + 20, 36);

            // 左下角生命值
            for (let i = 0; i < player.hp; i++) {
                ctx.fillStyle = '#ff3366';
                // 像素心形
                ctx.fillRect(25 + i * 25, 420, 4, 4);
                ctx.fillRect(35 + i * 25, 420, 4, 4);
                ctx.fillRect(20 + i * 25, 425, 24, 6);
                ctx.fillRect(25 + i * 25, 431, 14, 4);
                ctx.fillRect(30 + i * 25, 435, 4, 4);
            }
        }

        // 界面层
        if (gameState === 'start') {
            ctx.fillStyle = 'rgba(18,19,28,0.8)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 40px "Microsoft Yahei", sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText('森林秘境', canvas.width/2, canvas.height/2 - 20);
            ctx.font = '20px "Microsoft Yahei", sans-serif';
            ctx.fillStyle = '#ccc';
            ctx.fillText('点击屏幕或按空格开始', canvas.width/2, canvas.height/2 + 30);
        } else if (gameState === 'win') {
            ctx.fillStyle = 'rgba(18,19,28,0.8)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = '#ffd700';
            ctx.font = 'bold 40px "Microsoft Yahei", sans-serif';
            ctx.textAlign = 'center';
            
            if (currentLevel < maxLevel) {
                ctx.fillText('关卡通过!', canvas.width/2, canvas.height/2 - 40);
                ctx.fillStyle = '#fff';
                ctx.font = '20px "Microsoft Yahei", sans-serif';
                ctx.fillText('点击屏幕或按空格进入下一关', canvas.width/2, canvas.height/2 + 30);
            } else {
                ctx.fillText('全部通关!', canvas.width/2, canvas.height/2 - 40);
                ctx.fillStyle = '#fff';
                ctx.font = '20px "Microsoft Yahei", sans-serif';
                ctx.fillText('点击屏幕或按 R 重新开始', canvas.width/2, canvas.height/2 + 30);
            }
            ctx.fillText('金币: ' + coinCount + ' 宝石: ' + gemCount, canvas.width/2, canvas.height/2 + 70);
        } else if (gameState === 'lose') {
            ctx.fillStyle = 'rgba(18,19,28,0.8)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = '#ff4500';
            ctx.font = 'bold 40px "Microsoft Yahei", sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText('游戏结束', canvas.width/2, canvas.height/2 - 20);
            ctx.fillStyle = '#fff';
            ctx.font = '20px "Microsoft Yahei", sans-serif';
            ctx.fillText('点击屏幕或按 R 重新开始', canvas.width/2, canvas.height/2 + 30);
        }
        ctx.textAlign = 'left';
    }

    // ========== 状态控制 ==========
    function restartGame() {
        currentLevel = 1;
        initPlayer();
        initLevel(currentLevel);
        initBackground();
        gameState = 'playing';
    }

    function nextLevel() {
        if (currentLevel < maxLevel) {
            currentLevel++;
            initPlayer();
            initLevel(currentLevel);
            initBackground();
            gameState = 'playing';
        } else {
            restartGame();
        }
    }

    // ========== 主循环 ==========
    function gameLoop() {
        update();
        render();
        requestAnimationFrame(gameLoop);
    }

    initPlayer();
    initLevel(1); 
    initBackground();
    gameState = 'start';
    gameLoop();
</script>