Как сделать игру змейку в javascript

Как сделать игру змейку в javascript

Простейшая реализация змейки на JavaScript требует минимального набора инструментов: HTML5-канвас, чистый JavaScript без сторонних библиотек и базовые знания работы с DOM и событиями клавиатуры. Такой проект подойдёт для практики начинающим разработчикам, которые хотят освоить анимацию, работу с массивами и обработку событий.

Игровое поле удобно реализовать через элемент <canvas>. Размер холста задаётся в пикселях, например, 400×400. Каждая ячейка змейки – это квадрат фиксированного размера, например, 20×20 пикселей. Вся логика перемещения основана на пересчёте координат головы и добавлении/удалении элементов из массива, представляющего тело змейки.

Для управления движением достаточно обработать событие keydown. Важно фильтровать некорректные направления, чтобы змейка не могла развернуться в противоположную сторону моментально. Игровой цикл строится на setInterval или requestAnimationFrame – второй вариант предпочтительнее из-за лучшей синхронизации с частотой обновления экрана.

Появление еды реализуется через генерацию случайных координат внутри границ поля с проверкой, чтобы еда не появилась внутри тела змейки. При совпадении координат головы змейки и еды – еда исчезает, хвост не обрезается, и длина увеличивается. Столкновение с границами или с собственным телом должно вызывать завершение игры и обнуление состояния.

Создание такой игры позволяет на практике отработать ключевые концепции JavaScript: работу с циклами, массивами, событиями, а также взаимодействие с канвасом и отладку логики пошаговой анимации.

Настройка HTML-структуры и подключение Canvas

Настройка HTML-структуры и подключение Canvas

В корне проекта создайте файл index.html. Внутри тега <head> укажите кодировку и подключите скрипт:

<meta charset="UTF-8">
<script src="snake.js" defer></script>

Внутри <body> добавьте тег <canvas> с явными размерами. Пример:

<canvas id="game" width="400" height="400"></canvas>

Атрибут id нужен для обращения к элементу через JavaScript. Задание размеров через атрибуты, а не CSS, важно для корректной отрисовки: масштабирование через стиль портит координатную сетку.

Чтобы предотвратить размытие на экранах с высокой плотностью пикселей, рекомендуется дополнительно масштабировать холст в коде. Это будет сделано на стороне JavaScript.

Инициализация игрового поля и сетки

Поле создаётся с использованием элемента <canvas>. Устанавливается фиксированный размер, кратный размеру одной ячейки сетки. Например, при размере ячейки 20 пикселей и поле 20×20 ячеек итоговые размеры: 400×400 пикселей.

  • Создайте canvas в HTML: <canvas id="game" width="400" height="400"></canvas>
  • Получите контекст отрисовки: const ctx = document.getElementById('game').getContext('2d');
  • Размер ячейки задаётся явно: const cellSize = 20;

Для хранения координат змейки и еды удобно использовать двумерную сетку с привязкой к ячейкам. Координаты выражаются в числах, кратных размеру ячейки.

const cols = 20;
const rows = 20;

Отрисовка сетки не обязательна, но для отладки можно добавить её:

for (let x = 0; x <= 400; x += cellSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 400);
ctx.stroke();
}
for (let y = 0; y <= 400; y += cellSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(400, y);
ctx.stroke();
}

При обновлении кадра важно очищать поле перед отрисовкой нового состояния:

ctx.clearRect(0, 0, 400, 400);

Рекомендуется вынести размеры поля и ячеек в константы, чтобы при изменении сетка оставалась пропорциональной.

Создание логики движения змейки

Движение реализуется с помощью обновления координат головы и сдвига остального тела. Каждая часть змейки получает координаты предыдущей. Начальные координаты задаются в массиве, например:

let snake = [
{ x: 160, y: 160 },
{ x: 140, y: 160 },
{ x: 120, y: 160 }
];

Скорость определяется шагом по сетке, кратным размеру ячейки:

