🎮ArcadeLab

Geometry Dash — Бег с препятствиями

by PrismBolt10
495 lines17.8 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
    <title>Geometry Dash — Бег с препятствиями</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
        }
        body {
            background: #1a4d2a;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Courier New', monospace;
            touch-action: pan-x pan-y;
        }
        .game-container {
            position: relative;
            box-shadow: 0 20px 35px rgba(0,0,0,0.5);
            border-radius: 20px;
            overflow: hidden;
            border: 2px solid #8bc34a;
        }
        canvas {
            display: block;
            width: 100%;
            height: auto;
            cursor: pointer;
            touch-action: manipulation;
        }
        .info {
            position: absolute;
            top: 12px;
            left: 0;
            right: 0;
            text-align: center;
            pointer-events: none;
            z-index: 10;
        }
        .score-board {
            display: inline-block;
            background: rgba(0,0,0,0.7);
            backdrop-filter: blur(4px);
            padding: 6px 20px;
            border-radius: 40px;
            font-size: 1.7rem;
            font-weight: bold;
            color: #ffe484;
            border: 1px solid #ffcc44;
        }
        .restart-btn {
            position: absolute;
            bottom: 25px;
            left: 50%;
            transform: translateX(-50%);
            background: #ff4757;
            color: white;
            border: none;
            padding: 10px 24px;
            border-radius: 50px;
            font-weight: bold;
            font-size: 1.1rem;
            cursor: pointer;
            box-shadow: 0 5px 0 #8b1e2a;
            transition: 0.05s linear;
            z-index: 20;
        }
        .restart-btn:active {
            transform: translateX(-50%) translateY(3px);
            box-shadow: 0 2px 0 #8b1e2a;
        }
        .instruction {
            position: absolute;
            bottom: 20px;
            right: 12px;
            background: rgba(0,0,0,0.5);
            padding: 4px 12px;
            border-radius: 50px;
            font-size: 0.7rem;
            color: #ffffbb;
            pointer-events: none;
        }
        @media (max-width: 600px) {
            .score-board { font-size: 1.3rem; padding: 4px 16px; }
            .restart-btn { padding: 8px 20px; font-size: 1rem; bottom: 18px; }
        }
    </style>
</head>
<body>
<div class="game-container">
    <canvas id="gameCanvas" width="450" height="650"></canvas>
    <div class="info"><div class="score-board" id="scoreDisplay">0</div></div>
    <button class="restart-btn" id="restartButton">⟳ СТАРТ / ЗАНОВО</button>
    <div class="instruction">⬆️ ТАПНИ, ЧТОБЫ ПРЫГНУТЬ</div>
</div>

