Узнайте, как создать JS-игру с использованием MelonJS и Tiled

Разработка игр - это не то, что нужно ограничивать только людьми, использующими Unity или Unreal Engine4. Разработка игр на JavaScript ведется уже довольно давно. Фактически, последние версии самых популярных браузеров (например, Chrome, Firefox и Edge) обеспечивают поддержку расширенного графического рендеринга (например, WebGL), что открывает очень интересные возможности для разработки игр.

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

Вот почему, проведя небольшое исследование, я решил написать это краткое руководство с использованием MelonJS.

Что такое MelonJS?

Как вы уже, наверное, догадались, MelonJS - это игровой движок JavaScript, который полностью совместим со всеми основными браузерами (от Chrome до Opera, Chrome для мобильных устройств и iOS Safari).

У него есть набор функций, которые сделали его очень интересным во время моего исследования:

  • Во-первых, он полностью независим, для его работы не нужны внешние зависимости.
  • Однако он имеет интеграцию с несколькими сторонними инструментами, которые сделают вашу жизнь намного проще, такими как Tiled (который поможет вам создавать карты и этапы для ваших игр), TexturePacker (который также поможет вам создать требуемый атлас текстур для упрощения и оптимизации управления спрайтами).
  • Встроенный 2D физический движок. Это означает, что вы можете получить доступ к готовому к использованию реалистичному 2D-движению и обнаружению столкновений. Это очень важно, поскольку для того, чтобы все это проработать, потребовалось бы много работы (не говоря уже об очень интересном объеме математики, что на самом деле не является моей чашкой чая).
  • Поддержка звукового API, позволяющая добавлять звуковые эффекты и фоновую музыку с исключительной простотой.

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

Совет. Используйте Bit (Github), чтобы легко делиться и повторно использовать модули JS и компоненты пользовательского интерфейса в ваших проектах, предлагать обновления, синхронизировать изменения и быстрее работать в команде. .

Планируем нашу игру

Цель игры с набором текста - предоставить игроку возможность перемещаться или выполнять какое-либо действие, набирая слова (или просто нажимая случайные клавиши).

Я помню, как учился печатать еще в детстве (да, это было довольно давно), используя платформер Mario Teaches Typing, в котором вам нужно было набирать отдельные буквы, чтобы двигаться вперед, прыгая на черепаху или ударив по блоку снизу. Изображение ниже дает вам представление о том, как выглядела игра и как вы могли с ней взаимодействовать.

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

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

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

  1. двигаться вперед
  2. прыгать вперед
  3. подпрыгнуть
  4. отпрыгнуть назад
  5. двигаться в обратном направлении

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

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

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

Инструменты, которые вам понадобятся

Хотя melonJS полностью независим, есть несколько инструментов, которые ОЧЕНЬ помогут в этом процессе, и я рекомендую их приобрести:

  • Пакетатор текстур: с его помощью вы сможете автоматически сгенерировать атлас текстур, что является еще одним способом создания файла JSON, в котором все изображения упакованы, чтобы движок мог позже их извлекать и использовать по своему усмотрению. . Если у вас нет этой утилиты, поддерживать атлас вручную может быть слишком сложно.
  • Tiled: это будет наш главный редактор уровней. Хотя вы можете скачать его бесплатно (вам нужно будет найти ссылку Нет, спасибо, просто перейдите к загрузкам), вы можете пожертвовать автору этого замечательного инструмента всего лишь 1 доллар. Если у вас есть учетная запись PayPal или дебетовая карта, я бы посоветовал вам это сделать, подобное программное обеспечение необходимо обслуживать, и для этого требуются время и усилия.

С помощью этих инструментов вы сможете следовать инструкциям и выполнять их, так что давайте займемся кодированием, не так ли ?!

Базовый платформер

Лучшее, что мы можем сделать, чтобы приступить к работе над этим проектом (особенно если вы раньше не контактировали с melonJS), - это использовать образец кода. По умолчанию, когда вы загружаете движок, он поставляется с набором примеров проектов, которые вы можете проверить (они находятся в папке examples).

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

  • d ata f старше, содержащий все, что не связано с кодом. Здесь вы найдете звуки, музыку, изображения, определение карты и даже шрифты.
  • Папка js, в которой вы будете хранить весь код, связанный с игрой.

Файлы index.html и index.css. Это будет ваша точка контакта, необходимая вашему приложению для взаимодействия с внешним миром.

Понимание существующего кода

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

Запуск игры

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

  1. Копия melonJS. Если вы скачали его, убедитесь, что вы получили содержимое папки dist. Скопируйте его в любую папку и обязательно добавьте его из файла index.html, как и любой другой файл JS.
  2. Установите (если вы еще этого не сделали) модуль http-server, доступный в npm, который может быстро обслуживать все соответствующие файлы внутри этой папки. Если вы еще не установили его, просто выполните:
$ npm install -g http-server

И после установки из папки проекта запустите:

$ http-server

После этого вы сможете посетить http://localhost:8080, чтобы протестировать игру.