const cellSize = 20;
let dx = cellSize;
let dy = 0;

На каждом кадре в начало массива добавляется новый элемент – голова со смещёнными координатами. Последний элемент удаляется, если не съедено яблоко:

function moveSnake() {
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
snake.unshift(head);
snake.pop();
}

Направление нельзя менять на противоположное, иначе змейка столкнётся с собой. Проверка направления выполняется до обновления dx и dy:

function changeDirection(event) {
const LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
if (event.keyCode === LEFT && dx === 0) {
dx = -cellSize;
dy = 0;
} else if (event.keyCode === UP && dy === 0) {
dx = 0;
dy = -cellSize;
} else if (event.keyCode === RIGHT && dx === 0) {
dx = cellSize;
dy = 0;
} else if (event.keyCode === DOWN && dy === 0) {
dx = 0;
dy = cellSize;
}
}

Для непрерывного движения используется setInterval или requestAnimationFrame. Перед каждым обновлением позиции следует очищать холст и отрисовывать все элементы заново.

Реализация столкновений и окончания игры

Реализация столкновений и окончания игры

Для завершения игры при столкновении с границами поля или телом змейки требуется проверка координат головы. Координаты головы сравниваются с размерами игрового поля и элементами массива, представляющего тело.

Проверка выхода за границы:

Если размер игрового поля – 600×600 пикселей, а размер одной клетки – 20 пикселей, допустимые координаты головы находятся в диапазоне от 0 до 580 включительно. Пример условия:

if (head.x < 0 || head.x >= 600 || head.y < 0 || head.y >= 600) {
clearInterval(gameLoop);
alert('Столкновение со стеной');
}

Проверка столкновения с телом змейки:

Голова не должна совпадать ни с одной из координат тела. Для этого перебирается массив сегментов, начиная со второго элемента (индекс 1):

for (let i = 1; i < snake.length; i++) {
if (snake[i].x === head.x && snake[i].y === head.y) {
clearInterval(gameLoop);
alert('Столкновение с телом');
}
}

После остановки основного цикла игры через clearInterval необходимо отключить обработку нажатий клавиш, чтобы избежать некорректных действий после завершения. Пример:

document.removeEventListener('keydown', handleKeyPress);

Дополнительно можно отрисовать сообщение о завершении игры прямо на холсте, используя метод canvas:

ctx.fillStyle = 'red';
ctx.font = '40px Arial';
ctx.fillText('Игра окончена', 150, 300);

Добавление яблока и расчёт очков

Добавление яблока и расчёт очков

Яблоко создаётся как объект с координатами x и y>, которые выбираются случайным образом в пределах игрового поля. Для корректной генерации нужно учитывать размеры клетки и ширину/высоту канваса:

const cellSize = 20;
const apple = {
x: Math.floor(Math.random() * (canvas.width / cellSize)) * cellSize,
y: Math.floor(Math.random() * (canvas.height / cellSize)) * cellSize
};

Чтобы избежать появления яблока внутри змейки, перед генерацией новых координат требуется проверка на совпадение с сегментами змейки:

function generateApple(snake) {
let newApple;
do {
newApple = {
x: Math.floor(Math.random() * (canvas.width / cellSize)) * cellSize,
y: Math.floor(Math.random() * (canvas.height / cellSize)) * cellSize
};
} while (snake.some(segment => segment.x === newApple.x && segment.y === newApple.y));
return newApple;
}

При каждом обновлении состояния игры проверяется, совпадают ли координаты головы змейки и яблока. В случае совпадения:

  • в хвост не удаляется последний сегмент (змейка удлиняется),
  • счёт увеличивается,
  • яблоко пересоздаётся.
if (snake[0].x === apple.x && snake[0].y === apple.y) {
score += 10;
apple = generateApple(snake);
} else {
snake.pop();
}

Рекомендация: счёт удобно хранить в переменной score и отображать через fillText на канвасе:

