Gamedev Андрей Верещагин
3 759

Создание мультиплеерной RTS на Unreal Engine — опыт авторов The Maestros

Организация передачи данных для стабильной игры.

В закладки

Автор RTS The Maestros Эндрю Эрридж в блоге на сайте Gamasutra рассказал о том, с какими трудностями он столкнулся при создании многопользовательской стратегии на движке Unreal, не приспособленном для такого рода игр. Мы выбрали из материала главное.

Unreal Engine построен вокруг элементов, необходимых для создания шутеров. В частности, это касается и сетевого кода движка. По умолчанию он использует модель клиент-сервер, схожую с той, что применялась в Tribes. Эрридж отмечает, что такой способ сетевого взаимодействия — это «чистый лист», поэтому его можно использовать для создания чего угодно.

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

Чем дольше клиенту приходится ждать ответа от сервера, тем выше вероятность того, что «предсказание» окажется неверным. Лаги же возникают из-за того, что игра ошибочно предположила, что вы, например, продолжите двигаться вперёд, в то время как вы решили свернуть, а, получив ответ от сервера, отправила вас туда, куда вы на самом деле отправлялись.

Частота обновления сервера шесть раз в секунду
Частота обновления сервера 30 раз в секунду

Сетевая модель 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 юнитов одновременно, то лагов и задержек не будет.

Показатели при одновременном движении 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. После перевода разработчики могли вручную распаковать каждую переменную в функцию, чтобы отсеять нулевые. Всё это позднее снова собирается воедино и снова переводится в определённый тип команды.

#опыт #unreal

{ "author_name": "Андрей Верещагин", "author_type": "editor", "tags": ["\u043e\u043f\u044b\u0442","unreal"], "comments": 12, "likes": 33, "favorites": 37, "is_advertisement": false, "subsite_label": "gamedev", "id": 29943, "is_wide": false, "is_ugc": false, "date": "Wed, 24 Oct 2018 14:50:07 +0300" }
{ "id": 29943, "author_id": 22254, "diff_limit": 1000, "urls": {"diff":"\/comments\/29943\/get","add":"\/comments\/29943\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/29943"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 64954 }

12 комментариев 12 комм.

Популярные

По порядку

Написать комментарий...
9

Как то неожиданно статья обрывается :)

Ответить
1

Большая часть пакетов - служебные символы. Что типично для json. Если бы сократили или, ещё лучше, если бы использовать протобаф, то уменьшили бы размер пакетов. Думаю, даже по дефолту без магии раза в 2.

Ответить
3

Паравоз еблановоз что ты сегодня нам привез! protobuf - детишки!

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

Ответить
1

Peer-to-peer даёт раздолье для мапхаков и прочей ереси. В том же Starcraft2 мапхак, анализируя данные присланные от другого игрока, способен отображать даже куда смотрит этот самый игрок, не говоря уже про стандартные ресурсы, продакшен и раллипоинты войск.
Очень интересно как создатели игры планируют бороться с читерами, а то маленькая инди-компания Близзард за 8 лет не смогла решить эту проблему.

Ответить
0

Только ли 8? Скорее всего проблема ровесница первого WarCraft, если говорить о Blizzard. В данном же случае скорее всего никто не будет заморачиваться делать мапхак.

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

Ответить
1

Я не очень хорошо знаю вар3.
Проблема в том, что чтобы узнать что враг не должен видеть, твой клиент всё равно должен получить полную информацию от противника, чтобы знать, что видит он.
Плюс p2p может вызывать много проблем, как это уже было показано на примере For Honor. Плюс дропхаки, плюс возможность узнать точный IP противника и заддосить его и т.д.
Так что вопрос "А так ли нужен пинг 30 вместо пинга 60" учитывая все минусы остаётся открытым.

Ответить
0

Почему бы не отправлять только координаты курсора и нажатия кнопок/клавиш?

Ответить
1

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

Ответить
0

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

Ответить
0

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

Ответить
0

В общем всё понятно получается те же проблемы, что у юнити и сетевых ассетов типа фотона и юнета. А я то думал хотя бы на анреале всё хорошо с этим а нет.

Ответить
0

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

Ответить
0

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjog" } } }, { "id": 10, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-250597-0", "render_to": "inpage_VI-250597-0-1134314964", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=clmf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudo", "p2": "ftjf" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvc" } } } ]
В лутбоксы начали включать багфиксы
Подписаться на push-уведомления