Просмотр кода

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

Ключевые файлы, которые следует проверить здесь:

  • game.js: этот файл содержит весь код инициализации, интересно посмотреть, как создается игровая графика и основные элементы управления.
  • screen / play.js: здесь есть весь код, необходимый для настройки уровня. Вы заметите, что это не так уж и много. Поскольку определение уровня выполняется с помощью другого инструмента (например, Tiled), этот код просто позволяет это сделать.
  • entity / player.js: несомненно, ваша основная цель. Этот файл содержит код движения вашего персонажа, ваши реакции на столкновение и привязки управляющих клавиш. Хотя он не такой уж и большой, но именно там вы хотите проводить больше всего времени.
  • entity / враги.js: это важно, во-вторых, после кода игрока, потому что вы увидите, как настроить автоматическое поведение на основе заранее определенных координат.

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

Понимание откуда все идет

Если вы выполнили домашнее задание, то, вероятно, заметили, что нет строки, создающей экземпляр игрока или кого-либо из врагов. И их координаты нигде не найти. Как же тогда игра может это понять?

Здесь в игру вступает редактор уровней. Если вы скачали Tiled, вы можете открыть файл с именем map1.tmx в папке data/map, и вы увидите что-то вроде этого:

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

Это приведет меня в правую часть экрана, где вы увидите список слоев (в правом верхнем углу). Есть разные типы слоев:

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

И нижний правый угол, содержащий набор тайлов этой карты. Набор плиток также можно создать с помощью Tiled, и этот набор можно найти внутри той же папки с расширением tsx.

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

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

Изменение схемы движения

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

Это означает, что первое, что нам нужно изменить, - это схему движения, или другими словами: изменить элементы управления.

Так что перейдите к entities/player.js и ознакомьтесь с методом init. Вы заметите много звонков bindKey и bindGamepad. Эти строки, по сути, связывают определенный ключ с логическим действием. Проще говоря, он гарантирует, что независимо от того, нажимаете ли вы клавишу со стрелкой вправо, клавишу D или перемещаете аналоговый джойстик вправо, в вашем коде будет выполняться одно и то же «правильное» действие.

Это все должно уйти, так что убирайся, нам это не пригодится. И параллельно, давайте создадим новый файл, назовем его wordServices.js, и внутри этого файла мы создадим объект, который будет возвращать слова для каждого хода, и это поможет нам понять, какое действие было выбрано игроком.

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

  • Свойство coords используется для размещения текста в правильных координатах на основе HUD действия (подробнее об этом позже).
  • Свойство regionPostfix используется для выбора правильного фрейма для HUD действий (по сути, для выделения записанного действия).

А теперь давайте посмотрим, как запрашивать ввод данных пользователем в середине игры.

Примечание. Прежде чем двигаться дальше, помните, что для того, чтобы сделать эту новую службу доступной для остальной части вашего кода, вам необходимо включить ее в index.html файл, как и любую другую библиотеку JS:

<script type="text/javascript" src="js/wordServices.js"></script>

Как записать ввод пользователя

Захват пользовательского ввода с помощью melonJS на самом деле довольно просто, поскольку для этого нам не нужен melonJS. В конце концов, это веб-страница, и вас более чем приветствуют (фактически поощряют) использовать для этого старое доброе поле ввода.

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

Поэтому вместо этого мы можем просто добавить текстовое поле на главную HTML-страницу и с помощью CSS мы можем стилизовать его так, чтобы оно располагалось над элементом Canvas, и, таким образом, оно стало частью игры.

Все, что вам нужно, это код внутри элемента <body>:

<input type="text" id="current-word" />

Затем, хотя это полностью зависит от вас, я бы рекомендовал использовать jQuery для упрощения кода, необходимого для присоединения обратного вызова к событию keypress. Конечно, это можно сделать с помощью ванильного JS, но я предпочитаю синтаксический сахар, предоставляемый этой библиотекой.

Следующий код, находящийся в методе loaded файла game.js, заботится о захвате введенных пользователем данных:

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

При этом мы можем настроить обработчик событий для любой нажатой клавиши. И, как вы можете видеть, я проверяю ключевой код 13 (который обозначает клавишу ENTER), чтобы распознать, когда игрок закончил печатать, в противном случае я проверяю, что они вводят допустимые символы (я просто избегаю специальных символов, чтобы избежать проблем со шрифтом по умолчанию, предоставляемым melonJS).

И последнее, но не менее важное: я устанавливаю два разных состояния для объекта StateManager: lastWord, чтобы понимать последнее слово, введенное игроком, и partialWord, чтобы понимать, что они ' перепечатываю прямо сейчас. Оба эти состояния станут важными через минуту.

Обмен данными между компонентами

Это обычная проблема во многих фреймворках: как вы можете обмениваться данными между компонентами? В этом случае, зная, что мы собираем ввод как часть компонента game, как мы можем поделиться этим вводом с другими?

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

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