ctx.fillStyle = 'black';
ctx.font = '16px Arial';
ctx.fillText('Счёт: ' + score, 10, 20);

Обработка управления с клавиатуры

Обработка управления с клавиатуры

Для управления змейкой в игре нужно захватывать нажатия клавиш и соответствующим образом изменять направление движения. В JavaScript это можно реализовать через событие keydown, которое реагирует на нажатие клавиши.

Для начала, создадим обработчик события, который будет отслеживать нажатия клавиш:

document.addEventListener('keydown', function(event) {
switch(event.key) {
case 'ArrowUp':
// Логика движения вверх
break;
case 'ArrowDown':
// Логика движения вниз
break;
case 'ArrowLeft':
// Логика движения влево
break;
case 'ArrowRight':
// Логика движения вправо
break;
}
});

Этот код позволяет менять направление змейки в зависимости от того, какая клавиша была нажата. Для обработки движения змейки важно учесть несколько моментов:

  • Используйте только клавиши направления (ArrowUp, ArrowDown, ArrowLeft, ArrowRight) для предотвращения случайных изменений направления.
  • Сделайте так, чтобы змейка не могла двигаться в противоположную сторону. Например, если она движется вправо, то она не должна сразу перейти влево.

Рассмотрим, как можно ограничить движение змейки в противоположные стороны. Для этого добавим проверку текущего направления:

let direction = 'right';  // Направление по умолчанию
document.addEventListener('keydown', function(event) {
switch(event.key) {
case 'ArrowUp':
if (direction !== 'down') direction = 'up';
break;
case 'ArrowDown':
if (direction !== 'up') direction = 'down';
break;
case 'ArrowLeft':
if (direction !== 'right') direction = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') direction = 'right';
break;
}
});

Для реализации этого механизма можно создать переменную, которая будет отслеживать текущее направление. В каждом обработчике нажатия клавиши добавляем проверку: если текущее направление противоположно, то смена направления не происходит.

Также важно учитывать скорость игры. Часто при разработке игр на JavaScript используется setInterval или requestAnimationFrame для анимации. Для корректного реагирования на изменения направления следует учитывать, что пользователь может нажимать клавиши быстро. Поэтому для предотвращения множественных срабатываний одной клавиши можно использовать временные задержки или флаги:

let canChangeDirection = true;
document.addEventListener('keydown', function(event) {
if (!canChangeDirection) return;
switch(event.key) {
case 'ArrowUp':
if (direction !== 'down') direction = 'up';
break;
case 'ArrowDown':
if (direction !== 'up') direction = 'down';
break;
case 'ArrowLeft':
if (direction !== 'right') direction = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') direction = 'right';
break;
}
canChangeDirection = false;
setTimeout(() => canChangeDirection = true, 100);  // Задержка 100ms
});

Здесь переменная canChangeDirection контролирует, чтобы пользователь не мог быстро менять направление без пауз. Это улучшает игровой процесс, делая управление более плавным.

В результате таких изменений управление будет более стабильным и интуитивно понятным для игрока, а сама змейка не будет двигаться в противоположные направления при случайных нажатиях.

Цикл игры и обновление состояния через requestAnimationFrame

Цикл игры и обновление состояния через requestAnimationFrame

Для создания игры, как змейка, важно правильно организовать цикл игры. Один из лучших способов обновления состояния и рендеринга кадров – использование функции requestAnimationFrame. Этот метод позволяет браузеру синхронизировать анимацию с частотой обновления экрана, улучшая плавность игры и снижая нагрузку на процессор.

Цикл игры начинается с вызова функции, которая отвечает за обновление состояния игры. Важно, чтобы эта функция была вызвана рекурсивно, создавая тем самым постоянный цикл. Для этого в качестве параметра можно использовать requestAnimationFrame, который автоматически повторяет вызов функции, когда экран готов для следующего кадра.

Пример базовой реализации:

