Final Foe. О разработке сетевого мультиплеера на Unity

Final Foe. О разработке сетевого мультиплеера на Unity

Привет всем!

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

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

Часть 1. Многопользовательская игра - что это?

Лично мне, играть с друзьями - гораздо интереснее, чем в одиночку. Поэтому я изначально хотел, чтобы в моей игре Final Foe был мультиплеер.

По сути, существует 3 самых распространённых варианта многопользовательской игры:

Локальный мультиплеер
Вариант, при котором двое или более людей одновременно играют на одном и том же устройстве. Не путать с игрой по локальной сети (LAN). Для локального мультиплеера не нужно передавать данные по сети. Разработка почти не отличается от разработки однопользовательской игры. Кстати, в Steam специально для таких игр есть отличная функция Remote Play Together, позволяющая одному игроку подключиться к другому, видеть его экран и управлять удалённо. Эдакий TeamViewer для игр.

Хост-Клиент
Он же Host-Client или Listen Server. Один из игроков одновременно является и сервером и клиентом, т.е. хостом. Остальные игроки - только клиентами. Хост включает сервер на собственном компьютере и подключается сам к себе. Клиенты подключаются к хосту. Все сетевые данные между клиентами передаются через хоста. У хоста всегда нулевой пинг, у удалённых клиентов пинг выше.

Выделенный сервер
Он же Dedicated Server. Существует отдельный независимый сервер, к которому подключаются игроки. Все сетевые данные между игроками передаются через него. Ключевая игровая логика также обрабатывается на этом сервере, поэтому возможностей для читерства гораздо меньше. Все игроки находятся в равных условиях. Такому серверу требуется отдельный хостинг и постоянная поддержка.

Final Foe. О разработке сетевого мультиплеера на Unity

Каждый из этих вариантов имеет свои преимущества и недостатки, поэтому выбор всегда зависит от того, что у вас за игра.

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

Выделенный сервер, при всех своих преимуществах, потребовал бы от меня постоянно оплачивать хостинг и постоянно администрировать, а возможно и масштабировать этот сервер. Это означало, что после выпуска игры мне бы пришлось регулярно тратить деньги и время на поддержание работоспособности мультиплеера в моей игре. А учитывая, что я планировал продавать свою игру по принципу единовременной продажи (Buy-To-Play) - однажды мог бы настать такой момент, когда игру уже никто не покупает, а поддерживать выделенные сервера для ранее купленных копий игры я должен. И в этот момент математика доходов и расходов начала бы работать не в мою пользу.

А вот вариант "Хост-Клиент" давал мне ряд преимуществ. Во-первых, Final Foe - это PVE-ориентированная игра. Игроки в ней должны сражаться на одной стороне, отбиваясь от монстров. Это значит, что небольшая разница в сетевой задержке между хостом и подключёнными игроками не так критична, как в PVP-ориентированных играх. Во-вторых, для варианта "Хост-Клиент" не требуется постоянно поддерживать отдельный сервер. Если несколько друзей хотят сыграть в Final Foe, то один из них просто запускает игру как хост, а остальные подключаются к нему. И всё, они играют.

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

Final Foe - Multiplayer Gameplay

Часть 2. Доступные сетевые решения.

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

Я расскажу вам о паре сетевых решений для Unity. Однако вы наверняка знаете, что существует немало других игровых движков: Unreal Engine, Godot, российский движок Unigine и остальные. Вполне возможно, что вам подойдёт какой-то другой движок, с какими-то другими сетевыми решениями. Например, в движке Unreal Engine - сетевое решение доступно сразу, "из коробки". В какой-то момент я даже хотел перевести проект Final Foe на Unreal Engine из-за этого. Сравнение Unity и Unreal Engine - это тема для отдельной статьи. И эта тема не так проста и однозначна, как может показаться. Лично для меня, и Unity и Unreal Engine - это очень мощные и функциональные инструменты. Со своими преимуществами и недостатками по отношению друг к другу.

Говоря обобщённо, "сетевое решение" (сетевая библиотека) - это дополнительный ассет с библиотекой кода, в которой весь функционал для передачи данных по сети уже написан. Вам остаётся лишь использовать его в своей игровой логике, следуя документации и обучающим материалам. Скачиваем, добавляем в проект. И после этого вы сразу можете делать то, что вам нужно: синхронизировать передвижение персонажей между клиентами, синхронизировать анимации, визуальные эффекты, звуки, переменные, методы и многое другое. Буквально с помощью нескольких строчек кода. А иногда, просто добавив определённый компонент на игровой объект. Нет необходимости вручную формировать и отправлять пакеты с данными. Это уже написано за вас. Конечно, понадобится изучить документацию и понять, что к чему. Но это всё равно гораздо проще, чем писать абсолютно всю сетевую логику для вашей игры с нуля.