Добавление пользовательского интерфейса

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

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

  • Один для графики, который будет иметь несколько разных кадров, по существу, один для обычного изображения, а затем один, показывающий каждое направление как «выбранное» (это связано с атрибутом «regionPostfix» в ActionWordsService)
  • Один для текста, который будет напечатан вокруг изображения из предыдущего пункта. Это также, кстати, связано с атрибутом «coords» в ActionWordsService.

Для этого мы можем припарковать существующий файл HUD.js внутри папки js. Мы добавим туда два новых компонента.

Первый, компонент ActionControl, будет выглядеть так:

Кажется, что это много, но он просто делает несколько вещей:

  1. Он получает свои координаты из атрибута settings, который мы рассмотрим, когда настроим карту на Tiled.
  2. Он добавляет код для реакции на введенные частичные слова. Мы используем атрибут postfix для текущего написанного слова, чтобы выбрать правый фрейм.
  3. И он добавляет код для реакции на последние слова. Если с этим словом связано действие (то есть правильное слово), оно добавит очки к счету игрока. В противном случае экран будет сотрясаться и воспроизводиться звук ошибки.

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

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

Вот как будет выглядеть предлагаемый дизайн элемента управления действием (и как к нему относятся координаты):

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

Просто убедитесь, что вы сохранили эти изображения в папке /data/img/assets/UI, это нужно для того, чтобы позже, когда вы откроете TexturePacker, он распознает новые изображения, и вы сможете добавить их в атлас текстур.

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

Собираем все вместе с Tiled

Хорошо, теперь, когда мы рассмотрели основы, давайте приступим к игре. Перво-наперво: карта.

Используя тайлы и набор тайлов по умолчанию, включенный в melonJS, я создал эту карту (карта тайлов 25x16 с тайлами 32x32px):

По сути, я использую следующие слои:

  • HUD: он содержит только один элемент под названием HUD.ActionControl (важно, чтобы имена оставались одинаковыми, вы поймете, почему через минуту). На следующем изображении показаны свойства этого элемента (обратите внимание на настраиваемые свойства).

  • столкновение: по умолчанию melonJS будет считать любой слой, который начинается со слова «столкновение», как слой столкновения, что означает, что любая фигура внутри него не будет проходима. Итак, в этой форме вы определите все формы пола и платформ.
  • player: этот слой просто содержит элемент mainPlayer (форма, которая позволит melonJS узнать, куда нужно поместить игрока в начале игры).
  • сущности: на этом слое я добавил монеты, опять же, их имена важны, поэтому оставьте их такими же, поскольку они должны совпадать с именем, с которым вы их регистрируете в своем коде (продолжайте читать, это будет иметь смысл через секунду ).
  • Последние три слоя нужны только для того, чтобы можно было добавить изображения для карты и фона.

Когда все будет готово, мы можем перейти к game.js файлу и внутри метода loaded добавим следующие строки:

Эти строки регистрируют ваши объекты (по крайней мере, те, которые вы хотите разместить прямо на карте с помощью Tiled). Имя, которое вы указываете в качестве первого параметра, - это имя, которое вам нужно сопоставить с помощью Tiled.

Кроме того, в этом файле метод onLoad должен выглядеть так:

По сути, вы запрашиваете разрешение 965x512 (я обнаружил, что все работает отлично, когда высота вашего экрана равна высоте вашей карты, а в нашем случае это 16 * 32 = 512). После этого ActionWordsService инициализируется 5 словами (это 5 направлений, по которым вы можете двигаться).

Другая интересная строка из метода onLoad - это строка:

me.loader.preload(game.resources, this.loaded.bind(this));

Мы пока не рассмотрели ресурсы, так что сейчас отличный момент для этого.

Файл ресурсов

В этот файл (файл resources.js) необходимо добавить любой тип ресурсов, который нужен вашей игре (например, изображения, звуки, фоновая музыка, файлы конфигурации JSON и т. Д.).

Вот как должен выглядеть ваш файл ресурсов на этом этапе:

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

Ваши монеты

Монеты в игре очень простые, но когда вы сталкиваетесь с ними, что-то должно происходить, поэтому код для них будет выглядеть так:

Обратите внимание, как наш объект монеты фактически расширяет CollectibleEntity (это дает ему специальный тип столкновения для объекта, поэтому melonJS знает, что нужно вызывать обработчик столкновений, когда игрок перемещается по нему), поэтому все, что вам нужно сделать, это вызвать его родительский конструктор и затем, в методе onCollision, мы проигрываем быстрый звук, когда вы поднимаете его, добавляем очки к глобальному счету и, наконец, удаляем объект из мира.

Готовый продукт

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

Это должно выглядеть примерно так:

А поскольку это руководство уже слишком длинное, вы можете проверить полную кодовую базу для этой игры на Github. Не стесняйтесь оставлять комментарии ниже, если у вас есть какие-либо вопросы или если вы хотите понять, как делать то, что я еще не упомянул.

Я буду создавать больше руководств с более подробной информацией о различных аспектах, которые я не упомянул, так что следите за обновлениями!

Учить больше