Всем привет! Последние 7 месяцев я разрабатываю свою игру на Godot и Rust для выпуска в Steam. Задумка игры простая — исследование фэнтези-средневекового подземелья в кооперативе с друзьями. Важная особенность проекта — воксельный мир, полностью процедурная генерация подземелий и перманентная смерть всех персонажей отряда.

Итак, это уже 11-й Devlog, но я решил написать на Хабр статью гораздо более объёмную и рассказать про сетевую часть, которую дописал на днях. Думаю, получится интересно как для разработчиков игр, так и для просто любителей технологий.

Godot

Godot — это открытый кроссплатформенный игровой движок с исходным кодом, предназначенный для разработки 2D и 3D-игр. Он распространяется бесплатно под лицензией MIT и поддерживает Windows, Linux, macOS, Android, iOS и Web.

Движок написан на C++, а на GitHub у него достаточно много активных разработчиков, которые постепенно его улучшают. Если посмотреть на Milestone, новая версия движка выходит каждые несколько месяцев, что очень быстро. Также Godot имеет большое и активное сообщество на разных площадках.

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

По моему опыту использования десятка других движков, Godot оказался для меня наиболее удобным и функциональным по всем аспектам. Единственным вариантом лучше может быть создание собственного движка на Rust, однако это подходит не всем из-за медленной скорости разработки и узкой сферы применения.

GDExtension

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

GDExtension — это система расширений Godot, позволяющая писать нативные модули вне ядра движка на других языках, подключая их как динамические библиотеки. Она даёт прямой доступ к API движка без пересборки Godot, обеспечивает высокую производительность, строгий контроль памяти и позволяет выносить тяжёлые подсистемы — генерацию, симуляцию, сетевой код или AI — в отдельные низкоуровневые слои, сохраняя интеграцию с редактором и сценовой системой.

Это значит, что можно использовать C, C++, C#, Java, Python, Rust и другие языки для реализации любых функций игры.

Почему не встроенный сетевой стек Godot

Godot предлагает несколько уровней работы с сетью:

  • Низкоуровневый API (ENet, PacketPeer, StreamPeer) — полный контроль над протоколом.

  • Высокоуровневый Multiplayer API — репликация, authority, ownership, RPC.

  • MultiplayerSynchronizer — автоматическая синхронизация состояний.

Для многих проектов этого достаточно, но для CaveVox стандартный стек оказался слишком ограниченным по гибкости и производительности. Поскольку я активно использую Rust через GdExt, я решил вынести серверную часть наружу и подключить её через мост к отдельному серверу на библиотеке Quinn.

Что такое Quinn и почему QUIC

Quinn — компактная реализация протокола QUIC (Quick UDP Internet Connections) на Rust. QUIC работает поверх UDP, но добавляет:

  • Надёжность TCP-подобную доставку там, где нужно.

  • Мультиплексирование потоков без head-of-line blocking.

  • Встроенное шифрование (TLS 1.3).

  • Быстрое установление соединения (0–1 RTT).

  • Возможность отправлять как надёжные, так и ненадёжные сообщения.

По сути, это современный транспорт, спроектированный именно для realtime-приложений, включая сетевые игры.

Для FPS-roguelike это критично: нужна высокая частота апдейтов, предсказуемые задержки и возможность разделять трафик по независимым каналам (ввод, события, снапшоты).

TOFU и генерация сертификата на лету

Для подключения клиентов к серверу я использую подход TOFU (Trust On First Use). Суть метода в том, что клиент изначально не проверяет сертификат сервера: при первом подключении устанавливается доверие к публичному ключу, который сервер предоставляет. В моём случае сервер генерирует самоподписной сертификат прямо в памяти при запуске — без создания файлов на диске.

Механизм работает так:

  1. Клиент инициирует подключение к серверу через QUIC. Сертификат пока не проверяется.

  2. Сервер создаёт сертификат с ключами RSA/ECDSA в памяти и отправляет клиенту.

  3. Клиент сохраняет публичный ключ в локальной памяти или кэше и доверяет соединению.

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

Такой подход упрощает запуск сервера, устраняет зависимость от файловой системы и минимизирует риски утечки ключей, сохраняя при этом TLS-шифрование и защищённость соединения для realtime-игры.

Архитектура: relay-сервер для кооператива на 4 игроков

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

  • Клиенты сами генерируют одинаковые миры по общему seed (детерминированная процедурная генерация).

  • По сети передаются только события, корректировки и ввод игроков.

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

Пример: игрок A выключает факел → сообщение уходит на сервер → сервер пересылает остальным → все видят выключенный факел. Задержка минимальна, нагрузка на сервер почти нулевая.

Синхронизация взаимодействий с окружением
Синхронизация взаимодействий с окружением

Datagrams vs Bidirectional streams

QUIC позволяет гибко разделять трафик:

  • Datagrams (ненадёжные пакеты): передвижение, анимации, повороты, позиция. Отправляются часто (60–120 Гц), потери не критичны — следующее обновление просто корректирует состояние.

  • Bidirectional streams (надёжные потоки): подключение, урон, смерть, активация объектов, чат. Гарантированная доставка и порядок, но потоки независимы — задержка в одном не блокирует другие.

На этой основе реализованы:

  • Лобби для игроков;

  • Обмен seed’ом и синхронизация мира;

  • Синхронизация позиций и взаимодействий;

  • Синхронизация анимаций и костей персонажей.

Лобби, синхронизация готовности игроков, флаг хоста
Лобби, синхронизация готовности игроков, флаг хоста

Основная сложность заключается в том, чтобы доказать, что сущность на одном клиенте соответствует сущности на другом. Так, например:

  • Игроки — это заранее объявляемые сущности в лобби, их можно сразу привязать к клиентам.

  • Мир — можно передать по seed, однако если воксельный мир имеет изменения (например, можно ставить и ломать кубы), сложность системы растёт.

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

  • Статические предметы — могут синхронизироваться напрямую, если имеют логику (например, открытие, активация, ломание).

  • Предметы инвентаря — требуют отдельной синхронизации: нужно отслеживать владельца, состояние предмета (например, заряд, прочность, слоты), а также корректно обрабатывать перенос между игроками и взаимодействие с миром.

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

Интеграция со Steam

Поскольку игра разрабатывается для Steam, я подключил плагин GodotSteam для работы с ним в движке. Аналогично это можно реализовать через библиотеки на Rust. Для сетевой игры плагин позволяет получать никнейм игрока — без него он не появится в лобби. Кроме того, я добавил отображение ника над игроком в 3D.

Никнеймы игроков
Никнеймы игроков

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

Итог и планы

Сетевая часть готова и работает стабильно. Несмотря на это, в будущем поверх неё уже можно будет добавить:

  • Текстовый чат;

  • Голосовой чат;

  • Отображение списка игроков внутри игры, пинг до игрока;

  • Передача предметов между игроками.

Спасибо, что прочитали — надеюсь, вам понравилась эта статья и была полезна. Если есть вопросы по реализации или архитектуре, пишите в комментариях, с радостью обсудим.

Ссылка на страницу игры.

Также, кому интересно, есть ролик: