Сетевая синхронизация
Сетевая синхронизация
Сетевая синхронизация необходима для согласованного и честного игрового процесса для всех игроков. Вы уже синхронизировали перемещение и анимацию NetworkObject игрока с помощью модели под управлением клиента. Но игровой процесс редко ограничивается персонажем: игрок может стрелять, открывать двери и взаимодействовать с объектами сцены. Такие взаимодействия тоже должны работать по сети, а их состояния синхронизироваться между клиентами и сервером.
В этом разделе мы настроим клиент-серверный обмен для взаимодействия игрока с окружением. Для этого реализуем сетевые состояния игры и отправку удалённых вызовов процедур (RPC) на сервер и обратно.
Игровая механика Рассмотрим клиент-серверный обмен на примере простой игровой механики.
Добавим на уровень триггерные коллайдеры, которые меняют цвет при контакте с игроком: при касании одного игрока объект принимает один цвет, а при касании другого - другой. Создайте в сцене новый GameObject, например куб. Добавьте BoxCollider и включите параметр IsTrigger. Создайте прозрачный материал, назначьте его компоненту MeshRenderer - в примере используется шейдер URP/Lit - и задайте нейтральный исходный цвет, например белый.
ColorTrigger меняет цвет при срабатывании триггера
Триггер будет получать сетевое значение цвета.
Теперь добавим сетевую синхронизацию. Для передачи изменения цвета по сети используем NetworkVariable и RPC.
Объявление NetworkVariable NetworkVariable - специализированная переменная для синхронного управления состоянием по сети. Изменения на сервере передаются всем клиентам. Такой механизм подходит для непрерывно синхронизируемых данных: позиции, здоровья или, как в этом примере, цвета триггера.
Создадим NetworkBehaviour с именем ColorTrigger и добавим его к объекту триггера. Скрипт будет содержать NetworkVariable m_NetworkColor со значением типа Color.
using UnityEngine;
using Unity.Netcode;
public class ColorTrigger : NetworkBehaviour {
public NetworkVariable<Color> m_NetworkColor = new NetworkVariable<Color>(Color. white);
private Material m_InstanceMaterial;
public override void OnNetworkSpawn() {
m_NetworkColor.OnValueChanged += OnColorChanged;
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
if (meshRenderer != null) {
m_InstanceMaterial = new Material(meshRenderer.material);
meshRenderer.material = m_InstanceMaterial;
UpdateMaterialColor(m_NetworkColor.Value);
}
}public override void OnNetworkDespawn() {
m_NetworkColor.OnValueChanged -= OnColorChanged;
}
private void OnColorChanged(Color oldColor, Color newColor) { UpdateMaterialColor(newColor); }
private void UpdateMaterialColor(Color newColor) {
if (m_InstanceMaterial != null) {
m_InstanceMaterial.SetColor(“_BaseColor”, newColor);
}
}
}Когда игрок входит в триггер, скрипт переключает свойство базового цвета экземпляра материала. Для этого используется NetworkVariable m_NetworkColor. Разберём принцип работы: - NetworkVariable хранит фактическое значение цвета и синхронизирует его между всеми клиентами. Скрипт выполняется и на сервере, и на клиентах, но по умолчанию записывать NetworkVariable может только сервер; клиенты могут лишь читать значение Color. - Событие OnValueChanged обновляет базовый цвет материала триггера при каждом изменении NetworkVariable. Скрипт подписывается на событие в OnNetworkSpawn и отписывается в OnNetworkDespawn. NetworkTransform предназначен именно для данных Transform - позиции, поворота и масштаба, - тогда как NetworkVariable может синхронизировать более общие данные: примитивные типы, пользовательские структуры и другие значения игрового состояния. На клиенте нельзя напрямую изменить NetworkVariable, поскольку право записи принадлежит серверу. Клиент должен запросить изменение у сервера; сервер обновляет состояние и передаёт его обратно, после чего клиент видит результат. Для такого обмена используется RPC. Удалённый вызов процедур позволяет одному устройству потребовать от другого выполнить действие или обновление. RPC можно отправлять с клиента на сервер и в обратном направлении.
Рассмотрим, как реализовать RPC для этой задачи.
Серверные RPC Клиент КЛИЕНТ
Только место вызова; на клиенте не выполняется Задержка между клиентом и сервером
MyServerRPC() выполняется в серверной версии obj
Сервер СЕРВЕР
obj существует и на клиенте, и на сервере
Серверный RPC удалённо вызывается клиентом и выполняется на сервере.
Добавление RPC RPC позволяют удалённо вызывать функции на сервере или других клиентах. Метод с атрибутом [Rpc(SendTo.Server)] клиент вызывает на сервере, а метод с [Rpc(SendTo.Client)] сервер вызывает на клиентах. Также поддерживается устаревший синтаксис [ServerRpc] и [ClientRpc] для серверных и клиентских RPC.
RPC похожи на обычные методы, но должны соответствовать нескольким соглашениям:
- Атрибут Rpc: пометьте метод атрибутом [Rpc] и укажите получателя. Например, [Rpc(SendTo.Server)] вызывает метод только на сервере. - Именование: имя метода должно оканчиваться суффиксом Rpc, например DoSomethingRpc.
RPC лучше подходят для отдельных событий - действий игрока или изменений игрового состояния, которые не требуют непрерывной синхронизации. Добавьте следующие методы в скрипт TriggerColor:
private void OnTriggerEnter(Collider other) {
NetworkObject networkObject = other.GetComponent<NetworkObject>();
if (IsClient && networkObject != null && networkObject.IsOwner) {
ChangeColorServerRpc(networkObject.OwnerClientId);
}
}private void ChangeColorServerRpc(ulong playerId) {
∕∕ система команд: для чётных, для нечётных // Простая Simple team system: blueсиний for even, red forкрасный odd Color newColor = (playerId % 2 == 0) ? new Color(0, 0, 1, 0.5f) : new Color(1, 0, 0, 0.5f);
m_NetworkColor.Value = newColor;
}В одиночной игре нужную логику можно добавить в OnTriggerEnter. В многопользовательской игре взаимодействия обычно обрабатываются через клиент-серверный обмен, чтобы состояния всех клиентов оставались согласованными. Вместо прямого изменения m_NetworkColor метод OnTriggerEnter проверяет, является ли текущий экземпляр игры клиентом. Если да, он вызывает ChangeColorServerRpc и передаёт OwnerClientId. Метод выбирает новый цвет по идентификатору игрока - в этом примере чётным номерам соответствует синий, нечётным красный - и обновляет m_NetworkColor. Изменение передаётся всем подключённым клиентам, поэтому каждый экземпляр игры видит одинаковое состояние. Когда m_NetworkColor меняется, на всех клиентах вызывается OnColorChanged и обновляет цвет материала триггера.
NetworkVariable меняет цвет на сервере и синхронизирует его у всех клиентов
Триггер вызывает RPC
Эта простая механика демонстрирует клиент-серверное взаимодействие.
Механика триггера Пример прост, но похожая механика встречается во многих многопользовательских играх, где действия игрока вызывают визуальные изменения окружения. Например:
- В кооперативной головоломке игроки могут воздействовать на окружение кнопками и триггерами. Визуальная реакция игрового мира служит общим сигналом и помогает согласовать действия. - В соревновательном шутере игроки захватывают контрольные точки, которые часто окрашиваются в цвет владеющей ими команды. Этот визуальный сигнал помогает игрокам корректировать стратегию. - В сетевых RPG отдельные области уровня могут накладывать усиления, ослабления и другие эффекты.
RPC и NetworkVariable Выбор между NetworkVariable и RPC при синхронизации данных зависит от задачи:
- NetworkVariable и NetworkTransform подходят для непрерывно синхронизируемых данных: позиции, здоровья или, как здесь, цвета триггера. В этом примере NetworkVariable хранит активный цвет триггера.
- Примеры: здоровье, позиционные данные, игровой счёт.
- Удалённые вызовы процедур (RPC) лучше подходят для отдельных событий, например действий игрока или изменений состояния, не требующих непрерывной синхронизации. В этом примере RPC сообщает серверу, что игрок вошёл в зону триггера, после чего сервер меняет цвет с учётом идентификатора игрока.
- Примеры: действия игрока - выстрел или применение способности; игровые события - создание объекта, начало игры или победа. Оба механизма необходимы для синхронизации сетевой игры: NetworkVariable управляет длительными состояниями, а RPC - отдельными событиями и действиями.
Следующие примеры помогут определить, когда применять NetworkVariable, а когда RPC.
Задача / система
RPC
NetworkVariables
Управление инвентарём
Уведомляют клиентов о подборе или использовании предмета, его добавлении и удалении.
Хранят инвентарь каждого игрока; NetworkList синхронизирует список предметов.
Боевая система
Выполняют атаки и особые приёмы; RPC может наносить урон и эффекты.
Синхронизируют здоровье, состояние игрока и активные эффекты.
Взаимодействие с окружением
Запускают действия: открытие двери или включение механизма.
Хранят состояние интерактивных объектов, например открыта ли дверь.
Цели
Сообщают о выполнении цели.
Отслеживают прогресс, число собранных предметов и выполненных задач.
Подробнее см. на странице документации RPC vs NetworkVariable.
Проектирование многопользовательской игры Освоив основы Netcode, при разработке важно мыслить категориями сетевой многопользовательской игры. Поведение, простое в одиночной игре, часто усложняется, когда его необходимо воспроизвести на нескольких устройствах с помощью NetworkVariable или RPC.
Заранее спланируйте синхронизацию состояний между клиентами и сервером. Для постоянно обновляемых данных используйте NetworkVariable, для отдельных событий RPC. Передавайте только необходимые данные, чтобы сократить сетевой трафик. Одиночную игру можно преобразовать в многопользовательскую, но обычно эффективнее учитывать сетевой режим с самого начала. Заранее решите, какие объекты и действия контролирует сервер, а какие - клиенты. Серверные полномочия дают максимальную безопасность и согласованность, но для снижения задержки некоторых действий их следует разумно сочетать с полномочиями клиента.
Задержка часто становится серьёзной проблемой при создании сетевой игры. Далее рассмотрим её влияние на многопользовательский процесс. Для вдохновения посмотрите доклад Unite 2024 о наиболее распространённых и серьёзных ошибках при создании многопользовательских игр и способах их избежать.