Стартовали новые рейтинги digital-подрядчиковУспейте принять участие! Предварительные результаты.
CREEX TEAM
Прототип сетевой настолки на Unity — «Делаем без красоты, но с головой»
CREEX TEAM
#Программирование игр

Прототип сетевой настолки на Unity — «Делаем без красоты, но с головой»

37 
CREEX TEAM Россия, Краснодар
Поделиться:
Прототип сетевой настолки на Unity — «Делаем без красоты, но с головой»
Клиент

Физическое лицо

Бюджет

55 000

Сфера

Развлечение и спорт

Регион

Россия

Сдано

Декабрь 2024

Задача

Заказчик пришёл с чётким ТЗ: нужен прототип сетевой игры на Unity с NGO, поддержкой Lobby, Relay и Authentication. Ни графики, ни дизайна, ни фишек — только логика и функционал. Суть игры — простая "ходилка": бросаешь кубик, двигаешься по точкам, выполняешь действие. Но всё должно работать по сети, с синхронизацией и управлением сессией.

Решение

На старте мы детально разобрали ТЗ вместе с заказчиком. Главное, что выяснилось:

• Ему не нужна игра как продукт, ему нужна структура сетевого проекта, которую он может развивать или показывать команде.

• UX может быть грубым, но логика должна быть чёткой — всё, что написано в ТЗ, должно выполняться, даже если оно визуально выглядит как интерфейс из 2008 года.

• Он сам хотел понимать, что где происходит в проекте, поэтому — комментарии в коде, документация и чистый код.

С этого момента проект начал восприниматься как максимально честный прототип, без "украшательств", но с полной логической базой. Цель — чтобы любой разработчик потом мог зайти в проект и сразу понять структуру.

1Архитектура — подготовка сцены и фундамента через DI и менеджеры

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

Почему решили использовать DI (Dependency Injection)?

С заказчиком обсуждали, что:

• Он сам будет дальше работать с проектом или подключать других разработчиков.

• Важно, чтобы логика не была раскидана по случайным MonoBehaviour на сценах.

• Компоненты должны быть включаемыми и заменяемыми, без переписывания префабов.

• Не хочется танцев с синглтонами, статикой и прочими утечками памяти, которые потом чинятся слезами.

Поэтому встал вопрос: что использовать как DI-контейнер?

• VContainer

Почему не Zenject:

Zenject — перегружен, и у него периодически странные баги с Unity 2022+.

VContainer — компактнее, быстрее стартует, имеет хорошую интеграцию с Unity Jobs/Addressables, и не требует вороха аттрибутов ради простых вещей.

Заказчик хотел простую структуру, но при этом — чтобы сервисы можно было переиспользовать. Так и родилось ядро на VContainer.

Что конкретно сделали на этапе подготовки:

1. SceneManager (не Unity’s, а наш)

Создан отдельный ISceneLoaderService, реализующий логику переключения сцен через SceneManager.LoadSceneAsync, с передачей нужных параметров. Обёртка нужна, чтобы:

• Управлять сценами централизованно;

• Подключать middleware (например, прогресс-бары, preloader, async data injection);

• Не пихать SceneManager.LoadScene() в каждый UI Button.

• Загружать и проверять по сети состояние сцену у игроков.

Почему так: чтобы в будущем, при расширении, не пришлось пересобирать всё через ScriptableObject события или вручную прокидывать ссылки. Всё контролируется DI, всё централизовано.

2. GameState / AppState менеджер

Создана стейт-машина и базовые состояния

Это простая стейт-машина, чтобы понимать, где находится игрок, и какие сервисы должны быть активны. Например, Relay стартует только в состоянии LobbyHosting, а PlayerSpawner — только в Game.

Сделано это потому что Unity сама по себе не управляет состоянием. Без этого в сетевой игре легко словить ошибку, когда компонент активен не в то время и делает что-то лишнее (спавнит кубик в лобби, подключает Lobby в сцене Game и т.п.)

3. UI Navigation — через DI, а не через FindObjectOfType

Вместо того, чтобы искать UI объекты через Find или SerializedField, UI-канвасы и панели регистрируются в VContainer и вызываются из сервисов.