Final Foe. О разработке сетевого мультиплеера на Unity

Больше года назад команда Unity Technologies сделала обзорное сравнение нескольких сетевых решений для Unity. Я вкратце рассмотрю три из них.

Photon PUN 2
В проект интегрируется достаточно просто. В YouTube даже есть цикл обучающих видеороликов на русском языке. У Photon есть собственная сетевая инфраструктура, в которой можно создать лобби для подключения между игроками. Поэтому вам не нужно будет беспокоиться о том, каким образом игроки найдут друг друга в сети и подключатся друг к другу. Однако если в вашей игре будет более 100 игроков одновременно - вам нужно будет платить от 95$ в месяц. Чем больше игроков - тем больше стоимость.

Mirror
Простая интеграция. Понятная и полезная документация. Есть возможность использовать сетевой транспорт Steam. Есть обучающие видеоролики от DapperDino по интеграции в сетевую инфраструктуру Steam для подключения игроков друг к другу с помощью сетевого лобби. Огромное и дружелюбное сообщество в Discord, в котором я неоднократно получал полезные ответы на важные вопросы. У сетевой библиотеки Mirror крайне положительные отзывы на Unity Asset Store и репутация надёжного бесплатного сетевого решения.

Netcode
На данный момент - это официальное сетевое решение от Unity. Оно вышло не так давно, поэтому мне пока не довелось поработать с Netcode и я сужу лишь по тем данным, которые мне удалось узнать. Netcode базируется на сетевой библиотеке MLAPI и активно развивается. В интернете появляется всё больше обучающих видеороликов по этому сетевому решению. На официальном сайте сказано, что настроить подключение Netcode к сетевой инфраструктуре Steam достаточно просто. Было бы интересно испытать данное сетевое решение в будущем.

Часть 3. Mirror + Steam.

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

Без использования статического IP-адреса и правильно настроенных портов - велик шанс, что наладить подключение будет довольно проблематично. Возможно игрокам понадобится использовать дополнительные программы типа Hamachi, эмулирующие локальную сеть между ними. Захотят ли ваши игроки этим заниматься? Вряд ли. Поэтому для подбора и подключения игроков во всех современных играх типа "Хост-Клиент" обычно используются специальные промежуточные сервера. Так называемые "лобби". В заранее определённой сетевой инфраструктуре.

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

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

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

Примерно за 4 месяца мне удалось добавить мультиплеер на базе Mirror в свою игру. И ещё 1-2 месяца заняла настройка взаимодействия со Steam. Наверное, можно было бы справиться и быстрее. Не могу сказать, что я торопился.

Final Foe. О разработке сетевого мультиплеера на Unity

Часть 4. Послесловие

До выхода моей игры Final Foe в Steam осталось меньше недели! Так что самое время добавить её в желаемое :-)

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

Присоединяйтесь к социальным сетям моей студии Alekliart. Спасибо за внимание!

Моя студия Alekliart в социальных сетях:

3939
20 комментариев

1. Что по нагрузке на хост? Насколько требования к железу растут для такие клиентов?
2. Что по поводу пинга и того, что у хоста явное преимущество за счёт нулевого пинга?
3. Что с читерством со стороны хоста?

4

1. Требования к железу для хоста растут. Потому что ему приходится обсчитывать и серверную и клиентскую логику. А другим игрокам - только клиентскую. На сколько именно - зависит от того, как именно устроена серверная логика. Скажу так - примерно 70-80% процессорного времени в игре занимает рендеринг, обсчёт физики, работа сборщика мусора. А на саму игровую логику остаётся около 20%. Расти будут только последние 20%, потому что только в этой части процессорной нагрузки между хостом и клиентом есть различия. Предположим, у хоста она будет 40%, а у клиента - 20%. Соответственно, 120% суммарной нагрузки для хоста и 100% суммарной нагрузки для клиента. Не такая уже большая разница. Но повторюсь, всё зависит от конкретной сетевой логики.

Более того, нагрузка на сеть тоже будет отличаться. У хоста пропускная способность должна быть выше. Если играют 4 игрока, то хост должен будет вести обмен с 3-мя игроками, а каждый из клиентов - только с хостом. В моей игре на сцене может быть до 60 персонажей. И у всех постоянно синхронизируется позиция, ротация, анимация и ещё куча всего. Иногда трафик у хоста может составлять, например, 3 Мбит/с. А у клиента раза в 2 меньше. Мои замеры были довольно грубыми, поэтому в этих цифрах я не уверен. Чем меньше синхронизаций - тем меньше пропускной способности требуется. Я проводил сетевые тесты вдвоём, втроём, вчетвером. Скорость интернета у меня не очень высокая. Тем не менее всё работает стабильно.

