Нет, это не будет Animal Crossing или Cyberpunk 2077. Но все мы должны с чего-то начинать, верно? Мы создадим действительно простую игру с уклонением от врагов в HTML5 с помощью PixiJS, и мы сможем сделать это довольно быстро (средний уровень предполагает 6 минут чтения, так что давайте скажем 10 минут!). Выглядит это так:

Живая демонстрация: https://www.undeveloped.games/game0a.html

Преимущества построения игр

Игры бывают разных жанров, и чаще всего игры очень интерактивны, и иногда для решения которых требуются совершенно иные подходы, чем для большинства задач фронтенд-разработки. Привыкание к «игровому мышлению» приносит большую пользу веб-разработчикам, если ваши проекты состоят из интерактивных элементов. Тем не менее, сразу же начать изучение игрового движка может быть непросто. Я думаю, что проще будет проиллюстрировать некоторые технические моменты исключительно на JavaScript ES6 с PixiJS, популярной и простой в использовании графической библиотекой.

Шаг 1. Создание пустой сцены

Мы начнем с создания пустого холста для нашей игры, некоторых переменных и основного gameLoop(). Игры работают непрерывно, и в этом руководстве мы предполагаем, что достаточно просто запускать со скоростью 60 кадров в секунду, поэтому gameLoop() будет вызываться 60 раз в секунду, при этом мы вызываем функцию .update(). во всех игровых объектах в каждом кадре. update() будет там, где мы будем обновлять расположение каждого объекта, обрабатывать коллизии, а также другую логику.

Более продвинутый подход - использовать requestAnimationFrame(), который обновляет графику настолько плавно, насколько может поддерживать целевое устройство. Но имейте в виду, чтобы скорость анимации была одинаковой на разных устройствах (с разной частотой кадров), нам нужно учитывать разницу во времени, прошедшую между кадрами в наших движениях, что, я уверен, вы сможете перескочить позже. Но давайте пока будем простыми.

Шаг 2: создание монстра

Сначала давайте создадим монстров! Наши монстры (и наш игрок, а также награда…) будут просто маленькими кружочками, поэтому имеет смысл создать базовый класс круга. Для построения круга требуются три параметра, цвет , радиус и скорость. Базовый конструктор запоминает эти значения, рисует круг как новый графический объект this.circle и добавляет его в основную сцену.

Класс monster расширяет базовый класс окружности и добавляет функцию обновления, которая перемещает this.circle каждый кадр по скорости. Теперь вы должны знать, что наша скорость определяется как «пикселей на кадр», что просто, но не идеально. В реальной жизни мы, вероятно, использовали бы «расстояние в секунду» и рассчитали бы количество, необходимое для прохождения каждого кадра, на основе времени между каждым кадром. Но концепция та же.

Функция update() также проверяет, достигает ли круг (закрепленный в середине) краев рабочей области. Если да, то измените значение vx или vy. .

Наконец addMonster() создает монстра со случайным радиусом и случайной скоростью и добавляет его к основному массиву монстров.

Шаг 3. Добавление игрока и управление

Затем мы создадим проигрыватель, который расширяет тот же базовый класс круга, но с другим цветом. Мы добавили функцию Player.reset() для сброса его скорости и положения в центре сцены, что пригодится позже, когда игрок умрет.

В Player.update() вместо изменения скорости при столкновении с краями сцены мы просто ограничиваем ее.

Controls прост, мы слушаем события keydown и keyup и обновляем Player.v соответственно. Теперь, казалось бы, длинные коды соответствуют конкретному случаю, когда одно направление отпускается, а противоположное направление удерживается все время. Например, игрок удерживает ВЛЕВО, нажимает ВПРАВО и отпускает, мы должны обнаружить эту ситуацию, сохраняя нажатые состояния клавиш. Это добавляет много элементов управления. В конце концов, мы делаем игру, и она не будет гладкой, если управление будет дергаться.

Шаг 4: условие проигрыша

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

Затем в Player.update() мы будем вызывать .collide() против каждого монстра в каждом кадре. Фу. Но это жизнь (подробнее см. Сноски.) Если игрок сталкивается с любым монстром, игра перезагружается.

В основной reset() функции мы удаляем всех монстров со сцены на Circle.remove(), сбрасываем массивы и другие переменные, добавляем нового монстра по умолчанию.

Шаг 5. Что-нибудь подобрать

Теперь наш игрок может удручающе умереть, давайте добавим награды, не так ли? Здесь мы создаем еще один класс Coin, расширяющий тот же базовый класс Circle. Мы добавим функцию random(), чтобы рандомизировать ее положение, и мы будем постоянно масштабировать монету в функции update(), небольшой трюк, чтобы сообщить: «Эй, я интересный, подойди за мной».

В Player.update() мы дополнительно проверяем, сталкивается ли игрок с монетой, если да, поднимает ее, увеличивая счетчик монет и перемещая монету в новое положение. Что касается количества монет (счета), я просто показываю его с помощью элемента HTML.

Сбор монет также усложняет игру, что достигается следующими двумя строками в Player.update ():

addMonster();
this.speed = Math.min(4, this.speed + 0.2);

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

  1. игроки перемещаются
  2. избегает опасности
  3. собирает награды
  4. избегает большей опасности, труднее играть

Что дальше?

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

Замечание об обнаружении коллизий для каждого объекта на кадр на шаге 4. Для повышения масштабируемости мы должны использовать какую-то структуру данных пространственного разделения для обработки обнаружений коллизий (скажем, квадродерево или разбиение на основе сетки). И нам, возможно, не потребуется рассчитывайте их для каждого кадра, в зависимости от того, насколько «реактивным» мы хотим, чтобы игра ощущалась.

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

Надеюсь, вам понравится и вы найдете это полезным в некотором смысле!

Живая демонстрация: https://www.undeveloped.games/game0a.html

Полный источник: https://www.undeveloped.games/downloads/game0a.zip