function gameLoop(timestamp) {
updateGameState();  // Обновление состояния игры
renderGame();       // Отображение нового кадра
requestAnimationFrame(gameLoop);  // Рекурсивный вызов для следующего кадра
}
requestAnimationFrame(gameLoop);  // Запуск цикла игры

Функция gameLoop вызывается браузером, когда готов обновить экран, что гарантирует плавное и эффективное обновление состояния игры. Параметр timestamp, который передается в функцию, можно использовать для вычисления времени, прошедшего с момента последнего обновления. Это полезно, если необходимо синхронизировать движения объектов, например, для расчета скорости змейки или перемещения еды.

Основные преимущества использования requestAnimationFrame:

  • Эффективность: браузер автоматически управляет частотой обновления, что снижает нагрузку на процессор.
  • Синхронизация с обновлением экрана: кадры рендерятся только тогда, когда это необходимо, что предотвращает лишние вычисления и снижает энергопотребление.
  • Плавность анимации: игра будет выглядеть более плавной, так как каждый кадр отрисовывается в оптимальный момент времени.

requestAnimationFrame может быть использован не только для визуализации, но и для обновления состояния игры, например, изменения положения змейки или проверки столкновений. Для этого необходимо вызывать функции, отвечающие за изменение состояния, перед рендерингом.

Для точной синхронизации движения объектов можно использовать deltaTime, который вычисляется как разница во времени между кадрами. Это важно для обеспечения равномерного движения, независимо от частоты обновления кадров.

Вопрос-ответ:

Как начать создание игры змейка на JavaScript?

Чтобы создать игру змейка на JavaScript, нужно разобраться с основами программирования на этом языке. Начать стоит с написания структуры игры: создание поля, отображение змейки и обработка пользовательского ввода. Для простоты можно использовать HTML5 canvas для рисования игрового поля. Важный момент — нужно определить поведение змейки, её движение по клеточкам поля и возможность увеличения длины при поедании пищи.

Какие технологии и инструменты лучше использовать для создания игры змейка?

Для создания игры змейка на JavaScript можно использовать стандартные веб-технологии: HTML, CSS и JavaScript. HTML создаст структуру страницы, CSS будет отвечать за оформление, а JavaScript — за игровую логику. Для рисования графики и управления игровым процессом удобно использовать элемент canvas из HTML5. Можно подключить дополнительные библиотеки, например, для работы с анимациями или для более удобной работы с событиями.

Какие шаги нужно выполнить для создания логики игры змейка?

Логика игры змейка включает несколько ключевых шагов: первое — создание игрового поля с помощью canvas. Далее необходимо реализовать движение змейки. Для этого можно использовать интервалы с функцией setInterval, которая будет обновлять позицию змейки через определённые промежутки времени. Следующий шаг — обработка клавиш для управления направлением змейки. Также важно реализовать правила столкновения с границами и самой собой. После этого добавляется механика поедания пищи и увеличение длины змейки.

Как сделать так, чтобы змейка увеличивалась в размерах при поедании пищи?

Чтобы змейка увеличивалась при поедании пищи, нужно отслеживать столкновения змейки с едой. При каждом столкновении необходимо генерировать новую еду в случайной точке поля. Кроме того, при столкновении с едой в массив змейки добавляется новый элемент, что и увеличивает её длину. Важно также позаботиться о том, чтобы змейка не вылезала за пределы поля, а когда её длина становится слишком большой, игра завершалась.

Какие возможные ошибки могут возникнуть при разработке игры змейка на JavaScript?

Один из самых распространённых типов ошибок — это неправильное обновление положения змейки, что приводит к её «запутыванию» или исчезновению с поля. Часто возникает проблема с обработкой клавиш, когда игрок не может правильно изменить направление змейки из-за ошибок в обработчиках событий. Также могут возникать ошибки при проверке столкновений, если не учесть все возможные условия (например, столкновение с границей экрана или самой собой). Важно также контролировать скорость движения змейки и время обновления экрана, чтобы игра не была слишком быстрой или медленной.

Ссылка на основную публикацию