2. Пинг зависит от ряда факторов: удалённость между игроками, качество сетевого кода, качество сетевой инфраструктуры и т.д. Как я писал в статье - у хоста нулевой пинг, а у клиентов - нет. Соответственно, у хоста в любом случае будет преимущество. В моей игре Final Foe пинг обычно не более 100 ms, иногда меньше. Для PVE-кооператива это не критично.

3. Большинство игр типа "Хост-Клиент" рассчитаны на нескольких друзей, решивших вместе во что-то поиграть. Так что даже если игра соревновательная - было бы странно пытаться читерить. Заниматься реверсивной инженерией или чем-то вроде этого, чтобы заменять исходящие сетевые пакеты - чтобы победить своего знакомого в дружеской игре? Это бред. А если говорить про всякие MOBA или MMO-игры, то в них повсеместно используются выделенные сервера. Подключение "Хост-Клиент" не используется в современных соревновательных играх.

6

Ананасовые вопросы, аж жуть, словно на собеседование на сеньор-юнити-бекенд-разраба попал

1

А расскажите пожалуйста, как вы реализовали Client-Side Prediction/Server Reconciliation, какие есть проблемы синхронизации игроков (с разным пингом), и как вы их решали. Есть ли это в решении Mirror или писали сами. В общем, интересует техническая часть мультиплеера на Unity :)

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

1

Да, думаю что с тех времён многое кардинально поменялось. Если честно, я не до конца понимаю, что конкретно вы имеете в виду под Client-Side Prediction/Server Reconciliation в кооперативном мультиплеере типа "Хост-Клиент", но как я могу предположить - это ряд мер, которые нужно предпринять, чтобы на стороне удалённых клиентов всё работало и выглядело плавно при условии, что ключевая логика обрабатывается на хосте. Т.е. всё, что касается ввода, передвижения персонажа и т.д.

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

Коротко об интерполяции. Если пинг в игре составляет 100 ms, это означает, что клиент получает 10 сетевых пакетов в секунду. Если мы перемещаем персонажа только тогда, когда он получает пакет - то его движение будет дёрганым, как будто у нас в игре 10 FPS. Интерполяция передвижения - это предсказание направления движения персонажа на клиенте. Таким образом, пакетов по-прежнему приходит 10 в секунду, но на стороне клиента всё работает плавно. Например, если вы играете в Albion Online, ваш персонаж куда-то бежит и в этот момент вас резко убивают - ваш персонаж резко отбросится немного назад. К своему настоящему местоположению на сервере.

Так вот, в Mirror - интерполяция на данный момент работает очень плавно и реализуется автоматически при добавлении на сетевой объект компонента NetworkTransform.

Также в Final Foe есть ряд механик, в которых клиент сразу должен получать отклик, не дожидаясь ответа от хоста.

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

Т.к. Final Foe - это не соревновательная ММО-игра, а кооператив для друзей - у меня в данном случае больше свободы действия. Например, я могу реализовывать ключевую логику не только на сервере, но и на клиенте. В данном случае - если игрок-клиент наносит монстру смертельный урон, то он не посылает запрос на сервер мол "Я его точно убил? Можно превратить этого монстра в рэгдолл?". Он сразу отправляет его в рэгдолл, потому что нанесение урона сначала обрабатывается на клиенте. А потом на сервер отправляется уведомительная информация о нанесённом уроне. И сервер синхронизирует нанесение урона и смерть этого моба для всех остальных клиентов, включая самого хоста.

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

Вообще в Final Foe довольно много подобных вещей. Если вы посмотрите видеоролик из данной статьи - вы увидите, что там есть маг в белой мантии. Он создаёт различные предметы и убивает ими противников. Так вот, эти предметы синхронизируются не с помощью NetworkTransform + интерполяция. Мне нужно было, чтобы на всех клиентах, этот созданный предмет был абсолютно точно привязан к магу и следовал бы за ним постоянно. Интерполяция не даёт такой абсолютной плавности и точности позиции и ротации. Это было реализовано по-другому. Клиент, играющий за мага, передаёт всем остальным игрокам данные мага и данные предмета. И на каждом клиенте этот предмет привязывается к этому магу. И его следование за этим магом обсчитывается на клиенте, а не синхронизируется по сети. У предметов в Final Foe вообще нет компонента NetworkTransform.

Сообщение получилось длинным. Надеюсь, я ответил на ваш вопрос)

2

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

2

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