<script>
    (function() {
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreSpan = document.getElementById('scoreDisplay');
        
        const W = 450, H = 650;
        canvas.width = W; canvas.height = H;
        
        // Глобальные настройки
        const GROUND_Y = H - 60;
        const PLAYER_SIZE = 32;
        const PLAYER_FIXED_X = 90;
        const GRAVITY = 1600;
        const JUMP_POWER = -560;
        const BASE_SPEED = 240;   // скорость бега (иллюзия движения)
        
        // Разные препятствия
        const OBSTACLE_TYPES = {
            BLOCK: { w: 28, h: 32, color1: "#e74c3c", color2: "#c0392b", yOffset: 0, name: "block" },
            SPIKE: { w: 32, h: 24, color1: "#f39c12", color2: "#e67e22", yOffset: 8, name: "spike" },
            DOUBLE: { w: 58, h: 32, color1: "#9b59b6", color2: "#8e44ad", yOffset: 0, name: "double" },
            FLY:    { w: 28, h: 28, color1: "#1abc9c", color2: "#16a085", yOffset: -35, name: "fly" } // летающий
        };
        
        let gameRunning = true;
        let score = 0;
        let frameRequest;
        let deathFlash = 0;
        let timeToNextObstacle = 0;
        let obstacles = [];
        
        // Игрок
        let player = {
            x: PLAYER_FIXED_X,
            y: GROUND_Y - PLAYER_SIZE,
            vy: 0,
            width: PLAYER_SIZE,
            height: PLAYER_SIZE,
            isOnGround: true,
            legWobble: 0   // для анимации бега
        };
        
        // Эффекты движения
        let scrollOffset = 0;
        
        function updateScoreUI() { scoreSpan.innerText = Math.floor(score); }
        
        function resetGame() {
            gameRunning = true;
            score = 0;
            updateScoreUI();
            obstacles = [];
            player.y = GROUND_Y - PLAYER_SIZE;
            player.vy = 0;
            player.isOnGround = true;
            deathFlash = 0;
            timeToNextObstacle = 0.5;
            scrollOffset = 0;
        }
        
        // Генерация случайного препятствия
        function addRandomObstacle() {
            const types = Object.values(OBSTACLE_TYPES);
            const type = types[Math.floor(Math.random() * types.length)];
            let width = type.w;
            let height = type.h;
            let yPos = GROUND_Y - height + type.yOffset;
            if (type.name === "fly") yPos = GROUND_Y - 50 - height; // летает выше
            obstacles.push({
                x: W,
                width: width,
                height: height,
                y: yPos,
                type: type,
                counted: false
            });
            // Иногда добавляем пару (для двойного)
            if (type.name === "double") {
                // второй блок рядом
                obstacles.push({
                    x: W + 35,
                    width: 28,
                    height: 32,
                    y: GROUND_Y - 32,
                    type: OBSTACLE_TYPES.BLOCK,
                    counted: false
                });
            }
        }
        
        // Проверка столкновения с учётом разных форм
        function checkCollision() {
            const pLeft = player.x, pRight = player.x + PLAYER_SIZE;
            const pTop = player.y, pBottom = player.y + PLAYER_SIZE;
            for (let obs of obstacles) {
                const oLeft = obs.x, oRight = obs.x + obs.width;
                const oTop = obs.y, oBottom = obs.y + obs.height;
                if (pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom) {
                    return true;
                }
            }
            return false;
        }
        
        function updateScoreFromObstacles() {
            for (let obs of obstacles) {
                if (!obs.counted && (obs.x + obs.width) < player.x) {
                    obs.counted = true;
                    score += 10;
                    updateScoreUI();
                }
            }
        }
        
        function jump() {
            if (!gameRunning) return;
            if (player.y + PLAYER_SIZE >= GROUND_Y - 3 && player.vy >= -50) {
                player.vy = JUMP_POWER;
                player.isOnGround = false;
            }
        }
        
        function updatePhysics(dt) {
            if (dt > 0.03) dt = 0.03;
            player.vy += GRAVITY * dt;
            player.y += player.vy * dt;
            if (player.y + PLAYER_SIZE >= GROUND_Y) {
                player.y = GROUND_Y - PLAYER_SIZE;
                player.vy = 0;
                player.isOnGround = true;
            }
            if (player.y < 0) { player.y = 0; if (player.vy < 0) player.vy = 0; }
            // Анимация ног при беге
            if (gameRunning && player.isOnGround) {
                player.legWobble = (player.legWobble + dt * 18) % (Math.PI * 2);
            } else {
                player.legWobble = 0;
            }
        }
        
        function updateObstacles(dt) {
            // Движение препятствий (создаёт иллюзию бега персонажа)
            for (let obs of obstacles) {
                obs.x -= BASE_SPEED * dt;
            }
            obstacles = obstacles.filter(obs => obs.x + obs.width > 0);
            
            if (gameRunning) {
                if (timeToNextObstacle <= 0) {
                    addRandomObstacle();
                    timeToNextObstacle = 0.9 + Math.random() * 0.9;
                } else {
                    timeToNextObstacle -= dt;
                }
            }
            // Эффект бегущей дороги
            scrollOffset = (scrollOffset + BASE_SPEED * dt) % 40;
        }
        
        function updateGame(dt) {
            if (!gameRunning) return;
            updatePhysics(dt);
            updateObstacles(dt);
            updateScoreFromObstacles();
            if (checkCollision()) {
                gameRunning = false;
                deathFlash = 1.0;
            }
            if (player.y + PLAYER_SIZE > GROUND_Y + 15) {
                gameRunning = false;
                deathFlash = 1.0;
            }
        }
        
        // ----- ОТРИСОВКА С ИЛЛЮЗИЕЙ ДВИЖЕНИЯ -----
        function drawSkyAndGround() {
            // Небо
            const grad = ctx.createLinearGradient(0, 0, 0, GROUND_Y);
            grad.addColorStop(0, "#7ec8ff");
            grad.addColorStop(1, "#3c9eff");
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, W, GROUND_Y);
            // Облака (плывут)
            ctx.fillStyle = "rgba(255,255,240,0.9)";
            let time = Date.now() / 1000;
            for (let i = 0; i < 3; i++) {
                let cloudX = (i * 187 + time * 12) % (W + 150) - 75;
                let cloudY = 40 + i * 60;
                ctx.beginPath();
                ctx.ellipse(cloudX, cloudY, 35, 25, 0, 0, Math.PI*2);
                ctx.ellipse(cloudX+25, cloudY-8, 30, 22, 0, 0, Math.PI*2);
                ctx.ellipse(cloudX-20, cloudY-5, 28, 22, 0, 0, Math.PI*2);
                ctx.fill();
            }
            // Солнце
            ctx.fillStyle = "#fff9c4";
            ctx.shadowBlur = 12;
            ctx.shadowColor = "#ffdd77";
            ctx.beginPath();
            ctx.arc(W-45, 55, 28, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = "#ffeb3b";
            ctx.beginPath();
            ctx.arc(W-45, 55, 22, 0, Math.PI*2);
            ctx.fill();
            ctx.shadowBlur = 0;
        }
        
        function drawMovingGround() {
            // Зелёная земля
            ctx.fillStyle = "#4caf50";
            ctx.fillRect(0, GROUND_Y, W, H-GROUND_Y);
            // Бегущие полосы (иллюзия движения)
            ctx.fillStyle = "#81c784";
            for (let i = -40; i < W+40; i += 40) {
                let x = i + scrollOffset;
                ctx.fillRect(x, GROUND_Y-4, 18, 6);
            }
            ctx.fillStyle = "#2e7d32";
            for (let i = -30; i < W+40; i += 45) {
                let x = i + scrollOffset*1.2;
                ctx.fillRect(x, GROUND_Y-8, 8, 8);
            }
            // Травинки
            ctx.fillStyle = "#8bc34a";
            for (let i = 0; i < W; i+=15) {
                ctx.fillRect(i, GROUND_Y-3, 2, 6);
            }
            ctx.strokeStyle = "#b9f6ca";
            ctx.lineWidth = 3;
            ctx.beginPath();
            ctx.moveTo(0, GROUND_Y);
            ctx.lineTo(W, GROUND_Y);
            ctx.stroke();
        }
        
        function drawPlayer() {
            let yPos = player.y;
            ctx.shadowBlur = 6;
            ctx.shadowColor = "#00bcd4";
            // Тело
            ctx.fillStyle = "#ffd54f";
            ctx.fillRect(player.x, yPos, PLAYER_SIZE, PLAYER_SIZE);
            // Анимация ног (бег)
            if (player.isOnGround && gameRunning) {
                let legOffset = Math.sin(player.legWobble) * 4;
                ctx.fillStyle = "#ff8a65";
                ctx.fillRect(player.x+5, yPos+PLAYER_SIZE-8, 7, 8+legOffset);
                ctx.fillRect(player.x+20, yPos+PLAYER_SIZE-8, 7, 8-legOffset);
            } else {
                ctx.fillStyle = "#ff8a65";
                ctx.fillRect(player.x+5, yPos+24, 7, 8);
                ctx.fillRect(player.x+20, yPos+24, 7, 8);
            }
            // Глаза
            ctx.fillStyle = "#3e2723";
            ctx.fillRect(player.x+8, yPos+12, 6, 7);
            ctx.fillRect(player.x+18, yPos+12, 6, 7);
            ctx.fillStyle = "white";
            ctx.fillRect(player.x+9, yPos+13, 2, 3);
            ctx.fillRect(player.x+19, yPos+13, 2, 3);
            // Улыбка
            ctx.beginPath();
            ctx.arc(player.x+16, yPos+22, 7, 0.1, Math.PI-0.1);
            ctx.strokeStyle = "#3e2723";
            ctx.lineWidth = 2;
            ctx.stroke();
            // Рожки
            ctx.fillStyle = "#ffb74d";
            ctx.fillRect(player.x+4, yPos-5, 6, 8);
            ctx.fillRect(player.x+22, yPos-5, 6, 8);
            ctx.shadowBlur = 0;
        }
        
        function drawObstacles() {
            for (let obs of obstacles) {
                const type = obs.type;
                const x = obs.x, y = obs.y;
                const grad = ctx.createLinearGradient(x, y, x+obs.width, y+obs.height);
                grad.addColorStop(0, type.color1);
                grad.addColorStop(1, type.color2);
                ctx.fillStyle = grad;
                ctx.fillRect(x, y, obs.width, obs.height);
                // Детали в зависимости от типа
                if (type.name === "spike") {
                    ctx.fillStyle = "#f1c40f";
                    for (let i=0; i<3; i++) {
                        ctx.beginPath();
                        ctx.moveTo(x+5+i*10, y-5);
                        ctx.lineTo(x+10+i*10, y-12);
                        ctx.lineTo(x+15+i*10, y-5);
                        ctx.fill();
                    }
                } else if (type.name === "fly") {
                    ctx.fillStyle = "#ecf0f1";
                    ctx.beginPath();
                    ctx.moveTo(x+obs.width/2, y-8);
                    ctx.lineTo(x+obs.width-4, y-2);
                    ctx.lineTo(x+4, y-2);
                    ctx.fill();
                } else if (type.name === "double") {
                    ctx.fillStyle = "#f1c40f";
                    ctx.fillRect(x+5, y+5, obs.width-10, 6);
                }
                // Глаза для всех
                ctx.fillStyle = "white";
                ctx.fillRect(x+5, y+8, 6, 6);
                ctx.fillRect(x+obs.width-11, y+8, 6, 6);
                ctx.fillStyle = "#000";
                ctx.fillRect(x+7, y+10, 3, 3);
                ctx.fillRect(x+obs.width-9, y+10, 3, 3);
            }
        }
        
        function drawEffects() {
            if (deathFlash > 0) {
                ctx.fillStyle = `rgba(255, 50, 50, ${deathFlash * 0.7})`;
                ctx.fillRect(0, 0, W, H);
                deathFlash -= 0.05;
                if (deathFlash < 0) deathFlash = 0;
            }
            if (!gameRunning) {
                ctx.font = "bold 28 monospace";
                ctx.fillStyle = "#ffd966";
                ctx.shadowBlur = 0;
                ctx.fillText("GAME OVER", W/2-100, H/2-50);
                ctx.font = "18px monospace";
                ctx.fillStyle = "#aaffdd";
                ctx.fillText("нажми кнопку внизу", W/2-110, H/2+20);
            }
            ctx.font = "bold 26 'Courier New'";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("🏃 "+Math.floor(score), 18, 58);
        }
        
        function draw() {
            drawSkyAndGround();
            drawMovingGround();
            drawObstacles();
            drawPlayer();
            drawEffects();
        }
        
        // ----- ЦИКЛ И УПРАВЛЕНИЕ -----
        let lastFrameTime = 0;
        function gameLoop(nowMs) {
            if (!lastFrameTime) {
                lastFrameTime = nowMs;
                frameRequest = requestAnimationFrame(gameLoop);
                draw();
                return;
            }
            let dt = Math.min(0.033, (nowMs - lastFrameTime) / 1000);
            if (dt <= 0) {
                lastFrameTime = nowMs;
                frameRequest = requestAnimationFrame(gameLoop);
                draw();
                return;
            }
            if (gameRunning) updateGame(dt);
            draw();
            lastFrameTime = nowMs;
            frameRequest = requestAnimationFrame(gameLoop);
        }
        
        function handleJump(e) { e.preventDefault(); jump(); }
        function attachEvents() {
            canvas.addEventListener('touchstart', handleJump, { passive: false });
            canvas.addEventListener('mousedown', handleJump);
            document.getElementById('restartButton').addEventListener('click', (e) => {
                e.stopPropagation();
                resetGame();
                gameRunning = true;
                deathFlash = 0;
                timeToNextObstacle = 0.5;
                obstacles = [];
                player.y = GROUND_Y - PLAYER_SIZE;
                player.vy = 0;
                score = 0;
                updateScoreUI();
            });
        }
        
        canvas.addEventListener('touchstart', (e) => { if(e.touches.length>1) e.preventDefault(); }, { passive: false });
        resetGame();
        attachEvents();
        frameRequest = requestAnimationFrame(gameLoop);
    })();
</script>
</body>
</html>

Game Source: Geometry Dash — Бег с препятствиями

Creator: PrismBolt10

Libraries: none

Complexity: complex (495 lines, 17.8 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: geometry-dash-prismbolt10" to link back to the original. Then publish at arcadelab.ai/publish.