1. Основы сетевой архитектуры и введение в Netcode for GameObjects
Основы сетевой архитектуры и введение в Netcode for GameObjects
Представьте, что вы нажимаете клавишу прыжка в одиночной игре: ваш персонаж мгновенно взмывает вверх, потому что компьютер сразу же обрабатывает ввод и меняет координаты объекта. В мультиплеере всё иначе. Когда вы нажимаете «прыжок», информация об этом должна долететь до сервера, обработаться там, а затем вернуться к вам и ко всем остальным игрокам. Если расстояние между игроками — тысячи километров, данные могут путешествовать сотни миллисекунд. Без понимания того, как устроена сетевая архитектура, ваша игра превратится в хаотичное слайд-шоу, где каждый видит свою версию реальности.
Фундамент сетевого взаимодействия: Клиент-Сервер vs P2P
Прежде чем открывать Unity и устанавливать пакеты, необходимо определиться с топологией сети. В индустрии разработки игр доминируют две основные модели: Peer-to-Peer (P2P) и Клиент-Сервер.
В модели Peer-to-Peer (равный с равным) нет выделенного главного узла. Каждый компьютер в сети является одновременно и клиентом, и сервером. Все участники обмениваются данными напрямую друг с другом.
Современный стандарт для соревновательных и масштабных игр — архитектура Клиент-Сервер. Здесь существует выделенный узел (Сервер), который обладает «абсолютной истиной». Клиенты лишь отправляют запросы на действия (например, «я хочу сдвинуться вправо») и получают от сервера подтвержденное состояние мира.
> Авторитарный сервер — это концепция, при которой сервер не доверяет данным от клиента. Если клиент сообщает: «Я нахожусь в точке Б», сервер проверяет, мог ли игрок физически переместиться из точки А в точку Б за прошедшее время. Если нет — сервер принудительно возвращает игрока назад.
Netcode for GameObjects (NGO) ориентирован именно на архитектуру с сервером, хотя и поддерживает режим Host (Хост). В режиме хоста один из игроков совмещает функции клиента и сервера. Это удобно для кооперативных игр на 2–4 человека, так как избавляет разработчика от необходимости арендовать облачные мощности, но сохраняет логическую структуру «главного узла».
Что такое Netcode for GameObjects и почему он заменил UNet
Долгое время в Unity существовала система UNet, которая была признана устаревшей (deprecated) из-за плохой масштабируемости и сложности в поддержке. На смену ей пришел Netcode for GameObjects — высокоуровневый фреймворк, построенный поверх транспортного уровня Unity Transport Package (UTP).
NGO берет на себя самые рутинные и сложные задачи:
Важно понимать разницу между высокоуровневым неткодом (NGO) и транспортным уровнем. Если транспорт — это «грузовик», который перевозит байты из точки А в точку Б, то NGO — это «логистическая компания», которая знает, какой товар (данные объекта) в какой грузовик положить и кому именно его доставить.
Ключевые сущности NGO: NetworkManager и NetworkObject
Работа с любым сетевым проектом в Unity начинается с создания объекта-диспетчера.
NetworkManager
Это «сердце» вашей сетевой игры. Без этого компонента, расположенного на сцене, ни один сетевой скрипт не будет работать.NetworkManager отвечает за:
NetworkObject
Чтобы обычный игровой объект (GameObject) стал «видимым» для сети, на него должен быть повешен компонентNetworkObject. Каждому такому объекту при создании присваивается уникальный NetworkObjectId. Именно по этому идентификатору сервер понимает, что «Меч игрока 1» на клиенте А — это тот же самый объект, что и «Меч игрока 1» на клиенте Б.Если вы попытаетесь вызвать сетевую функцию у объекта без NetworkObject, Unity выдаст ошибку, так как у системы не будет контекста: кому принадлежит этот объект и кто имеет право им управлять.
Поток данных и концепция Tick Rate
В сетевом программировании время не течет непрерывно, как в Update(). Оно дискретно. Сервер обрабатывает логику с определенной частотой, называемой Tick Rate (частота тиков).
Представим, что ваш сервер работает на Гц. Это значит, что каждые мс сервер делает «снимок» состояния мира и отправляет его клиентам.
Где — длительность одного сетевого кадра, а — частота обновления сети.
Если ваш игровой цикл (FPS) работает на Гц, а сеть на Гц, возникает рассинхронизация. Игрок видит плавную картинку, но сетевые данные приходят реже. Именно здесь вступают в игру методы интерполяции, которые мы будем изучать позже, чтобы «дорисовать» промежуточные положения объектов между сетевыми пакетами.
Жизненный цикл сетевого подключения
Понимание того, что происходит в момент нажатия кнопки «Connect», критически важно для архитектуры. В NGO этот процесс выглядит так:
NetworkManager есть поле Player Prefab — это шаблон, который будет автоматически создан для каждого подключившегося.Проблема задержки (Latency) и RTT
Любое действие в сети ограничено скоростью света и задержками в оборудовании провайдеров. Основной показатель здесь — RTT (Round Trip Time).
> RTT — это время, за которое пакет проходит путь от клиента до сервера и обратно. Часто это значение называют «пингом», хотя технически пинг — это время в одну сторону.
Если ваш RTT составляет мс, то любое ваше действие (выстрел) будет осознано сервером только через мс, а результат вы увидите еще через мс. В соревновательных шутерах это целая вечность.
Для борьбы с этим в NGO применяются техники, которые мы разберем в следующих главах:
Сериализация: как данные превращаются в байты
Компьютеры не умеют передавать «объекты C#». Они передают потоки байтов. Процесс превращения структуры данных в байты называется сериализацией.
В NGO используется эффективная бинарная сериализация. Когда вы используете NetworkVariable<int>, число int (4 байта) упаковывается в пакет. Если вы передаете строку, она занимает гораздо больше места.
Золотое правило сетевого разработчика: передавайте как можно меньше данных и как можно реже.
Вместо того чтобы передавать каждый кадр позицию игрока как Vector3 (12 байт), иногда выгоднее передать только факт нажатия клавиши (1 байт), если сервер может сам рассчитать траекторию. Однако это порождает риск рассинхронизации из-за плавающей запятой (floating point errors), о чем мы также поговорим в разделах про детерминизм.
Режимы работы: Server, Host, Client
При запуске проекта через API NetworkManager.Singleton у вас есть три пути:
Рассмотрим пример. Если два игрока одновременно подбегают к аптечке, в режиме Host компьютер первого игрока (хоста) мгновенно решит: «Я поднял аптечку». Второй игрок (клиент) отправит запрос, сервер его обработает и ответит: «Извини, аптечки уже нет». В этой схеме у хоста всегда есть преимущество в пинге ( мс), что является главным недостатком модели Host-Client в соревновательных играх.
Безопасность и авторитарность
Главная ошибка новичков — позволять клиенту самому решать, сколько у него здоровья или золота. В Netcode for GameObjects по умолчанию принята модель Server Authoritative (Авторитарный сервер).
Это означает, что:
Если вы пишете код для сетевого шутера, логика выстрела должна выглядеть так:
Такой подход делает создание читов (например, на бесконечные патроны) практически невозможным, так как клиентские данные о патронах — это просто визуальное отображение того, что хранится в памяти сервера.
Подготовка окружения
Для работы с NGO в Unity (версии 2021.3 LTS и выше) необходимо установить пакет через Package Manager: com.unity.netcode.gameobjects. Вместе с ним автоматически подтянется Unity Transport.
После установки важно правильно настроить NetworkManager. Одной из критических настроек является Tick Rate. Для динамичных экшенов рекомендуется значение или даже . Для пошаговых стратегий достаточно –. Чем выше Tick Rate, тем больше нагрузка на процессор сервера и на канал связи игрока.
Также стоит обратить внимание на параметр Network Transport. По умолчанию это UnityTransport. В его настройках вы указываете IP-адрес (для клиента) и порт. По умолчанию используется порт . Если вы тестируете игру на одном компьютере, используйте адрес 127.0.0.1 (localhost).
Первые шаги в коде: NetworkBehaviour
Все ваши скрипты, которые должны взаимодействовать с сетью, должны наследоваться не от MonoBehaviour, а от NetworkBehaviour. Это дает вам доступ к специфическим свойствам:
IsServer: истина, если код выполняется на стороне сервера.IsClient: истина, если код выполняется на стороне клиента.IsOwner: истина, если данный экземпляр объекта принадлежит текущему игроку.OnNetworkSpawn(): метод, который вызывается, когда объект успешно инициализирован в сети (аналог Start(), но для сетевой логики).Именно через эти проверки строится ветвление логики. Например:
Без проверки IsOwner вы бы управляли всеми персонажами на сцене одновременно, так как скрипт висит на каждом из них.
Сложности, с которыми вы столкнетесь
Сетевое программирование — это всегда борьба с неопределенностью. Пакеты могут теряться (Packet Loss), приходить в неправильном порядке или задерживаться.
Понимание этих основ — это фундамент, на котором строится всё остальное. В следующей главе мы перейдем к практике и разберем, как синхронизировать данные между игроками без постоянной пересылки тяжелых сообщений, используя мощный инструмент NetworkVariable.