Сетевой код для кооп-игры в Unity: Mirror vs Netcode for GameObjects
Я делал сетевой кооп на двух проектах: один — свободные подключения через Steam-relay, другой — закрытая P2P-комната до 4 игроков. Оба раза стоял выбор между Mirror и Unity Netcode for GameObjects (NGO). Каждый раз решение менялось, и каждый раз через месяц казалось, что выбрал бы наоборот. Расскажу честно: где Mirror лучше, где NGO, какие подводные камни общие, и что точно стоит делать одинаково в обоих.
Цифры по версиям: Mirror 89.x, Netcode for GameObjects 2.x в Unity 6 (6000.x). Mirror — community-driven форк UNet (старого Unity-нетворкинга), NGO — официальное решение Unity, наследник MLAPI.
Сначала — что такое «кооп»
Под кооп тут подразумевается:
- 2–8 игроков, обычно знакомых.
- Один из них — host (одновременно сервер и клиент), остальные — клиенты.
- Нет никакой системы matchmaking'а с тысячами игроков.
- Анти-чит — на минимуме (друзьям обычно не до читов).
Это другой жанр, чем competitive multiplayer на 100 игроков. Здесь нам подходят host-authoritative (или host-only) архитектуры, без отдельного dedicated server'а. Если у тебя ММО или соревновательный шутер — обе библиотеки работают, но требуют сильно больше кода и серверов.
Mirror: что это и кому
Mirror — open-source библиотека, форк старого Unity Networking. Развивается с 2018 года, активно поддерживается, много готовых ассетов и расширений (Edgegap, Steamworks plugins).
Стандартная схема Mirror — host-authoritative. У тебя есть NetworkBehaviour'ы со SyncVar'ами и Command/ClientRpc:
public class Player : NetworkBehaviour
{
[SyncVar(hook = nameof(OnHealthChanged))]
public int health = 100;
[Command]
public void CmdAttack(GameObject target)
{
// выполняется на сервере
target.GetComponent<Player>().TakeDamage(10);
}
[Server]
private void TakeDamage(int amount)
{
health -= amount;
}
private void OnHealthChanged(int oldVal, int newVal)
{
// колбэк на клиенте
UpdateHealthBarUI(newVal);
}
}
Что хорошо в Mirror:
- Зрелая экосистема. Готовые транспорты (KCP, Telepathy, Edgegap), плагины Steam Lobby, Mirage и другие форки.
- Source-кодген прозрачный: добавил
[Command], всё работает. - Документация — community wiki, много примеров.
- Лицензия MIT, никаких подписок Unity Services.
- Сериализация компактная, легко расширяется собственными типами.
Что болит:
- Архитектура наследована от UNet, и местами видны компромиссы.
NetworkIdentity,NetworkServer, статические менеджеры — это не самый современный код. - Нет встроенного сглаживания/интерполяции — берешь сторонний пакет (Mirror NetworkTransform делает базово, но для шутера слабовато).
- Anti-cheat почти ноль — Mirror не претендует, и для соревновательного PvP всё пишешь сам.
Netcode for GameObjects: что это и кому
NGO — Unity-овский официальный нетворк-стек 2.0. Опирается на Unity Transport (UTP) — низкоуровневый транспорт. Идея похожа: NetworkBehaviour'ы, NetworkVariable, ClientRpc/ServerRpc.
public class Player : NetworkBehaviour
{
public NetworkVariable<int> Health = new(100);
public override void OnNetworkSpawn()
{
Health.OnValueChanged += OnHealthChanged;
}
[ServerRpc]
public void AttackServerRpc(NetworkObjectReference targetRef)
{
if (targetRef.TryGet(out var target))
target.GetComponent<Player>().Health.Value -= 10;
}
private void OnHealthChanged(int oldVal, int newVal)
{
UpdateHealthBarUI(newVal);
}
}
Что хорошо:
- Официальное решение. Документация на docs.unity.com, обновляется вместе с Unity.
- Интеграция с Unity Services: Lobby, Relay, Authentication. Если запускаешь без выделенного сервера — всё через Unity Relay, без возни с NAT-traversal'ом.
- Современная архитектура:
NetworkVariable<T>, делегаты вместо статики, прозрачнее работа с спавном. - UTP под капотом — поддержка reliability/unreliable channel'ов, fragmenting, encryption.
- Anti-tamper для NetworkVariable: server-write only по умолчанию.
Что болит:
- Молодая. До 2.0 многие вещи менялись каждый минор. Если найдёшь старый туториал — половина API уже не работает.
- Меньше готовых решений. Lobby/Relay требуют Unity Services, и если хочешь Steam — пишешь свой transport (NetworkTransport for Steam от Unity Game Tech есть, но это отдельный пакет).
- Зависимость от Unity Services означает квоты, абонентка, потенциально — лимиты в free-tier'е.
- Обвязка Steam-only релизов сложнее: всё-таки Mirror писан с учётом Steam-mod'инга.
Как выбирать
Я смотрю на эти вопросы:
Steam-релиз?
Если Steam — Mirror по умолчанию. Много примеров с Steamworks, FizzySteamworks transport, общая практика. На NGO Steam-кооп тоже работает, но интеграция больше ручная.
Кросс-платформа с консолями?
NGO здесь часто проще, потому что Unity Services работают на всех платформах одинаково. Mirror на консоли можно, но лицензионные SDK Sony/MS имеют свои квирки.
Хочешь Unity Relay для NAT-обхода?
NGO. Mirror тоже умеет relay (через Edgegap или собственный сервер), но Unity Relay — самый простой способ для маленькой команды.
Ассет-стор-зависимость окей?
Если нет — Mirror, чистая лицензия MIT. Если ты уже на Unity Pro и Unity Services — NGO бесплатно.
Команда маленькая, опыта мало?
NGO. Документация Unity и интеграция с туториалами — снижают порог. Mirror требует чуть больше «копать самому».
Что одинаково: основные ошибки
Независимо от выбора, есть классы багов, в которые упираются все.
Race condition при спавне
Игрок подключился, заспавнили его, у клиента ещё не пришли SyncVar'ы — а скрипт уже пытается читать. Решение в обоих:
- Mirror: переопределяй
OnStartClient/OnStartLocalPlayerи инициализируй UI оттуда, не вStart. - NGO: используй
OnNetworkSpawnвместоStart.
Authoritative input
Если игрок жмёт «прыжок», это не должно быть «локально прыгнул, потом отправили на сервер». Это должно быть «отправили на сервер, сервер решил, прыгнули или нет, разослал клиентам».
[Command]
void CmdRequestJump()
{
if (!IsGrounded()) return;
velocity.y = jumpForce;
}
Иначе ловишь дубликаты прыжков на лагах, рассинхрон позиций и невозможность дебага.
NetworkTransform: чудес нет
NetworkTransform в обеих библиотеках синхронизирует позицию через snapshot'ы по сети. Без интерполяции на низких rate'ах ты получишь дёрганый бег. С интерполяцией — задержку.
Для кооп-игры обычно достаточно базовой интерполяции из коробки + send rate 20–30 Hz. Для шутера — нужны prediction и reconciliation, и это уже мегаломания (про неё отдельная статья).
Time-зависимая логика
Время на хосте и клиенте отличается на пинг. Если у тебя кулдаун «5 секунд», и ты считаешь его на клиенте, на хосте его уже истёк, у клиента — ещё нет. Авторитет — у хоста, клиент только запрашивает.
Spawning prefab'ов
Префабы для сетевого спавна должны быть зарегистрированы заранее. В Mirror — список в NetworkManager. В NGO — Network Prefab List ScriptableObject. Если префаб не в списке, при Spawn/NetworkSpawn на клиенте получишь ошибку «unknown prefab».
Подключение и отключение
Если хост ушёл — что делать? Самое плохое решение — игра падает. Адекватные:
- Host migration (один из клиентов становится новым хостом). Сложно, в Mirror и NGO коробкой нет.
- «Хост закрыл сессию», все возвращаются в меню. Просто и работает.
Я почти всегда делаю второй вариант, и предупреждаю в UI «если хост уйдёт — сессия закроется».
Сериализация: компакт и совместимость
Большие [SyncVar] или NetworkVariable — расход трафика. Не синхронизируй то, что можешь вычислить на клиенте.
Пример: вместо синхронизации currentSpeed — синхронизируй input. Клиент получит moveX/moveY, сам посчитает, как двигать персонажа. Меньше байт, больше реактивности.
Кастомные типы: оба фреймворка позволяют писать свой NetworkReader/NetworkWriter для типов, которые в коробке нет. Это спасает, когда у тебя структуры с битфилдами (например, состояние игрока «в воде/в воздухе/на земле»).
Lag compensation и cheating
В кооп почти не нужно. Но если обещаешь PvP-режим — готовься к:
- Server-side validation хитов (rewind world по timestamp клиента, проверка попадания на той позиции).
- Authoritative health (никаких
localPlayer.health -= 10). - Cooldown'ы по серверному времени.
- Защита от input flood'а (rate-limit на сервере).
В кооп всё это можно не делать, и обычно — не делают. Но граница между «кооп» и «PvP-кооп» (frenemies, sabotage-механики) тонкая, и я перестраховываюсь.
Тестирование сетевого кода
Один из самых недооценённых навыков. Несколько советов:
- Запускай два билда на одной машине. В Unity 6 есть Multiplayer Play Mode (через Package Manager) — открывает несколько окон Editor'а с разделёнными сценами. Спасает от постоянной сборки билдов.
- Симулируй лаг. У NGO в UTP есть Network Simulator (latency, jitter, packet loss). У Mirror — параметры в KCP transport'е. Прогоняй features под 100 мс пинга, иначе на проде сюрпризы.
- Логируй RPC'и. В девбилде включи лог каждого Command/ServerRpc с timestamp'ом. Когда багуется флоу — без лога не разберёшься.
- Тесты на отключение. Что происходит, когда клиент потерял соединение в разгар события? В лучшем случае — graceful timeout. В худшем — race condition с двойными спавнами.
Гипотетический switch и опыт миграций
Я переходил с Mirror на NGO один раз, и это была неделя работы на проекте средней сложности (15 NetworkBehaviour'ов, 30 RPC'ов). Что узнал:
- API похож, но не один в один.
SyncVar.hook≠NetworkVariable.OnValueChangedпо нюансам. - Сериализация кастомных типов делается по-разному.
- NetworkTransform ведут себя слегка по-разному — пришлось пересобирать ощущение управления.
- Интеграция UI и spawn-flow — самое долгое. Эти места специфичны для каждого проекта.
Не делай миграцию в середине разработки, если только это не критично. Лучше один раз ошибиться в выборе и довезти, чем переписать.
Что я бы выбрал сегодня для нового проекта
Если кооп через Steam, до 4 игроков, без выделенных серверов — Mirror. Steam-friendly экосистема, MIT-лицензия, проверенная база.
Если кооп через мобилки/консоли с Unity Relay, без Steam-обвязки — NGO. Unity Services закрывают NAT-обход и lobby за выходные.
Если PvP с lag compensation и dedicated server — оба требуют сильно больше работы. Тут стоит посмотреть ещё на Photon Fusion или собственный сервер на UTP.
Что почитать
Mirror — официальная документация и канал на YouTube (Mirror Networking, JesusLuvsYooh, Coding with Buz). NGO — docs.unity.com и пример Boss Room (репозиторий Unity-Technologies/com.unity.multiplayer.samples.coop) — там полноценный кооп-проект с инвентарём, drop-in/drop-out, lobby через Relay.
Главное — не пиши свой нетворк-стек на сокетах. Каждый, кто пробовал, рассказывает историю про неделю отладки UDP-handshake'а. Ни Mirror, ни NGO не идеальны, но они делают за тебя 80% грязной работы. И это правильный размен.