Найти в Дзене
Журнал «Код»

Пинг-понг на JavaScript

Оглавление

Да, вдвоём тоже можно.

У нас очередной проект в кайф. В прошлый раз была змейка, теперь напишем игру в пинг-понг для двоих. Всё, что для этого понадобится, вы и так уже знаете. Если не знаете — почитайте про змейку на JavaScript, там много интересного.

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

Логика проекта по шагам

  • Берём пустую HTML-страницу и рисуем на ней игровое поле.Там же делаем отдельный скрипт для игры.
  • В нём прописываем все переменные и константы, которые понадобятся.
  • Описываем отдельно платформы и мячик.
  • Делаем большой бесконечный цикл, где мы двигаем платформы и мячик.
  • Не забываем следить за тем, какие клавиши нажаты, чтобы управлять платформой.

Страница с игровым полем

Возьмём заготовку страницы из проекта со змейкой и немного его поправим. Отличия минимальные: нет рамки и другой размер поля. Остальное то же самое. Подход, где вы используете свой же код в других проектах, здорово экономит время.

Переменные и константы

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

// Обращаемся к игровому полю из документа
const canvas = document.getElementById('game');
// Делаем поле двухмерным
const context = canvas.getContext('2d');
// Размер игровой клетки
const grid = 15;
// Высота платформы
const paddleHeight = grid * 5; // 80
// Задаём максимальное расстояние, на которое может подняться платформа
const maxPaddleY = canvas.height - grid - paddleHeight;
// Скорость платформы
var paddleSpeed = 6;
// Скорость мяча
var ballSpeed = 5;

Платформы и мяч

Наша задача — описать отдельно каждую платформу для обоих игроков и мяч.

У нас есть переменная grid, которая отвечает за размер клетки на игровом поле. Мяч — это одна клетка, а платформа — 5 таких клеток подряд. На старте они обе стоят по краям, а мяч — в самом центре поля.

Дополнительная функция: проверка на пересечение

Нам нужно будет знать, коснулся ли мяч платформы, чтобы отскочить, или нет. Для этого возьмём готовую функцию из интернета. Так как она написана под лицензией Creative Commons Attribution-ShareAlike license, то мы добавим в проект ссылку на оригинальную статью:

// Проверка на то, пересекаются два объекта с известными координатами или нет
// Подробнее тут: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
function collides(obj1, obj2) {
  return obj1.x < obj2.x + obj2.width &&
         obj1.x + obj1.width > obj2.x &&
         obj1.y < obj2.y + obj2.height &&
         obj1.y + obj1.height > obj2.y;
}

Главный цикл игры

В нём нам нужно:

  • очистить игровое поле от всего;
  • проследить за тем, чтобы ничего при движении не ушло за границы игрового поля;
  • перезапустить мяч из центра, если он всё-таки вылетел за платформу;
  • если игрок успел подставить платформу — сделать отскок мяча; отрисовать мяч, все стены и сетку посередине.

Сначала создадим сам цикл, а потом наполним его всем остальным:

// Главный цикл игры
function loop() {
  // Очищаем игровое поле
  requestAnimationFrame(loop);
  context.clearRect(0,0,canvas.width,canvas.height);
// Тут будет остальное
}

Следим за границами

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

Возвращаем мяч в игру, если он улетел

Всё, что нам для этого понадобится, — проверить, выходят ли координаты мяча за координаты границ поля. Если да — ставим мяч по центру и даём игрокам секунду на подготовку.

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

// Если мяч улетел за игровое поле влево или вправо — перезапускаем его
if ( (ball.x < 0 || ball.x > canvas.width) && !ball.resetting) {
  // Помечаем, что мяч перезапущен, чтобы не зациклиться
  ball.resetting = true;

  // Даём секунду на подготовку игрокам
  setTimeout(() => {
    // Всё, мяч в игре
    ball.resetting = false;
    // Снова запускаем его из центра
    ball.x = canvas.width / 2;
    ball.y = canvas.height / 2;
  }, 1000);
}

Если игрок успел отбить мяч

Используем функцию collides, о которой мы говорили раньше, — она проверяет, есть на пути мяча препятствие или нет. Если есть — меняем направление движения мячика:

Рисуем всё остальное

Самый простой элемент игры:

// Рисуем мяч
context.fillRect(ball.x, ball.y, ball.width, ball.height);

// Рисуем стены
context.fillStyle = 'lightgrey';
context.fillRect(0, 0, canvas.width, grid);
context.fillRect(0, canvas.height - grid, canvas.width, canvas.height);

// Рисуем сетку посередине
for (let i = grid; i < canvas.height - grid; i += grid * 2) {
  context.fillRect(canvas.width / 2 - grid / 2, i, grid, grid);
}

Последнее: нажатия на клавиши управления

Используем для этого стандартный обработчик событий document.addEventListener(). Но хитрость в том, что нам нужно отслеживать как нажатие на клавиши, так и тот момент, когда игроки их отпускают.

Смысл в том, что платформы движутся только когда игрок зажимает клавишу. Как только он её отпускает — платформа останавливается. Именно поэтому мы сделаем два обработчика: один будет следить за нажатыми клавишами, а второй — за отпущенными.

Запускаем игру

Для запуска добавляем это в самый конец скрипта:

// Запускаем игру
requestAnimationFrame(loop);

Сохраняем всё как HTML-файл и открываем в браузере:

-2

Что дальше

Эту игру можно улучшать бесконечно:

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

Мы верим, что вы сможете всё это сделать самостоятельно. Если будут вопросы — задавайте их в комментариях. А мы пока поиграем.