Теперь мы куда-то движемся.

Догонять

На этой неделе я продолжил изучение возможностей фреймворка Phaser, внедрив некоторые функции поверх версии Breakout, которую я уже создал. Функции, которые я добавил на данный момент, включают:

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

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

Как я реорганизовался

Большую часть своей карьеры программиста я посвятил написанию объектно-ориентированного кода на PHP. Из-за этого я очень привык к тому, что мои классы написаны и разделены на несколько файлов. В Javascript вполне можно писать код в отдельных файлах, загружать их асинхронно, и все работает отлично. В случае с созданием состояний для этой игры просто их разделение не поможет, потому что они полагались на объект states, существовавший до того, как их можно было добавить к нему, и я хотел, чтобы все состояния были добавлены к этому. объект перед запуском любой части игры.

Итак, очень быстро проблема в том, что мне нужно сначала создать объект Phaser.Game, затем нужно создать объект states, затем я создаю столько состояний, сколько хочу, и добавляю их в объект states, а затем я могу вызвать game.state.start(), чтобы заставить мяч катиться (или в данном случае отскакивать). Код загружается асинхронно, поэтому я не могу гарантировать, что состояния будут созданы после создания игрового объекта, так что же мне делать?

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

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

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

Блипы и ляпы

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

Прежде чем мы перейдем к коду, я хотел бы отметить, что я совершенно забыл, что музыка и звуковые эффекты не бесплатны (в большинстве случаев), и мне пришлось найти некоторые из них, которые были в свободном доступе. Мне не потребовалось много времени, прежде чем я наткнулся на Freesound и своего спасителя: NoiseCollector. Подобные звуки можно сгруппировать в пакеты на Freesound, и NoiseCollector загрузил пакет Pong softsynth, в котором было именно то, что я искал.

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

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

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

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

Нет больше вечной смерти!

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

Реализация слишком проста, чтобы описывать ее здесь, но я добавил жизни как элемент состояния game и инициализировал его в нашем create()метод и проверял его, когда мяч достигает нижней части экрана:

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

Рекорд

Внедрить систему подсчета очков было так же просто, как следить за жизнями игроков. Я добавил счет в состояние game, инициализировал его в create() и обновил в update(). если мяч попал в блок. Я следил за счетом игрока на экране так же, как и с жизнями.

Уровень повышен

Система уровней пошла почти так, как я и предполагал. Раньше у меня был цикл строк и цикл столбцов, создающих кучу блоков в методе create(). Я вывожу координаты x и y для каждого из этих блоков и сохраняю каждую из них как точку в массиве массивов, называемых levels. Структура выглядит так: уровни [ уровень ][ точка ][ x, y ]. Затем я понял, что хочу иметь возможность поддерживать несколько типов блоков в будущем, поэтому я обновил структуру, чтобы она выглядела следующим образом: уровни [уровень] [точка] [тип блока, [x, y]]. Оттуда я написал небольшую функцию для чтения из массива для построения уровня:

Как только мне удалось построить уровень из входного массива, я добавил пару уровней и добавил следующее в метод update():

Первоначальная проверка того, выиграл ли игрок игру (this.blocks.size === 0), была изменена, чтобы сначала проверить, есть ли другой уровень. Если это так, обновите текст уровня, остановите движение мяча, сбросьте положение мяча, постройте уровень и позвольте игроку снова запустить мяч. Если в очереди нет другого уровня, игрок выигрывает.

Добавление разнообразия

Одной из особенностей оригинального Breakout, отсутствовавшей в моей реализации, была изменчивость скорости и угла мяча. Изменчивость, наблюдаемая в оригинале, чаще всего была вызвана тем, где мяч соприкасался с ракеткой. Для моей реализации я решил, что на скорость и угол будет влиять попадание мяча в левую или правую треть ракетки, и что они останутся прежними, если попадут в середину. Кроме того, я использовал Math.random(), чтобы сделать его немного непредсказуемым.

Эта часть, несмотря на всю ее простоту, немного сбила меня с толку, потому что я совершенно забыл об установке точки привязки на ракетке игрока. Я предполагал, что запросив this.paddle.x, я получу координату x левой стороны весла. стороны, я хотел добавить 1/3 ширины весла, чтобы получить область, в которой я хотел проверить на столкновение с мячом. Эта часть была правильной, но поскольку я установил привязочную точку весла в центре, это то, что возвращалось, когда я ссылался на this.paddle.x.Как только я понял это, я настроил проверку, чтобы вычесть половину ширины ракетки из положения x, и все это волшебным образом заработало. Мораль этой истории: обратите внимание на свои якорные точки.

Пасхальное яйцо

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

Я начал с загрузки изображения в create() и обновил свой метод createBlock(), чтобы он принимал ключ для другого спрайта. Как только дополнительный блок был построен для уровня, мне просто нужно было добавить проверку в части столкновения мяча и блока в update() и проверить block.key, чтобы увидеть какой тип блока был поражен.

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

Выкрикивать

Я указывал на них в прошлом, но люди из Mozilla Developer Network разместили одну из самых полезных документов по веб-разработке, которые вы можете найти. Работая над несколькими проблемами, я наткнулся на их замечательную информацию о разработке игр. Оказывается, у них даже есть учебник по созданию Breakout с помощью Phaser, так что если вы застряли с тем, что я делал, не стесняйтесь проверить их.

Что дальше

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

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