Примитивный IUIService управляет открытием окон, переключением страниц (типа лобби, подключения, создания сервера и т.д.), так UI становится управляемым, тестируемым и не завязанным на конкретную сцену. Панель можно заменить, отложить инициализацию, подключать поздно (например, если игрок перезашёл).

Как это обсуждалось с заказчиком:

Показывал, что можно сделать «по-старому» — навесить всю логику прямо в LobbyController, но тогда при любом добавлении новой фичи всё плывёт. Он согласился, что проект, скорее всего, будет жить долго, и лучше один раз сделать систему, чем потом "фиксить баги с появлением кнопок".

Он хотел видеть максимум прозрачности: где что находится, как что работает. Поэтому вся DI-интеграция была построена по именованным интерфейсам, с комментариями в LifetimeScope-конфигурации. Указано, что регистрируется, зачем, и как это потом используется.

2Сетевая логика — Lobby, Relay, подключение, слоты

На этом этапе фокус был на сетевом подключении: создание игры, поиск серверов, присоединение, выбор слота, все это должно работать через Lobby + Relay и параллельно по локалке, если интернет отключён. 

Почему именно NGO + Lobby + Relay

С заказчиком обсудили два варианта:

• Photon (старый добрый вариант, но не встроен в Unity и платный);

• Netcode for GameObjects (NGO) с Relay и Lobby — встроенное, официальное, и главное — бесплатное в пределах лимитов.

Выбрали NGO. Он проще в интеграции, хорошо стыкуется с Unity Services. Relay — нужен для обхода NAT (иначе соединения тупо не проходят за роутерами), а Lobby — чтобы игроки могли искать сервера без внешней БД.

Далее приступили к системе слотов.

Каждому игроку нужно выбрать слот. Потому что дальше всё будет строиться на этих слотах — от очереди хода до отображения статуса. Сделали максимально просто:

•  Слоты фиксируются после нажатия "Готов".

•  Только свободные слоты активны.

•  При выходе игрока слот освобождается.

•  Хост может кикнуть любого игрока из слота.

У клиентов кнопка — "Готов", у хоста — "Старт".

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

С заказчиком договорились, что важна прозрачность: у всех игроков отображаются имена, слоты, статус (Готов/Не готов), кто хост. Чтобы не было недопонимания “кто сейчас ждёт кого”.

3Игровая сцена — синхронизация, очереди, действия

На этом этапе — сама игра, то есть та самая "ходилка". Сцена Game, на ней происходит основное действие. У каждого игрока есть персонаж, очки, текущая точка. Всё должно быть синхронизировано, всё должно быть видно всем.

? Очередность и управление ходом

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

Как реализовано:

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

Порядок ходов строится по слотам, выбранным в Lobby.

Когда игрок завершает ход — вызывается NextTurn() у сервера (хоста), и активным становится следующий игрок по списку.

Если игрок выходит — очередь пересчитывается.

Если игрок AFK > 3 минуты — исключается, вызывается SkipTurn().

? Кубик и движение

Бросок кубика — это физический объект, спавнится и кидается только у активного игрока, остальные видят через RPC позицию/значение.

После броска — движение по List Points.

После движения — событие (появляется UI с выбором действий).

У каждого есть PlayerData, синхронизированный через NetworkBehaviour.

Когда один игрок получает очки, все видят это обновление сразу (через ClientRpc).

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

Использовали только прямое взаимодействие по сети, с явной синхронизацией состояний, без NetworkVariable и так далее.

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

Результат

Всё, что здесь есть — результат последовательного диалога. Мы каждый этап проговаривали:

• Что точно нужно

• Что можно упростить

• Что должно остаться масштабируемым

Базовый принцип был такой: игра должна быть максимально "простой" — и в логике, и в коде. То есть — не просто «работает», а понятно, как работает. Никаких лишних абстракций, никаких custom SDK, никакой дикой архитектуры. Только простые, чистые инструменты Unity, чтобы заказчик или другой разработчик мог спокойно в это войти.

Комментарий агентства

Алексей Кострыкин
Алексей Кострыкин

Не всегда разработка - это разработка полноценного продукта.


Стек технологий

  • Unity Unity Среда разработки

Над проектом работали:


Выскажите мнение
Авторизуйтесь, чтобы добавить свой комментарий.
оставить заявку

Хотите заказать похожий проект?

CREEX TEAM с удовольствием обсудит вашу задачу

Оставить заявку