Geometry Dash Style - бег с препятствиями
by PrismBolt10548 lines20.6 KB
<!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 Style - бег с препятствиями</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
background: #0a0f1e;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Courier New', 'Monaco', monospace;
touch-action: pan-x pan-y; /* разрешаем жесты, но canvas сам перехватит тапы */
}
.game-container {
position: relative;
box-shadow: 0 20px 35px rgba(0,0,0,0.5);
border-radius: 20px;
overflow: hidden;
border: 2px solid #2affb6;
}
canvas {
display: block;
width: 100%;
height: auto;
background: #0b1120;
cursor: pointer;
touch-action: manipulation;
}
.info {
position: absolute;
top: 12px;
left: 0;
right: 0;
text-align: center;
pointer-events: none;
z-index: 10;
font-weight: bold;
text-shadow: 0 0 5px #00ffff;
}
.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;
letter-spacing: 2px;
color: #ffe484;
font-family: monospace;
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;
font-family: inherit;
cursor: pointer;
box-shadow: 0 5px 0 #8b1e2a;
transition: 0.05s linear;
z-index: 20;
pointer-events: auto;
letter-spacing: 1px;
}
.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: #bbffdd;
pointer-events: none;
font-weight: bold;
}
@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() {
// ------ НАСТРОЙКИ ИГРЫ (Geometry Dash стиль) ------
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreSpan = document.getElementById('scoreDisplay');
// ЛОГИЧЕСКИЕ РАЗМЕРЫ (фиксированное поле для удобства физики)
const W = 450;
const H = 650;
canvas.width = W;
canvas.height = H;
// Параметры мира
const GROUND_Y = H - 60; // уровень земли (верхняя граница земли)
const PLAYER_SIZE = 32; // размер квадратного игрока
const PLAYER_FIXED_X = 90; // игрок статичен по X, мир движется на него
// Физика (используем секундные величины, delta time)
const GRAVITY = 1600; // пикселей/сек²
const JUMP_POWER = -400; // начальная скорость прыжка (вверх)
// Препятствия
const OBSTACLE_WIDTH = 28;
const OBSTACLE_HEIGHT = 32;
const BASE_SPEED = 230; // скорость движения препятствий вправо (пикс/сек)
// Генерация препятствий
const MIN_GAP_SEC = 0.9; // мин интервал между препятствиями (сек)
const MAX_GAP_SEC = 1.5; // макс интервал (сек)
// состояние игры
let gameRunning = true;
let score = 0;
let frameRequest;
let lastTimestamp = 0;
// динамические объекты
let player = {
x: PLAYER_FIXED_X,
y: GROUND_Y - PLAYER_SIZE, // стоя на земле
vy: 0,
width: PLAYER_SIZE,
height: PLAYER_SIZE,
isOnGround: true
};
let obstacles = []; // каждый объект: { x, width, height, counted }
// таймер для генерации
let timeToNextObstacle = 0; // секунд до следующего препятствия
// анимационные эффекты (вспышка при смерти)
let deathFlash = 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.6; // первый блок появится быстро
}
// добавить новое препятствие
function addObstacle() {
obstacles.push({
x: W, // появляется справа за границей
width: OBSTACLE_WIDTH,
height: OBSTACLE_HEIGHT,
counted: false
});
}
// обработка столкновений (прямоугольник с прямоугольником)
function checkCollision() {
// границы игрока
const playerLeft = player.x;
const playerRight = player.x + PLAYER_SIZE;
const playerTop = player.y;
const playerBottom = player.y + PLAYER_SIZE;
for (let i = 0; i < obstacles.length; i++) {
const obs = obstacles[i];
const obsLeft = obs.x;
const obsRight = obs.x + obs.width;
const obsTop = GROUND_Y - OBSTACLE_HEIGHT; // препятствие стоит на земле
const obsBottom = GROUND_Y;
if (playerRight > obsLeft && playerLeft < obsRight &&
playerBottom > obsTop && playerTop < obsBottom) {
return true; // столкновение
}
}
return false;
}
// обновление очков: когда препятствие проходит левее игрока и не посчитано
function updateScoreFromObstacles() {
for (let i = 0; i < obstacles.length; i++) {
const obs = obstacles[i];
if (!obs.counted && (obs.x + obs.width) < player.x) {
obs.counted = true;
score += 10;
updateScoreUI();
}
}
}
// прыжок (вызывается по тапу или клику)
function jump() {
if (!gameRunning) return;
// проверяем, стоит ли игрок на земле (маленький допуск)
const epsilon = 3;
if (player.y + PLAYER_SIZE >= GROUND_Y - epsilon && player.vy >= -50) {
player.vy = JUMP_POWER;
player.isOnGround = false;
// эффект (звука нет, но для отдачи)
}
}
// обновление физики и положения (dt - секунды)
function updatePhysics(dtSec) {
if (dtSec > 0.03) dtSec = 0.03; // ограничение
// вертикальное движение
player.vy += GRAVITY * dtSec;
player.y += player.vy * dtSec;
// проверка земли
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;
}
}
// обновление движения препятствий и генерация
function updateObstacles(dtSec) {
// 1) двигаем все препятствия влево
for (let i = 0; i < obstacles.length; i++) {
obstacles[i].x -= BASE_SPEED * dtSec;
}
// удаляем вышедшие за левую границу
obstacles = obstacles.filter(obs => obs.x + obs.width > 0);
// 2) генерация новых препятствий
if (gameRunning) {
if (timeToNextObstacle <= 0) {
// добавляем новый блок
addObstacle();
// случайная задержка до следующего
const gap = MIN_GAP_SEC + Math.random() * (MAX_GAP_SEC - MIN_GAP_SEC);
timeToNextObstacle = gap;
} else {
timeToNextObstacle -= dtSec;
}
}
}
// ОСНОВНОЙ ЦИКЛ ОБНОВЛЕНИЯ (логика)
function updateGame(dtSec) {
if (!gameRunning) return;
// запоминаем предыдущее состояние для проверки смерти
updatePhysics(dtSec);
updateObstacles(dtSec);
updateScoreFromObstacles();
// проверка столкновения после всех изменений
const collided = checkCollision();
if (collided) {
gameRunning = false;
deathFlash = 1.0; // эффект красной вспышки
return;
}
// Дополнительная проверка: если игрок упал ниже земли (по багу) - тоже смерть
if (player.y + PLAYER_SIZE > GROUND_Y + 10) {
gameRunning = false;
deathFlash = 1.0;
}
}
// ---------- ОТРИСОВКА (стиль Geometry Dash неон) ----------
function drawBackground() {
// градиентное небо
const grad = ctx.createLinearGradient(0, 0, 0, H);
grad.addColorStop(0, "#0a0f2a");
grad.addColorStop(1, "#131b3c");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// декоративные линии (как рельсы)
ctx.beginPath();
ctx.strokeStyle = "#2effc6";
ctx.lineWidth = 2;
ctx.setLineDash([12, 16]);
for (let i = 0; i < 4; i++) {
const yLine = GROUND_Y - 15 + i * 12;
ctx.beginPath();
ctx.moveTo(0, yLine);
ctx.lineTo(W, yLine);
ctx.stroke();
}
ctx.setLineDash([]);
// частицы/звезды
ctx.fillStyle = "#fffbd0";
for (let i = 0; i < 120; i++) {
if (i%2 === 0) continue;
let sx = (i * 131) % W;
let sy = (i * 253) % (GROUND_Y-40);
ctx.globalAlpha = 0.5 + Math.sin(Date.now() * 0.002 + i) * 0.3;
ctx.fillRect(sx, sy, 2, 2);
}
ctx.globalAlpha = 1;
}
function drawGround() {
// земля с текстурой "кирпичи / металл"
ctx.fillStyle = "#252b4a";
ctx.fillRect(0, GROUND_Y, W, H - GROUND_Y + 5);
ctx.fillStyle = "#ffbe4d";
for (let i = 0; i < W; i += 25) {
ctx.fillRect(i, GROUND_Y - 4, 12, 5);
}
ctx.fillStyle = "#fbff47";
for (let i = 6; i < W; i += 30) {
ctx.fillRect(i, GROUND_Y - 2, 8, 4);
}
// блестящая кайма
ctx.strokeStyle = "#0effd0";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(0, GROUND_Y);
ctx.lineTo(W, GROUND_Y);
ctx.stroke();
}
function drawPlayer() {
// игрок - глэйдиаторский куб с глазами (Geometry dash стиль)
let yPos = player.y;
// тень
ctx.shadowBlur = 8;
ctx.shadowColor = "#00ffe0";
ctx.fillStyle = "#fee440";
ctx.fillRect(player.x, yPos, PLAYER_SIZE, PLAYER_SIZE);
ctx.fillStyle = "#f15b2b";
ctx.fillRect(player.x+5, yPos+6, 6, 8);
ctx.fillRect(player.x+PLAYER_SIZE-11, yPos+6, 6, 8);
ctx.fillStyle = "#000000";
ctx.fillRect(player.x+8, yPos+18, 6, 8);
ctx.fillRect(player.x+18, yPos+18, 6, 8);
// динамическая усмешка при прыжке
ctx.beginPath();
if (player.vy < -50) {
ctx.arc(player.x+16, yPos+26, 8, 0.1, Math.PI - 0.1);
} else {
ctx.arc(player.x+16, yPos+26, 8, 0, Math.PI);
}
ctx.strokeStyle = "#1e2a3e";
ctx.lineWidth = 2;
ctx.stroke();
// "рожки"
ctx.fillStyle = "#ffaa33";
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 obsX = obs.x;
const obsY = GROUND_Y - OBSTACLE_HEIGHT;
// градиент препятствия (шипованный стиль)
const grad = ctx.createLinearGradient(obsX, obsY, obsX+OBSTACLE_WIDTH, obsY+OBSTACLE_HEIGHT);
grad.addColorStop(0, "#e74c3c");
grad.addColorStop(1, "#c0392b");
ctx.fillStyle = grad;
ctx.fillRect(obsX, obsY, OBSTACLE_WIDTH, OBSTACLE_HEIGHT);
ctx.fillStyle = "#f1c40f";
ctx.fillRect(obsX+4, obsY-4, OBSTACLE_WIDTH-8, 6);
ctx.fillStyle = "#2ecc71";
for (let i = 0; i < 3; i++) {
ctx.fillRect(obsX+4 + i*7, obsY+8, 4, 12);
}
// шипы наверху
ctx.fillStyle = "#f39c12";
ctx.beginPath();
ctx.moveTo(obsX+5, obsY-4);
ctx.lineTo(obsX+10, obsY-10);
ctx.lineTo(obsX+15, obsY-4);
ctx.fill();
ctx.beginPath();
ctx.moveTo(obsX+15, obsY-4);
ctx.lineTo(obsX+20, obsY-10);
ctx.lineTo(obsX+25, obsY-4);
ctx.fill();
}
}
function drawEffectsAndUI() {
// вспышка при смерти
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.shadowBlur = 0;
ctx.fillStyle = "#ffd966";
ctx.shadowColor = "black";
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 28 'Courier New'";
ctx.fillStyle = "#ffe484";
ctx.shadowBlur = 0;
ctx.fillText("⚡"+Math.floor(score), 20, 58);
}
function draw() {
drawBackground();
drawGround();
drawObstacles();
drawPlayer();
drawEffectsAndUI();
}
// ----- главный игровой цикл с delta time -----
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 handleJumpStart(e) {
e.preventDefault();
jump();
}
function attachEvents() {
// для мобильных и пк
canvas.addEventListener('touchstart', handleJumpStart, { passive: false });
canvas.addEventListener('mousedown', handleJumpStart);
// также клик по кнопке рестарта не должен вызывать прыжок
const restartBtn = document.getElementById('restartButton');
restartBtn.addEventListener('click', (e) => {
e.stopPropagation();
resetGame();
gameRunning = true;
deathFlash = 0;
// обнуляем счетчик генерации, чтобы резко не спамило препятствиями
timeToNextObstacle = 0.5;
obstacles = [];
player.y = GROUND_Y - PLAYER_SIZE;
player.vy = 0;
player.isOnGround = true;
score = 0;
updateScoreUI();
});
}
// предотвращаем зуминг двойным тапом на canvas
canvas.addEventListener('touchstart', (e) => {
if (e.touches.length > 1) e.preventDefault();
}, { passive: false });
// ИНИЦИАЛИЗАЦИЯ
resetGame();
attachEvents();
// старт цикла
lastFrameTime = 0;
frameRequest = requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>Game Source: Geometry Dash Style - бег с препятствиями
Creator: PrismBolt10
Libraries: none
Complexity: complex (548 lines, 20.6 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-style-prismbolt10" to link back to the original. Then publish at arcadelab.ai/publish.