Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Glitch

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

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Svetlana (you can also view the original English article)

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

Это предназначено для разработчиков, которые знают, как делать игры и знакомы с JavaScript, но никогда не делали многопользовательскую онлайн-игру. Как только вы закончите, вам должно быть удобно внедрять базовые сетевые компоненты в любую игру и быть в состоянии строить на ней оттуда!

Это то, что мы будем строить:

Screenshot of the Final Game - Two Ships Attacking Each Other

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

Я объединил эту игру, используя искусство Kenney's Pirate Pack и основную работу Phaser. Вы будете играть роль сетевого программиста для этого урока. Отправной точкой будет полноценная версия с одним игроком в этой игре, и ваша работа будет заключаться в том, чтобы написать сервер в Node.js, используя Socket.io для сетевой части. Чтобы этот учебник был управляемым, я сосредоточусь на многопользовательских частях и скрою конкретные концепции Phaser и Node.js.

Не нужно ничего настраивать локально, потому что мы будем делать эту игру полностью в браузере на Glitch.com! Glitch - отличный инструмент для создания веб-приложений, включая задний конец, базу данных и все такое. Это отлично подходит для прототипирования, обучения и сотрудничества, и я очень рад познакомить вас с этим во всем этом учебном пособии.

Давай погрузимся. 

1. Настройка

Я поставил стартовый комплект на Glitch.com.

Некоторые советы по быстрому интерфейсу: в любое время вы можете просмотреть предварительный просмотр своего приложения, нажав кнопку «Показать» (вверху слева).

The show button is at the top left on the Glitch interface

Вертикальная боковая панель слева содержит все файлы в вашем приложении. Чтобы изменить это приложение, вам нужно «пересоздать» его. Это создаст копию его в вашей учетной записи (или разветвит ее в git lingo). Нажмите кнопку Remix для этой кнопки.

The remix button is at the top of the code editor

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

Теперь, прежде чем идти дальше, важно ознакомиться с кодом для игры, в которую вы пытаетесь добавить многопользовательскую игру. Взгляните на index.html. Существует три важных функции, чтобы быть в курсе: pre load (линия 99), create (линии 115), и GameLoop (линия 142), в дополнение к игроку объект (линия 35).

Если вы предпочитаете учиться, попробуйте эти проблемы, чтобы убедиться, что вы получаете суть работы игры:

  • Сделайте мир более крупным (строка 29) - отметьте, что для игрового мира есть отдельный размер мира, а размер окна - для фактического холста на странице.
  • Сделать ПРОБЕЛ также направленной вперед (строка 53).
  • Измените тип корабля вашего игрока (строка 129).
  • Сделайте пули медленнее (строка 155).

Установка Socket.io

Socket.io - это библиотека для управления связью в реальном времени в браузере с использованием WebSockets (в отличие от использования протокола UDP, если вы создаете многопользовательскую настольную игру). Он также имеет резервные копии, чтобы убедиться, что он все еще работает, даже если WebSockets не поддерживается. Таким образом, он заботится о протоколах обмена сообщениями и предоставляет хорошую систему сообщений на основе событий для вас.

Первое, что нам нужно сделать, это установить модуль Socket.io. В Glitch вы можете сделать это, перейдя в файл package.json и либо набрав требуемый модуль в зависимостях, либо щелкнув «Add package» и набрав «socket.io».

The add package menu can be found at the top of the code editor when selecting the file packagejson

Это было бы подходящее время, чтобы указать журналы сервера. Нажмите кнопку «Logs» слева, чтобы открыть журнал сервера. Вы должны увидеть, что он устанавливает Socket.io вместе со всеми его зависимостями. Здесь вы увидите ошибки или вывод из кода сервера.

The Logs button is on the left side of the screen

Теперь нужно перейти server.js. Вот где живет ваш код сервера. Сейчас у него просто есть базовый шаблон для обслуживания нашего HTML. Добавьте эту строку вверху, чтобы включить Socket.io:

Теперь нам также нужно включить Socket.io на клиенте, поэтому вернитесь к index.html и добавьте это вверху внутри  твой "head" ярлык :

Примечание. Socket.io автоматически обрабатывает обслуживание клиентской библиотеки по этому пути, поэтому эта строка работает, даже если вы не видите каталог /socket.io/ в своих папках.

Теперь Socket.io включен и готов к работе!

2. Обнаружение и нереста игроков

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

Принятие соединений на сервере

В нижней части server.js добавьте этот код:

Это указывает Socket.io прослушивать любое событие соединения, которое автоматически запускается при подключении клиента. Он создаст новый объект гнездо  socket для каждого клиента, где socket.id - уникальный идентификатор для этого клиента.

Чтобы убедиться, что это работает, вернитесь к клиенту (index.html) и добавьте эту строку где-нибудь в create functio

Если вы запустите игру, а затем посмотрите на журнал своего сервера (нажмите кнопку «Logs»), вы увидите, что он регистрирует это событие соединения!

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

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

