🎮ArcadeLab

昨天、今天和明天

by LuckyKoala29
817 lines30.5 KB
▶ Play
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>昨天、今天和明天</title>
    <style>
        * { margin: 0; padding: 0; }
        body {
            background: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            overflow: hidden;
            touch-action: none; /* 禁止触摸滚动 */
        }
        canvas {
            display: block;
            background: #0a0a0a;
            cursor: default;
            border: 1px solid #222;
            image-rendering: pixelated;
            touch-action: none;
        }

        /* 移动端控制按钮 */
        .control-btn {
            position: fixed;
            bottom: 40px;
            width: 70px;
            height: 70px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.08);
            border: 2px solid rgba(255, 255, 255, 0.25);
            backdrop-filter: blur(4px);
            display: none;          /* 默认隐藏,游戏开始时显示 */
            align-items: center;
            justify-content: center;
            font-size: 36px;
            color: rgba(255, 255, 255, 0.6);
            user-select: none;
            touch-action: none;
            z-index: 20;
            transition: background 0.1s;
        }
        .control-btn:active {
            background: rgba(255, 255, 255, 0.2);
        }
        #btn-left {
            left: 30px;
        }
        #btn-right {
            right: 30px;
        }

        /* 重来按钮(与画布同层) */
        .restart-btn {
            position: fixed;        /* 改为fixed,覆盖全屏 */
            bottom: 120px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(255, 255, 255, 0.08);
            border: 1px solid #666;
            color: #ccc;
            padding: 12px 36px;
            font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
            font-size: 18px;
            cursor: pointer;
            border-radius: 30px;
            transition: 0.3s;
            backdrop-filter: blur(4px);
            display: none;
            letter-spacing: 2px;
            z-index: 30;
        }
        .restart-btn:hover { background: rgba(255, 255, 255, 0.2); color: #fff; border-color: #aaa; }

        /* 针对移动端调整按钮大小 */
        @media (max-width: 600px) {
            .control-btn {
                width: 60px;
                height: 60px;
                font-size: 30px;
                bottom: 30px;
            }
            #btn-left { left: 20px; }
            #btn-right { right: 20px; }
        }
    </style>
</head>
<body>

<canvas id="gameCanvas"></canvas>

<!-- 触屏控制按钮 -->
<div class="control-btn" id="btn-left">◀</div>
<div class="control-btn" id="btn-right">▶</div>

<button class="restart-btn" id="restartBtn">↻ 重来</button>

<script>
    // ---------- 固定逻辑尺寸 ----------
    const DESIGN_W = 900;
    const DESIGN_H = 600;

    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    canvas.width = DESIGN_W;
    canvas.height = DESIGN_H;

    // ---------- 离屏画布 ----------
    const offscreen = document.createElement('canvas');
    offscreen.width = DESIGN_W;
    offscreen.height = DESIGN_H;
    const offCtx = offscreen.getContext('2d');

    // ---------- 自适应尺寸(保持 3:2) ----------
    function resizeCanvas() {
        const winW = window.innerWidth;
        const winH = window.innerHeight;
        const scaleX = winW / DESIGN_W;
        const scaleY = winH / DESIGN_H;
        let scale = Math.min(scaleX, scaleY) * 0.96;
        canvas.style.width = (DESIGN_W * scale) + 'px';
        canvas.style.height = (DESIGN_H * scale) + 'px';
    }
    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();

    // ---------- 鼠标/触摸坐标映射 ----------
    let mouseX = 0, mouseY = 0;
    let mouseOnMenu = false;

    canvas.addEventListener('mousemove', (e) => {
        const rect = canvas.getBoundingClientRect();
        const scaleX = canvas.width / rect.width;
        const scaleY = canvas.height / rect.height;
        mouseX = (e.clientX - rect.left) * scaleX;
        mouseY = (e.clientY - rect.top) * scaleY;
        if (gameState === STATE.MENU) {
            const text = '昨天、今天和明天';
            ctx.font = '40px "Microsoft YaHei", "PingFang SC", sans-serif';
            const metrics = ctx.measureText(text);
            const tw = metrics.width;
            const th = 50;
            const cx = canvas.width/2;
            const cy = canvas.height/2;
            mouseOnMenu = (mouseX >= cx - tw/2 && mouseX <= cx + tw/2 &&
                           mouseY >= cy - th/2 && mouseY <= cy + th/2);
            canvas.style.cursor = mouseOnMenu ? 'pointer' : 'default';
        }
    });

    canvas.addEventListener('click', (e) => {
        if (gameState === STATE.MENU && mouseOnMenu) {
            startGame();
        }
    });

    // ---------- 游戏状态 ----------
    const STATE = { MENU: 0, PLAYING: 1, GAMEOVER: 2 };
    let gameState = STATE.MENU;
    let deathCause = 'monster';

    const game = {
        player: { x: 0, y: 0, radius: 18 },
        gate: { x: 350, y: 0 },
        currentLightRadius: 220,
        speed: 3.5,
        isMoving: false,
        monsters: [],
        monsterSpawnTimer: 0,
        spawnInterval: 45,
        minX: -800,
        maxX: 800,
        minY: -150,
        maxY: 150,
        isGameOver: false,
        gameOverAlpha: 0,
        frame: 0,
        groundOffset: 0,
        leftHoldTime: 0,
        psycheStage: 0,
        prevPsycheStage: -1,
        psycheAlpha: 0,
        psycheDisplayTimer: 0,
        floatPhase: 0,
        arrowFlowOffset: 0,
    };

    // ---------- 键盘 ----------
    const keys = { left: false, right: false, up: false, down: false };

    document.addEventListener('keydown', (e) => {
        if (gameState !== STATE.PLAYING) return;
        if (e.key === 'ArrowLeft') { keys.left = true; e.preventDefault(); }
        if (e.key === 'ArrowRight') { keys.right = true; e.preventDefault(); }
        if (e.key === 'ArrowUp') { keys.up = true; e.preventDefault(); }
        if (e.key === 'ArrowDown') { keys.down = true; e.preventDefault(); }
        if (e.key === 'w' || e.key === 'W') { keys.up = true; e.preventDefault(); }
        if (e.key === 's' || e.key === 'S') { keys.down = true; e.preventDefault(); }
        if (e.key === 'r' || e.key === 'R') { restartGame(); }
    });
    document.addEventListener('keyup', (e) => {
        if (gameState !== STATE.PLAYING) return;
        if (e.key === 'ArrowLeft') { keys.left = false; e.preventDefault(); }
        if (e.key === 'ArrowRight') { keys.right = false; e.preventDefault(); }
        if (e.key === 'ArrowUp') { keys.up = false; e.preventDefault(); }
        if (e.key === 'ArrowDown') { keys.down = false; e.preventDefault(); }
        if (e.key === 'w' || e.key === 'W') { keys.up = false; e.preventDefault(); }
        if (e.key === 's' || e.key === 'S') { keys.down = false; e.preventDefault(); }
    });

    // ---------- 触摸按钮控制 ----------
    const btnLeft = document.getElementById('btn-left');
    const btnRight = document.getElementById('btn-right');

    function setupTouchButton(element, key) {
        // 触摸事件
        element.addEventListener('touchstart', (e) => {
            e.preventDefault();
            if (gameState === STATE.PLAYING) {
                keys[key] = true;
            }
        }, { passive: false });
        element.addEventListener('touchend', (e) => {
            e.preventDefault();
            keys[key] = false;
        }, { passive: false });
        element.addEventListener('touchcancel', (e) => {
            keys[key] = false;
        });
        // 鼠标支持(便于调试)
        element.addEventListener('mousedown', (e) => {
            e.preventDefault();
            if (gameState === STATE.PLAYING) {
                keys[key] = true;
            }
        });
        element.addEventListener('mouseup', (e) => {
            e.preventDefault();
            keys[key] = false;
        });
        element.addEventListener('mouseleave', () => {
            keys[key] = false;
        });
    }
    setupTouchButton(btnLeft, 'left');
    setupTouchButton(btnRight, 'right');

    // 防止触摸时页面滚动
    document.addEventListener('touchmove', (e) => {
        if (e.target.closest('canvas') || e.target.closest('.control-btn')) {
            e.preventDefault();
        }
    }, { passive: false });

    // ---------- 辅助 ----------
    function dist(a, b) {
        return Math.hypot(a.x - b.x, a.y - b.y);
    }

    // 文字怪物池
    const monsterTexts = [
        '我好累', '好想休息', '好困', '想睡觉', '作业还没做',
        '作业什么时候要交', '实习怎么办', '科二科三考了吗?',
        '大学了也得认真学习', '在干什么?', '吃什么?',
        '怎么这么久不找爸爸妈妈聊天?',
        '周末有没有出去玩?', '这么晚才起床?', '昨天几点睡的?',
        '什么时候放假?', '车票订好了吗?', '为什么又不回我消息?',
        '别天天待宿舍躺着'
    ];

    function spawnMonster() {
        const angle = Math.random() * Math.PI * 2;
        const spawnDist = 500 + Math.random() * 200;
        const x = game.player.x + Math.cos(angle) * spawnDist;
        const y = game.player.y + Math.sin(angle) * spawnDist * 0.6;
        const text = monsterTexts[Math.floor(Math.random() * monsterTexts.length)];
        return {
            x, y,
            radius: 12 + Math.random() * 14,
            speed: 0.25,
            wobble: Math.random() * 100,
            text: text,
            color: `hsl(${Math.random() * 60 + 300}, 80%, ${60 + Math.random() * 30}%)`,
            alpha: 0,
            seed: Math.random() * 1000,
        };
    }

    function startGame() {
        gameState = STATE.PLAYING;
        document.getElementById('restartBtn').style.display = 'none';
        // 显示触摸按钮
        btnLeft.style.display = 'flex';
        btnRight.style.display = 'flex';
        game.player.x = 0;
        game.player.y = 0;
        game.gate.x = 350;
        game.gate.y = 0;
        game.monsters = [];
        game.isGameOver = false;
        game.gameOverAlpha = 0;
        game.currentLightRadius = 220;
        game.monsterSpawnTimer = 10;
        game.groundOffset = 0;
        game.leftHoldTime = 0;
        game.psycheStage = 0;
        game.prevPsycheStage = -1;
        game.psycheAlpha = 0;
        game.psycheDisplayTimer = 0;
        game.floatPhase = 0;
        game.arrowFlowOffset = 0;
        keys.left = false;
        keys.right = false;
        keys.up = false;
        keys.down = false;
        deathCause = 'monster';
        canvas.style.cursor = 'none';
    }

    // ---------- 更新 ----------
    function update() {
        if (gameState === STATE.MENU) return;
        if (gameState === STATE.GAMEOVER) {
            game.gameOverAlpha = Math.min(1, game.gameOverAlpha + 0.02);
            return;
        }

        const player = game.player;
        const gate = game.gate;
        game.frame++;
        const deltaTime = 1 / 60;

        let moveX = 0, moveY = 0;
        if (keys.left) moveX = -game.speed;
        if (keys.right) moveX = game.speed;
        if (keys.up) moveY = -game.speed * 0.8;
        if (keys.down) moveY = game.speed * 0.8;

        game.isMoving = (moveX !== 0 || moveY !== 0);

        if (moveX > 0) { 
            player.x += moveX;
            gate.x += moveX;
        } else if (moveX < 0) {
            player.x += moveX;
        }

        if (moveY !== 0) {
            player.y += moveY;
            player.y = Math.min(game.maxY, Math.max(game.minY, player.y));
        }

        player.x = Math.min(game.maxX, Math.max(game.minX, player.x));
        gate.x = Math.min(1000, Math.max(-200, gate.x));

        if (keys.left || keys.right) {
            const dir = keys.left ? -1 : 1;
            game.groundOffset += dir * game.speed * 0.8;
        }

        const leftBound = game.minX;
        const rightBound = gate.x;
        let t = (player.x - leftBound) / (rightBound - leftBound);
        t = Math.max(0, Math.min(1, t));
        game.currentLightRadius = t * 220;

        // ----- 心理攻击时间(30s, 60s, 90s,每段显示5秒后消失) -----
        const isLeft = keys.left;
        if (isLeft) {
            game.leftHoldTime += deltaTime;
        } else {
            game.leftHoldTime = 0;
            game.psycheStage = 0;
            game.psycheDisplayTimer = 0;
        }

        if (game.psycheStage === 0 && game.leftHoldTime >= 30) {
            game.psycheStage = 1;
            game.psycheDisplayTimer = 0;
        } else if (game.psycheStage === 0 && game.leftHoldTime >= 60) {
            game.psycheStage = 2;
            game.psycheDisplayTimer = 0;
        } else if (game.psycheStage === 0 && game.leftHoldTime >= 90) {
            game.psycheStage = 3;
            game.psycheDisplayTimer = 0;
        }

        if (game.psycheStage > 0 && keys.left) {
            game.psycheDisplayTimer += deltaTime;
            if (game.psycheDisplayTimer >= 5) {
                game.psycheStage = 0;
                game.psycheDisplayTimer = 0;
            }
        }

        if (game.psycheStage > 0 && keys.left) {
            game.psycheAlpha = Math.min(0.45, game.psycheAlpha + deltaTime * 0.6);
        } else {
            game.psycheAlpha = Math.max(0, game.psycheAlpha - deltaTime * 0.8);
        }

        if (game.leftHoldTime >= 100 && game.psycheStage === 0) {
            deathCause = 'left';
            game.isGameOver = true;
            gameState = STATE.GAMEOVER;
            document.getElementById('restartBtn').style.display = 'block';
            btnLeft.style.display = 'none';
            btnRight.style.display = 'none';
            return;
        }

        if (game.isMoving) {
            const flowSpeed = Math.abs(moveX) * 0.005;
            game.arrowFlowOffset += flowSpeed * deltaTime * 60;
        }

        if (!game.isMoving) {
            game.monsterSpawnTimer--;
            if (game.monsterSpawnTimer <= 0) {
                if (game.monsters.length < 30) {
                    game.monsters.push(spawnMonster());
                }
                game.monsterSpawnTimer = game.spawnInterval;
            }
        } else {
            game.monsters = [];
            game.monsterSpawnTimer = 15;
        }

        for (let i = game.monsters.length - 1; i >= 0; i--) {
            const mon = game.monsters[i];
            if (mon.alpha < 1) {
                mon.alpha = Math.min(1, mon.alpha + deltaTime * 1.5);
            }
            if (!game.isMoving) {
                const dx = player.x - mon.x;
                const dy = player.y - mon.y;
                const d = Math.hypot(dx, dy);
                if (d > 0.5) {
                    mon.x += (dx / d) * mon.speed;
                    mon.y += (dy / d) * mon.speed * 0.8;
                }
                mon.wobble += 0.08;
                mon.x += Math.sin(mon.wobble) * 0.15;
            }

            if (dist(mon, player) < player.radius + mon.radius) {
                deathCause = 'monster';
                game.isGameOver = true;
                gameState = STATE.GAMEOVER;
                document.getElementById('restartBtn').style.display = 'block';
                btnLeft.style.display = 'none';
                btnRight.style.display = 'none';
                return;
            }

            if (Math.abs(mon.x - player.x) > 800 || Math.abs(mon.y - player.y) > 500) {
                game.monsters.splice(i, 1);
            }
        }

        if (game.isMoving) {
            game.floatPhase += deltaTime * 1.5;
        }
    }

    // ---------- 渲染 ----------
    function draw() {
        offCtx.clearRect(0, 0, DESIGN_W, DESIGN_H);
        const drawCtx = offCtx;
        const cx = DESIGN_W / 2;
        const cy = DESIGN_H / 2;
        const player = game.player;
        const gate = game.gate;
        const radius = game.currentLightRadius;

        // 背景
        const bgGrad = drawCtx.createRadialGradient(cx, cy, 10, cx, cy, 500);
        bgGrad.addColorStop(0, '#111');
        bgGrad.addColorStop(0.6, '#050505');
        bgGrad.addColorStop(1, '#000000');
        drawCtx.fillStyle = bgGrad;
        drawCtx.fillRect(0, 0, DESIGN_W, DESIGN_H);

        if (gameState === STATE.MENU) {
            const text = '昨天、今天和明天';
            drawCtx.textAlign = 'center';
            drawCtx.textBaseline = 'middle';
            drawCtx.font = '40px "Microsoft YaHei", "PingFang SC", sans-serif';
            const color = mouseOnMenu ? '#888' : '#eee';
            drawCtx.shadowBlur = 40;
            drawCtx.shadowColor = '#aaa';
            drawCtx.fillStyle = color;
            drawCtx.fillText(text, cx, cy);
            drawCtx.shadowBlur = 0;
            if (!mouseOnMenu) {
                drawCtx.fillStyle = '#555';
                drawCtx.font = '18px sans-serif';
                drawCtx.fillText('点击开始', cx, cy + 60);
            }
            // 菜单不抖动,直接绘制
            ctx.clearRect(0, 0, DESIGN_W, DESIGN_H);
            ctx.drawImage(offscreen, 0, 0);
            return;
        }

        // 地面
        const offset = game.groundOffset % 60;
        drawCtx.strokeStyle = 'rgba(80, 100, 120, 0.15)';
        drawCtx.lineWidth = 2;
        drawCtx.setLineDash([8, 16]);
        drawCtx.lineDashOffset = -offset;
        for (let y = 0; y < DESIGN_H; y += 40) {
            drawCtx.beginPath();
            drawCtx.moveTo(0, y);
            drawCtx.lineTo(DESIGN_W, y);
            drawCtx.stroke();
        }
        drawCtx.strokeStyle = 'rgba(60, 80, 100, 0.08)';
        drawCtx.lineWidth = 1;
        drawCtx.setLineDash([4, 20]);
        drawCtx.lineDashOffset = -offset * 0.3;
        for (let x = 0; x < DESIGN_W; x += 50) {
            drawCtx.beginPath();
            drawCtx.moveTo(x, 0);
            drawCtx.lineTo(x, DESIGN_H);
            drawCtx.stroke();
        }
        drawCtx.setLineDash([]);

        // 光圈
        if (radius > 0) {
            const time = Date.now() / 1000;
            const flicker = 0.8 + 0.2 * Math.sin(time * 1.5 + 0.5);
            const alphaMul = 0.7 + 0.3 * flicker;
            const fogGrad = drawCtx.createRadialGradient(cx, cy, 5, cx, cy, Math.max(radius * 1.3, 20));
            fogGrad.addColorStop(0, `rgba(200, 220, 255, ${(0.15 + radius/500) * alphaMul})`);
            fogGrad.addColorStop(0.4, `rgba(120, 150, 200, ${(0.08 + radius/600) * alphaMul})`);
            fogGrad.addColorStop(0.8, 'rgba(20, 30, 50, 0.0)');
            fogGrad.addColorStop(1, 'rgba(0,0,0,0)');
            drawCtx.fillStyle = fogGrad;
            drawCtx.beginPath();
            drawCtx.arc(cx, cy, Math.max(radius * 1.3, 20), 0, Math.PI * 2);
            drawCtx.fill();
        }

        // 光门(“美好的明天”半透明忽明忽暗)
        if (radius > 0) {
            const gx = cx + (gate.x - player.x);
            const gy = cy + (gate.y - player.y);
            const gateGlow = drawCtx.createRadialGradient(gx, gy, 5, gx, gy, 120);
            gateGlow.addColorStop(0, 'rgba(255, 255, 255, 0.4)');
            gateGlow.addColorStop(0.3, 'rgba(200, 230, 255, 0.15)');
            gateGlow.addColorStop(1, 'rgba(255, 255, 255, 0)');
            drawCtx.fillStyle = gateGlow;
            drawCtx.beginPath();
            drawCtx.arc(gx, gy, 120, 0, Math.PI * 2);
            drawCtx.fill();

            const chars = ['美', '好', '的', '明', '天'];
            const fontSize = 30;
            const charSpacing = fontSize * 0.9;
            const totalHeight = chars.length * charSpacing;
            const startY = gy - totalHeight / 2;
            drawCtx.font = `bold ${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`;
            drawCtx.textAlign = 'center';
            drawCtx.textBaseline = 'middle';
            const time = Date.now() / 1000;
            const alphaBase = 0.55 + 0.3 * Math.sin(time * 0.8 + 0.2);
            drawCtx.shadowColor = '#fff';
            drawCtx.shadowBlur = 40;

            chars.forEach((char, index) => {
                const yPos = startY + index * charSpacing;
                const waveX = Math.sin(time * 1.5 + index * 1.2) * 8;
                const waveY = Math.cos(time * 1.1 + index * 1.8) * 5;
                drawCtx.save();
                drawCtx.globalAlpha = alphaBase;
                drawCtx.translate(gx + waveX, yPos + waveY);
                drawCtx.rotate(Math.sin(time * 0.6 + index * 0.5) * 0.08);
                drawCtx.shadowBlur = 50 + Math.sin(time + index) * 10;
                drawCtx.fillStyle = '#ffffff';
                drawCtx.fillText(char, 0, 0);
                drawCtx.restore();
            });
        }

        // 光球
        let px = cx, py = cy;
        if (radius > 0) {
            const floatAmplitude = game.isMoving ? 4 : 0;
            const floatOffset = Math.sin(game.floatPhase) * floatAmplitude;
            px = cx;
            py = cy + floatOffset;
            const r = game.player.radius;
            const leftBound = game.minX;
            const rightBound = game.gate.x;
            let t = (player.x - leftBound) / (rightBound - leftBound);
            t = Math.max(0, Math.min(1, t));
            const baseBright = 0.5 + t * 0.5;
            const time = Date.now() / 1000;
            const flicker = 0.85 + 0.15 * Math.sin(time * 2.0 + 0.3);
            const bright = baseBright * flicker;
            drawCtx.shadowBlur = 0;
            drawCtx.fillStyle = `rgba(220, 235, 255, ${bright})`;
            drawCtx.beginPath();
            drawCtx.arc(px, py, r, 0, Math.PI * 2);
            drawCtx.fill();
        }

        // 整体变暗遮罩
        const leftBound = game.minX;
        const rightBound = game.gate.x;
        let t2 = (player.x - leftBound) / (rightBound - leftBound);
        t2 = Math.max(0, Math.min(1, t2));
        const darkAlpha = (1 - t2) * 0.92;
        if (darkAlpha > 0.01) {
            drawCtx.fillStyle = `rgba(0, 0, 0, ${darkAlpha})`;
            drawCtx.fillRect(0, 0, DESIGN_W, DESIGN_H);
        }

        // 怪物
        const timeNow = Date.now() / 1000;
        for (const mon of game.monsters) {
            const seed = mon.seed || 0;
            const nx = Math.sin(timeNow * 0.5 + seed * 2.0) * 6 + Math.sin(timeNow * 0.8 + seed * 3.1) * 4;
            const ny = Math.cos(timeNow * 0.6 + seed * 1.7) * 6 + Math.cos(timeNow * 0.9 + seed * 2.8) * 4;
            const wobX = Math.sin(mon.wobble) * 2;
            const wobY = Math.sin(mon.wobble * 1.3) * 3;
            const mx = cx + (mon.x - player.x) + nx + wobX;
            const my = cy + (mon.y - player.y) + ny + wobY;
            const distToPlayer = dist(mon, player);
            let alphaFactor = 1;
            if (distToPlayer < radius * 0.6) alphaFactor = 0.25;
            else if (distToPlayer < radius) alphaFactor = 0.65;
            const finalAlpha = mon.alpha * alphaFactor;
            drawCtx.save();
            drawCtx.globalAlpha = finalAlpha;
            drawCtx.font = '18px "Microsoft YaHei", "PingFang SC", sans-serif';
            drawCtx.textAlign = 'center';
            drawCtx.textBaseline = 'middle';
            drawCtx.shadowColor = mon.color;
            drawCtx.shadowBlur = 15;
            drawCtx.fillStyle = mon.color;
            const rot = Math.sin(timeNow * 0.3 + seed * 0.5) * 0.06;
            drawCtx.translate(mx, my);
            drawCtx.rotate(rot);
            drawCtx.fillText(mon.text, 0, 0);
            drawCtx.restore();
        }

        // 箭头流
        if (radius > 0) {
            const gx = cx + (gate.x - player.x);
            const gy = cy + (gate.y - player.y);
            const dx = gx - px;
            const dy = gy - py;
            const dist = Math.hypot(dx, dy);
            if (dist > 30) {
                const numArrows = 8;
                const flowOffset = game.arrowFlowOffset % 1;
                const time = Date.now() / 1000;
                for (let i = 0; i < numArrows; i++) {
                    let progress = (i / numArrows + flowOffset) % 1;
                    const ax = px + dx * progress;
                    const ay = py + dy * progress;
                    const angle = Math.atan2(dy, dx);
                    let alphaBase = 0.3;
                    if (game.isMoving) alphaBase = 0.6;
                    const flicker = 0.6 + 0.4 * Math.sin(time * 2.5 + i * 0.8);
                    const alpha = alphaBase * flicker;
                    const arrowSize = 12;
                    drawCtx.save();
                    drawCtx.translate(ax, ay);
                    drawCtx.rotate(angle);
                    drawCtx.beginPath();
                    drawCtx.moveTo(arrowSize, 0);
                    drawCtx.lineTo(-arrowSize * 0.5, -arrowSize * 0.4);
                    drawCtx.lineTo(-arrowSize * 0.5, arrowSize * 0.4);
                    drawCtx.closePath();
                    drawCtx.strokeStyle = `rgba(200, 220, 255, ${alpha})`;
                    drawCtx.lineWidth = 2;
                    drawCtx.stroke();
                    drawCtx.restore();
                }
            }
        }

        // 文字
        if (game.psycheStage >= 1 && !game.isGameOver && game.psycheAlpha > 0.01) {
            let message = '';
            if (game.psycheStage === 1) message = '又在逃避了吗?';
            else if (game.psycheStage === 2) message = '......';
            else if (game.psycheStage === 3) message = '';
            drawCtx.save();
            drawCtx.globalAlpha = game.psycheAlpha;
            drawCtx.textAlign = 'center';
            drawCtx.textBaseline = 'middle';
            drawCtx.shadowBlur = 0;
            drawCtx.fillStyle = 'rgba(255, 255, 255, 1)';
            drawCtx.font = '28px "Microsoft YaHei", "PingFang SC", sans-serif';
            drawCtx.fillText(message, DESIGN_W/2, DESIGN_H/2 - 60);
            drawCtx.restore();
        }

        // 底部提示(仅在游戏进行时)
        if (gameState === STATE.PLAYING && !game.isGameOver) {
            const bottomY = DESIGN_H - 40;
            drawCtx.save();
            drawCtx.globalAlpha = 0.4;
            drawCtx.font = '20px "Microsoft YaHei", "PingFang SC", sans-serif';
            drawCtx.textAlign = 'center';
            drawCtx.textBaseline = 'middle';
            drawCtx.fillStyle = '#aaa';
            drawCtx.fillText('往前', DESIGN_W/2 - 20, bottomY);
            const arrowX = DESIGN_W/2 + 25;
            const arrowSize = 16;
            drawCtx.beginPath();
            drawCtx.moveTo(arrowX + arrowSize, bottomY);
            drawCtx.lineTo(arrowX - arrowSize*0.4, bottomY - arrowSize*0.6);
            drawCtx.lineTo(arrowX - arrowSize*0.4, bottomY + arrowSize*0.6);
            drawCtx.closePath();
            drawCtx.fillStyle = '#aaa';
            drawCtx.fill();
            drawCtx.restore();
        }

        // 游戏结束
        if (gameState === STATE.GAMEOVER) {
            drawCtx.fillStyle = 'rgba(0, 0, 0, 0.85)';
            drawCtx.fillRect(0, 0, DESIGN_W, DESIGN_H);
            const alpha = game.gameOverAlpha;
            if (alpha > 0.01) {
                drawCtx.save();
                drawCtx.globalAlpha = alpha;
                drawCtx.textAlign = 'center';
                drawCtx.textBaseline = 'middle';
                drawCtx.shadowBlur = 0;
                drawCtx.fillStyle = '#eee';
                drawCtx.font = '28px "Microsoft YaHei", "PingFang SC", sans-serif';
                let msg = '';
                if (deathCause === 'monster') {
                    msg = '你为什么就不能再努力一点?';
                } else if (deathCause === 'left') {
                    msg = '好吧,这也是一种选择。';
                }
                drawCtx.fillText(msg, DESIGN_W/2, DESIGN_H/2);
                drawCtx.restore();
            }
        }

        // ---------- 像素抖动 ----------
        const scale = 0.6;
        const sw = Math.floor(DESIGN_W * scale);
        const sh = Math.floor(DESIGN_H * scale);
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = sw;
        tempCanvas.height = sh;
        const tempCtx = tempCanvas.getContext('2d');
        tempCtx.drawImage(offscreen, 0, 0, sw, sh);

        ctx.clearRect(0, 0, DESIGN_W, DESIGN_H);
        ctx.imageSmoothingEnabled = false;
        const jitterX = (Math.random() - 0.5) * 4;
        const jitterY = (Math.random() - 0.5) * 4;
        ctx.drawImage(tempCanvas, jitterX, jitterY, DESIGN_W, DESIGN_H);
    }

    // ---------- 循环 ----------
    function gameLoop() {
        update();
        draw();
        requestAnimationFrame(gameLoop);
    }

    // ---------- 重启(回到菜单) ----------
    function restartGame() {
        gameState = STATE.MENU;
        document.getElementById('restartBtn').style.display = 'none';
        btnLeft.style.display = 'none';
        btnRight.style.display = 'none';
        canvas.style.cursor = 'default';
        game.player.x = 0;
        game.player.y = 0;
        game.gate.x = 350;
        game.gate.y = 0;
        game.monsters = [];
        game.isGameOver = false;
        game.gameOverAlpha = 0;
        game.currentLightRadius = 220;
        game.monsterSpawnTimer = 10;
        game.groundOffset = 0;
        game.leftHoldTime = 0;
        game.psycheStage = 0;
        game.prevPsycheStage = -1;
        game.psycheAlpha = 0;
        game.psycheDisplayTimer = 0;
        game.floatPhase = 0;
        game.arrowFlowOffset = 0;
        keys.left = false;
        keys.right = false;
        keys.up = false;
        keys.down = false;
        deathCause = 'monster';
    }

    document.getElementById('restartBtn').addEventListener('click', restartGame);

    // ---------- 启动 ----------
    gameLoop();
</script>
</body>
</html>

Game Source: 昨天、今天和明天

Creator: LuckyKoala29

Libraries: none

Complexity: complex (817 lines, 30.5 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-luckykoala29" to link back to the original. Then publish at arcadelab.ai/publish.