Найти тему
Petr Nagel Games

Первые шаги в создании мульти-плеерной игры для Android

Привет всем! Я - Пётр, вы - вы, а это - статья о моих [проблемах и] попытках реализовать нормальный мультиплеер для Android игры.

Недавно я уже делал пару постов о том, как идёт работа над моей игрой "Бункер 2021", она полностью офлайн, если интересно, можете зайти в профиль да почитать.

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

Решил я, значит, сделать сетевую "гонку" + езду с препятствиями, в стиле Gravity Defied, но в 3D и с флажками. На мой взгляд, это было бы интересно, как с точки зрения реализации, так и с точки зрения "поиграть".

Начал делать.

С режимом "прохождение" в целом возился я не долго, т.к. настроить физику трактора - дело одного вечера. Благо Godot Engine уже имеет всё под капотом.
Далее оставалось наделать уровней, раскидать бонусов, флажков, и вроде как, всё.

А вот режим игры по сети стал для меня целым приключением. И ещё неизвестно, кто в итоге победит.

Работу я вёл поэтапно, изучая основы и плавно повышая сложность реализации.

Ранее я уже работал с сетью, но не в играх, а в приложениях для WEB, в основном это был AJAX и WebSocket.

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

-2

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

-3

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

Оставался только один 100% рабочий вариант - использовать промежуточный сервер.

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

Попытка № 1. WebSocket

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

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

Начал собирать тестовый стенд. Когда всё было готово, я сильно удивился от того, что это работает. Ну знаете: то, что работает с первого раза, скорее всего не работает, как надо. А тут не так. Тут сразу работает.

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

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

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

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

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

Пришлось делать раз в секунду коррекцию позиции. Это снова привело к микро-вздрагиваниям. Такой расклад мне не понравился, и я решил сменить подход.

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

-4

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

Попутно я читал про сеть и про обработки столкновений, и решил написать вторую реализацию.

Попытка № 2. ENet и физический сервер

Моя вторая попытка реализации была достаточно простая. На компьютере был запущен сервер, написанный уже на Godot, а не NodeJS, с использованием ENet, к нему цеплялись устройства (пока что по локальной WiFi сети) и всё это работало.

Общий сервер крутится на компе, к нему присоединяются устройства и регистрируются в специальный список "игроки".

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

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

Вся реализация была основана на официальной документации.

High-level multiplayer

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

В целом я полностью удовлетворился работой игры по WiFi. Даже когда игра идёт через провайдера (у меня это ДомRу). То есть и в локальной сети и через интернет, но по WiFi, всё работает чётко.

Как только я перешел на мобильное соединение, снова начались проблемы.

Иногда данные просто не прилетают, то есть клиент повисает в ожидании отправки данных, а иногда сервер повисает в ожидании.

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

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

Как только происходит расхождение, я отправляю параллельный запрос по WebSocket с корректировками. То есть приложение устанавливает сразу два соединения, одно основное, второе - резервное. Пока я не могу точно сказать, в чем именно проблема, так как воюю с ней прямо сейчас, однако WS соединение к NodeJS на мобильном операторе работает фактически без сбоев, благодаря чему можно поддерживать соединение.

-5

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

Буду думать!

Скоро постараюсь выкатить тестовую сборку игры, если вдруг кому-то будет интересно, пишите в ЛС или в комментарии.

Всем большое спасибо за внимание! Удачных вам проектов!