Создание мультиплеерной RTS на Unreal Engine — опыт авторов The Maestros
Организация передачи данных для стабильной игры.
Автор RTS The Maestros Эндрю Эрридж в блоге на сайте Gamasutra рассказал о том, с какими трудностями он столкнулся при создании многопользовательской стратегии на движке Unreal, не приспособленном для такого рода игр. Мы выбрали из материала главное.
Unreal Engine построен вокруг элементов, необходимых для создания шутеров. В частности, это касается и сетевого кода движка. По умолчанию он использует модель клиент-сервер, схожую с той, что применялась в Tribes. Эрридж отмечает, что такой способ сетевого взаимодействия — это «чистый лист», поэтому его можно использовать для создания чего угодно.
Модель работает следующим образом: пользователь посылает на команды на центральный сервер, а тот, обработав их, отправляет обратно клиенту данные о состоянии игрового мира через заданные временные интервалы. Обновление происходит не менее 20 раз в секунду, а в промежутках игра «предсказывает» события, чтобы не возникало задержек.
Чем дольше клиенту приходится ждать ответа от сервера, тем выше вероятность того, что «предсказание» окажется неверным. Лаги же возникают из-за того, что игра ошибочно предположила, что вы, например, продолжите двигаться вперёд, в то время как вы решили свернуть, а, получив ответ от сервера, отправила вас туда, куда вы на самом деле отправлялись.
Сетевая модель Unreal не поддерживает большого количества персонажей, ведь данные о каждом из них отправляются отдельно, а это нагружает соединение. Поэтому в RTS вроде Age of Empires, Starcraft, Warcraft или Total Annihilation используется другой подход.
В классических стратегиях применятся модель peer-to-peer. В этом случае данные каждого игрока отправляются другим игрокам. С одной стороны, в таком случае не нужны выделенные сервера, а с другой разница в пользовательском «железе» и соединении может стать препятствием для стабильной игры. Вся система будет настолько отзывчивой, насколько отзывчива игра на стороне пользователя с самой высокой задержкой.
Кроме того, некоторые домашние файерволлы препятствуют прямому подключению пользователей друг к другу. Для обхода этой проблемы в некоторых современных RTS вроде Age of Empires II HD используется прокси, однако это повышает задержку.
В такой модели клиенты передают друг другу лишь вводимые пользователями команды, а состояние мира симулируется самой игрой так, чтобы оно было идентичным для всех участников. Такая модель мультиплеера называется детерминированной. Клиентам приходится ждать ввода команд от каждого игрока, прежде чем исполнить их, а отсюда появляется задержка ввода. К счастью, в RTS такая задержка не слишком ощутима из-за непрямого управления.
Авторы The Maestros посчитали, что частоты обновления, доступной по умолчанию в Unreal Engine, должно им хватить. По словам Эрриджа, разработчики не планировали делать игру, в которой в битве сходились бы сотни или тысячи юнитов, поэтому пропускная способность каждого отдельного игрока не обязательно должна быть очень высокой.
Лимит войск рассчитывался исходя из средней скорости соединения пользователей. Создатели игры посчитали, что если обмен данными с сервером будет происходить со скоростью 30 раз в секунду, а положение и направление каждого отдельного персонажа будет содержаться в пакетах размером в четыре байта, то каждую секунду игра будет посылать на сервер данные объемом 720 байт каждую секунду.
Скорость в один мегабит предполагает, передачу 131072 байта в секунду. Разделив второе значение на первое, разработчики пришли к выводу, что если на карте будут находится от 150 до 200 юнитов одновременно, то лагов и задержек не будет.
В Unreal есть два основных способа передачи данных между клиентом и сервером: репликация переменных и отправка RPC. В первом случае, информация отправляется на сервер, как только она меняется. RPC — это функции, которые клиент «просит» сервер изменить. Разработчики The Maestros выбрали второй способ для передачи данных о командах, отдаваемых юнитам.
Первым делом клиент отправляет серверу JSON-текст, в котором содержится ID юнита и его координаты. Команда движения выглядит следующим образом.
{“commandType”:”MOVE”,“unitIds”:
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21],”location”:
{“x”:100.0,”y”:200.0,”z”:300.0}}
В этом примере 131 знак, то есть при отправке этот пакет данных будет весить около 131 байта. Вместе с информацией, которую добавляет движок, его вес увеличивается до 260 байт. Тесты показали, что каждый раз игроки будут отправлять примерно 8 тысяч байт в секунду. Разработчики оказались довольны такими результатами, так как пропускная способность интернета большинства игроков выше этого значения.
Впрочем, если в одном матче встречались более двух пользователей, производительность падала. Поэтому авторы The Maestros стали оптимизировать пакеты данных. Например, они приняли решение избавиться от информации о названии команды, «освободив» четыре байта.
Unreal Engine не приспособлен, чтобы хорошо «запаковывать» данные для передачи по сети. Однако движок может использовать данные типа 3float и vector, которые намного «дешевле» сериализовать и десериализовать. Авторы игры создали объект FastEvent, содержащий в себе простые данные и позволивший снизить вес отправляемых пактов до 110 байт. Таким образом, каждую секунду игроки уже отправляли не 8 тысяч байт, а лишь 2 тысячи. И это в том случае, если очень активно кликать мышью.
Из-за ограничений RPC, разработчики были вынуждены отправлять данные в виде строк и примитивов. Массивы пришлось писать вручную как структуры.
У каждой команды был свой объект, который имплементировал простой интерфейс с методом «перевода» команды в FastEvent. После перевода разработчики могли вручную распаковать каждую переменную в функцию, чтобы отсеять нулевые. Всё это позднее снова собирается воедино и снова переводится в определённый тип команды.