Вы не увидите ничего в журнале сервера, если вы запустите это. Это связано с тем, что мы еще не сказали клиенту выпустить это событие нового игрока. Но давайте притворимся, что на это позаботились, и продолжайте идти по серверу. Что должно произойти после того, как мы получим местоположение нового игрока, который присоединился?

Мы могли бы отправить сообщение каждому подключенному игроку, чтобы сообщить им, что к нему присоединился новый игрок. Socket.io предоставляет удобную функцию для этого:

Вызов socket.emit просто отправит сообщение обратно одному клиенту. Вызов socket.broadcast.emit отправляет его каждому клиенту, подключенному к серверу, за исключением того, что он вызван одним сокетом.

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

Код сервера теперь должен выглядеть так:

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

Нерест на клиенте

Теперь, чтобы завершить этот цикл, мы знаем, что нам нужно сделать две вещи на клиенте:

  1. Извлеките сообщение с нашими данными о местоположении после подключения.
  2. Прослушай результат создания игрока  и вызывай игрока в этом месте.

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

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

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

Мы знаем, что наш сервер получает наше объявление о подключении нового игрока, а также правильное получение данных о местоположении!

Затем мы хотим прослушать запрос на создание нового игрока. Мы можем разместить этот код сразу после нашего испускания, и он должен выглядеть примерно так:

Теперь проверьте это. Откройте два окна вашей игры и посмотрите, работает ли это.

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

Задача: можете ли вы понять, почему это происходит? Или как вы можете это исправить? Выполните описанную нами клиентскую / серверную логику и попытайтесь ее отладить.

Надеюсь, у вас был шанс подумать об этом сами! Что происходит, когда первый игрок подключился, сервер отправил событие для создателя для каждого другого игрока, но другого игрока не было, чтобы его получить. Как только второй игрок подключается, сервер снова отправляет свою трансляцию, а игрок 1 получает ее и правильно порождает спрайт, в то время как игрок 2 пропускает начальную передачу соединения 1 игрока.

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

Предупреждение о синхронизации состояния игры

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

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

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

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

Чтобы реализовать это, я буду:

  1. Держите словарь игроков, причем ключ является их идентификатором, а значение - их данными о местоположении.
  2. Добавьте игрока в этот словарь, когда они подключаются и отправляют событие обновления.
  3. Извлеките плеер из этого словаря, когда они отсоединяют и отправляют событие обновления.

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

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

Вот как я обработал это событие на клиенте:

Я отслеживаю корабли на клиенте в словаре под названием other_players, который я просто определил в верхней части моего сценария (не показано здесь). Поскольку сервер отправляет данные игрока всем игрокам, я должен добавить чек, чтобы клиент не создавал дополнительный спрайт для себя. (Если у вас возникли проблемы с структурированием этого, вот полный код, который должен быть в index.html на данный момент).

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

3. Синхронизация позиций кораблей

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

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

И это должно быть! Теперь ваша очередь попытаться реализовать это самостоятельно.

Если вы полностью застряли и вам нужен намек, вы можете посмотреть окончательный завершенный проект в качестве ссылки.

Примечание по минимизации сетевых данных

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

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

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

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

4. Синхронизация пуль

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

  • Каждый клиент отправляет позиции всех своих пуль каждый кадр.
  • Сервер передает это каждому игроку.

Но есть проблема.

Защита от читов

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

Чтобы смягчить это, мы попробуем другую схему:

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

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

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

Вы также можете прокомментировать весь этот раздел, который обновляет маркеры на клиенте:

Наконец, нам нужно заставить клиента прослушивать обновления пули. Я решил обработать это так же, как и с игроками, где сервер просто отправляет массив всех мест маркеров в событие с именем bullets-update, и клиент будет создавать или уничтожать патроны для синхронизации. Вот что это выглядит:

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

Теперь, на server.js, нам нужно отслеживать и моделировать пули. Во-первых, мы создаем массив для отслеживания пуль, точно так же, как и у игроков:

Затем мы слушаем событие нашей стрелялки:

Теперь мы моделируем пули 60 раз в секунду:

И последний шаг - отправить событие обновления где-нибудь внутри этой функции (но определенно вне цикла for):

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

5. Булевое столкновение

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

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

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

Вы можете попробовать сделать это полностью самостоятельно. Чтобы заставить игрока мигать при ударе, просто установите их альфа в 0:

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

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

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

6. Более плавное движение

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

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

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

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

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

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

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

Затем внутри нашей функции обновления (все еще на клиенте) мы перебираем всех других игроков и нажимаем их на эту цель:

Таким образом, ваш сервер может отправлять вам обновления 30 раз в секунду, но все равно играть в игру со скоростью 60 кадров в секунду, и это будет выглядеть гладко!

Заключение

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

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

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

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

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

The advanced options menu allows you to import export or download your project

Если вы сделаете что-нибудь классное, пожалуйста, поделитесь им в комментариях ниже! Или, если у вас есть какие-либо вопросы или разъяснения о чем-либо, я был бы более чем счастлив помочь.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.