Ray of Life
Кросс-клиентская мультиплеерная игра в реальном времени для мобильных устройств (Unity 2021.3 LTS, Photon PUN 2). В постапокалиптическом открытом мире на автомобилях соревнуются за луч жизни - источник энергии, который появляется в случайной точке карты: нужно добыть ключ на одной из активных станций, добраться до луча, удержать его и зарядить аккумулятор, попутно отбивая ключи у соперников. Победитель определяется по накопленной энергии либо по истечении 10-минутного раунда. Проект реализует полный игровой цикл от матчмейкинга до экрана результатов с детерминированной синхронизацией мира между всеми клиентами.
Задача
Создать готовый к публикации мобильный мультиплеер в реальном времени, в котором:
- несколько игроков видят одинаковое состояние мира - позицию луча, активные станции, ключи, уровни энергии - независимо от момента входа в комнату;
- соревновательная механика устойчива к гонкам состояний (race conditions): одновременная зарядка от одного луча, кража ключей, одновременное завершение матча;
- раунд завершается синхронно у всех ровно через 10 минут, без расхождения таймеров;
- управление и камера комфортны для сенсорного экрана;
- весь стек укладывается в бесплатные/доступные инфраструктурные лимиты (relay-сервер Photon, без собственного бэкенда).
Основная сложность - не графика, а сетевой детерминизм: в peer-to-peer-модели Photon без выделенного авторитетного сервера нужно гарантировать, что у всех клиентов мир сходится к одному состоянию.
Решение
Архитектура построена на авторитетной модели master-client: один клиент-хозяин владеет генерацией событий мира и игровым таймером, остальные получают состояние через реплицируемые вызовы.
- Матчмейкинг - автоматический вход в случайную комнату с фолбэком на создание новой (JoinRandomRoom, OnJoinRandomFailed, CreateRoom), лимит 3 игрока, авто-старт раунда при заполнении.
- Репликация состояния через буферизированные RPC (RpcTarget.AllBufferedViaServer): создание луча, активация станций, передача ключей, изменение энергии буферизуются на сервере, поэтому поздно подключившийся клиент получает корректное состояние мира, а не пустую сцену.
- Распределенная синхронизация времени: хозяин транслирует UTC-метку старта, каждый клиент локально отсчитывает обратный таймер от общей точки - раунд заканчивается одновременно у всех, без накопления дрейфа.
- Детерминированная генерация событий: координаты луча и индексы двух активных станций вычисляются хозяином и рассылаются как параметры RPC, гарантируя идентичную геометрию мира на всех устройствах.
- Соревновательная механика на синхронизированных счетчиках: общий счетчик игроков у луча (beamPlayers) исключает одновременную зарядку - при конфликте процесс зарядки прерывается; кража ключа реализована через raycast по направлению взгляда с обменом владением ключа и штрафом по заряду.
- Кросс-сценовая передача данных: итоги раунда сериализуются через Json.NET в PlayerPrefs и восстанавливаются на экране результатов с сортировкой по очкам.
- Мобильный UX: поверх Cinemachine FreeLook реализован собственный слой мультитач-жестов (pinch-zoom камеры с клампингом радиуса орбиты, свайп-орбита), экранные элементы управления автомобилем и динамический мини-радар.
Архитектура и сложности
Структура приложения - три сцены с четким разделением ответственности:
- Menu / Launcher - подключение к Photon, ввод никнейма, матчмейкинг.
- Main / GameManager - игровой цикл, спавн игроков, управление комнатой.
- Finish - таблица результатов.
Ключевые инженерные решения и преодоленные сложности:
- Консистентность распределенного состояния. Главный риск любого realtime-мультиплеера - расхождение мира между клиентами. Решено переводом всех изменяющих мир событий в буферизированные серверные RPC и централизацией их источника на master-client. Late-join перестал ломать сессию.
- Гонки состояний в соревновательной логике. Зарядка от луча, кража ключей и завершение игры - операции, которые могут произойти у нескольких игроков одновременно. Введены синхронизированные счетчики и проверки владения, прерывающие конфликтующие процессы вместо их дублирования.
- Рассинхрон таймеров. Вместо передачи оставшегося времени (накапливает лаг) транслируется единая точка отсчета, а обратный отсчет считается локально - классический и устойчивый паттерн сетевого времени.
- Интеграция физики автомобиля с сетью. Vehicle-контроллер из Standard Assets обернут в сетевую модель владения (PhotonView.IsMine) с автоматическим торможением при отпускании газа и спавном по высоте террейна (Terrain.SampleHeight).
- Навигация и мини-карта. Реализовано преобразование мировых координат в координаты радара (RealToMap) с динамическими маркерами игроков, луча и станций и подсветкой носителя ключа.
- Сервисный доступ к подсистемам через статическую точку входа (Helper.init), упрощающий связность UI, сетевого слоя и игровой логики в рамках компактного кодового объема.
Результат
- Завершенный сквозной мультиплеерный цикл: матчмейкинг, синхронный раунд, подсчет очков, таблица результатов - без выделенного бэкенда, только на relay-инфраструктуре.
- Детерминированное состояние мира у всех клиентов, включая поздно подключившихся, - устранены рассинхрон и пустые late-join-сессии.
- Синхронное завершение раунда на всех устройствах независимо от момента входа и расхождения системных часов.
- ~12 связанных игровых систем (матчмейкинг, репликация, таймер, зарядка, кража ключей, мини-карта, камера, физика, UI, результаты) в компактной поддерживаемой кодовой базе из ~1000 строк прикладного кода.
- Оптимизация сетевого трафика: передается не состояние каждого кадра, а только события мира (создание/уничтожение луча, передача ключей, изменения энергии), что минимизирует нагрузку на канал - критично для мобильных сетей.
- Адаптация под мобильную платформу: альбомная ориентация, мультитач-камера, экранное управление - игра управляема одной рукой на сенсорном экране.
Стек
- Движок: Unity 2021.3.18 LTS (C#)
- Сетевой слой: Photon PUN 2 (Realtime, Chat) - матчмейкинг, буферизированные RPC, master-client authority
- Камера: Cinemachine FreeLook + собственный слой мультитач-жестов
- Физика/мир: Unity Physics, Terrain, Standard Assets Vehicles
- UI: TextMesh Pro, UI Toolkit, uGUI; динамический мини-радар
- Данные: Json.NET (Newtonsoft), PlayerPrefs (кросс-сценовое состояние)
- Сеть/HTTP: UnityWebRequest, System.Net.Http
- Платформа: Android (мобильные устройства, альбомная ориентация)
