Unity 6.3
0 онлайн 55 гостей 3 в системе
Вход
Введение в сетевое взаимодействие многопользовательских игр Глава 6 из 11 Оригинал, стр. 53

Сетевая синхронизация

Сетевая синхронизация

Сетевая синхронизация необходима для согласованного и честного игрового процесса для всех игроков. Вы уже синхронизировали перемещение и анимацию NetworkObject игрока с помощью модели под управлением клиента. Но игровой процесс редко ограничивается персонажем: игрок может стрелять, открывать двери и взаимодействовать с объектами сцены. Такие взаимодействия тоже должны работать по сети, а их состояния синхронизироваться между клиентами и сервером.

В этом разделе мы настроим клиент-серверный обмен для взаимодействия игрока с окружением. Для этого реализуем сетевые состояния игры и отправку удалённых вызовов процедур (RPC) на сервер и обратно.

Игровая механика Рассмотрим клиент-серверный обмен на примере простой игровой механики.

Добавим на уровень триггерные коллайдеры, которые меняют цвет при контакте с игроком: при касании одного игрока объект принимает один цвет, а при касании другого - другой. Создайте в сцене новый GameObject, например куб. Добавьте BoxCollider и включите параметр IsTrigger. Создайте прозрачный материал, назначьте его компоненту MeshRenderer - в примере используется шейдер URP/Lit - и задайте нейтральный исходный цвет, например белый.

ColorTrigger меняет цвет при срабатывании триггера

Триггер будет получать сетевое значение цвета.

Теперь добавим сетевую синхронизацию. Для передачи изменения цвета по сети используем NetworkVariable и RPC.

Объявление NetworkVariable NetworkVariable - специализированная переменная для синхронного управления состоянием по сети. Изменения на сервере передаются всем клиентам. Такой механизм подходит для непрерывно синхронизируемых данных: позиции, здоровья или, как в этом примере, цвета триггера.

Создадим NetworkBehaviour с именем ColorTrigger и добавим его к объекту триггера. Скрипт будет содержать NetworkVariable m_NetworkColor со значением типа Color.

C#
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);
        }
    }
C#
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:

C#
private void OnTriggerEnter(Collider other) {
    NetworkObject networkObject = other.GetComponent<NetworkObject>();
    if (IsClient && networkObject != null && networkObject.IsOwner) {
        ChangeColorServerRpc(networkObject.OwnerClientId);
    }
}
C#
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 о наиболее распространённых и серьёзных ошибках при создании многопользовательских игр и способах их избежать.