SCRAP[b] {devlog}
24 subscribers
50 photos
14 videos
SCRAP Brigade | Web-based online game | Development blog
Download Telegram
Здания появляются на карте

#kitchensink
🔥2
Точечные доработки дорожного полотна

#kitchensink
🔥2
Ищем зоны для застройки

#kitchensink
👍2🔥1👻1
Сетевой слой.

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

Начал эксперименты я с хорошо мне знакомого HTTP + JSON.
Комбинация проверенная временем, позволяет удобно дебажить запросы, есть возможность использовать oauth, роутить ендпоинты через nginx. Готовый инструментарий на все случаи жизни.

Однако, в моём случае этот подход хранил много неприятных сюрпризов.
Во-первых, сам HTTP несет с собой много оверхеда — открытие соединения, обмен заголовками, закрытие соединения — и так на каждый запрос. А тайлы мира мы запрашиваем постоянно, особенно при движении на машине. И вместо того чтобы иметь постоянный канал нам приходится постоянно открывать/закрывать соединение.

Во-вторых, JSON очень громоздкий формат. Он безусловно очень удобен для ручного написания структур, для отладки, просмотра что там пришло с сервера, но проблема в том что для 1000 однотипных элементов сами имена свойств могут занимать места больше чем полезные данные! А значит мы гоняем по сети бесполезный балласт, расходуя ценный bandwidth.

В-третьих, JSON это строка, а значит принимающая сторона должна её распарсить превращая в понятные движку структуры. В худшем случае придется создавать managed-сущности, а это тот самый ненавистный GC-pressure.

Пришлось выкидывать этот подход на помойку и искать новый, более эффективный способ.

Начнем с самого формата данных. Вербозный JSON можно заменить на байт-код, либо кастомный свой формат, либо какой-нибудь всем известный protobuf или FlatBuffers. Так мы избавимся от парсинга строк, сократим до 60% объем передаваемых данных и сможем наладить unmanaged-пайплайн для обработки на стороне unity-клиента.

Осталось что-то придумать с HTTP. Как я уже говорил, у него есть несомненное преимущество в виде возможности роутить ендпоинты с помощью nginx и использовать oauth-авторизацию. Если мы возьмем голые сокеты, TCP или UDP — мы потеряем эти возможности.
На помощь пришел WebSocket — он умеет жить в среде http-роутов, его можно закрывать с помощью oauth. Какого-то значимого оверхеда в нашем случае он не несет. Вопрос в том как организовать unmanaged-подход для работы с ним на стороне Unity.

Обычный класс Socket является managed-сущностью, однако работу с ним можно сильно оптимизировать используя пулы и буферы. Но у меня уже большая часть проекта была на unmanaged-ECS и я искал способ ПОЛНОСТЬЮ избавиться от любых классов и использования GC.

Способ нашелся: в ECS-мире есть пакет com.unity.transport. Он полностью unmanaged, создан для использования в ECS, поддерживает WebSocket. Берем в работу и собираем прототип. Прототип собран, но что-то лыжи не едут (

Я вижу в логах nginx что соединение устанавливается, однако далее оно рвется ещё до первых сообщений. Пара часов отладки, логирования всего чего только можно и вдруг я натыкаюсь на информацию что не смотря на WebSocket-природу протокола, он является проприетарным и шлет свои handshake-сообщения. Да, он поддерживает WebSocket, но только для того чтобы работать из браузера при подключении к бэкенду на юнити где также используется com.unity.transport…

Как-то реверс-инженирить этот протокол смысла я не увидел — ненадежно это — и решил двигаться дальше.

К сожалению out-of-the-box решения для unmanaged сетевого взаимодействия я не нашел. Разве что вручную собирать сетевую библиотеку на C++ под все поддерживаемые платформы и подкладывать в сборку. Да, это в целом решение, но на текущей стадии разработки это большой оверхед. Поэтому я вернулся к managed-классу Socket, благо если обложить его пулами и буферами, в рантайме он никакой нагрузки на GC не создаёт.

В итоге мы имеем WebSocket канал взаимодействия с бэкендом, этот канал роутится на отдельный ендпоинт с помощью nginx, по нему мы гоняем бинарные данные и разбираем на клиенте без парсинга строк. PROFIT! )

Продолжение следует…
#devblog
🔥2
С получением игровых локаций разобрались. Здесь важен каждый пакет, потому TCP-природа WebSocket приходится кстати.

Но TCP также несет определенный оверхед, который может быть вреден для получения данных перемещения NPC и других игроков.

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

С «голым» UDP работать сложновато, слишком много всего нужно реализовывать самому, начиная с формата данных, заканчивая handshake и прочими системными сообщениями. Есть готовые open-source-протоколы которые можно использовать.

Я обратил внимание на KCP. Он также умеет работать в режиме гарантии доставки (как TCP), что может пригодится для важных игровых событий вроде обновления статуса игровых объектов, применения поверапов или «попадания в цель».

К сожалению, unmanaged-реализации KCP под юнити я не нашел, но это не беда — я написал свою.
В качестве формата данных я взял FlatBuffers. Он поддерживает delta-сообщения а значит мы можем атомарно обновлять конкретные значения и не гонять по сети весь статус целиком.

В итоге любые изменения состояния шлются быстрыми короткими delta-пакетами, а раз в несколько секунд проводится полная синхронизация.
Мы получили молниеносный и стабильный способ синхронизации состояния с бэкендом, но к сожалению потеряли http-удобняшки вроде роутинга и oauth. Придется с этим как-то жить )
#devblog
🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Farewell, Unity…
Подробности позже

#kitchensink
🙈2