Мультиплеер — Netcode for GameObjects
NetworkObject, NetworkVariable, ServerRpc/ClientRpc — основы сетевой игры.
Мультиплеер — отдельная вселенная сложности, но Unity Netcode for GameObjects (NGO) сильно упрощает старт. Это официальный пакет Unity для синхронизации GameObject между сервером и клиентами в архитектуре client-server.
WebSocket-сервер с собственным протоколом + ваш state-менеджмент. Каждое поле, которое должно быть видно у всех, вы синхронизируете руками: emit → on → update Redux/Zustand.
NetworkObject автоматически синхронизирует Transform и NetworkVariable между сервером и клиентами. RPC — это вызовы методов через сеть, как удалённый emit. Транспорт абстрагирован (Unity Transport, Relay, Steam, и т.д.).
Архитектура NGO
NGO работает в режиме authoritative server (сервер — источник правды). Варианты роли:
- Server — выделенный сервер, не имеет локального игрока. Для соревновательных игр и dedicated servers.
- Host — клиент, который одновременно сервер. Удобно для P2P и кооператива.
- Client — подключённый игрок.
NetworkManager — главный синглтон, который запускает соединение: StartHost(), StartServer(),
StartClient(). На сцене один.
NetworkObject — главный компонент
Чтобы GameObject жил в сети, ему нужен NetworkObject. Это даёт:
- Уникальный сетевой ID, одинаковый у сервера и клиентов.
- Привязку к owner (обычно — клиенту, который этот объект “контролирует”).
- Spawning —
NetworkObject.Spawn()на сервере создаёт объект и на всех клиентах.
using UnityEngine;
using Unity.Netcode;
public class EnemySpawner : NetworkBehaviour
{
[SerializeField] private GameObject enemyPrefab;
public override void OnNetworkSpawn() {
if (!IsServer) return;
var enemy = Instantiate(enemyPrefab, transform.position, Quaternion.identity);
enemy.GetComponent<NetworkObject>().Spawn();
}
}
Prefab, который вы спавните по сети, должен быть зарегистрирован в NetworkPrefabsList на NetworkManager. Иначе клиенты не знают, как его собрать на своей стороне.
NetworkBehaviour — сетевой MonoBehaviour
Вместо MonoBehaviour для сетевых компонентов наследуйтесь от NetworkBehaviour. Это даёт:
IsServer,IsClient,IsOwner,IsHost— где сейчас выполняется код.OnNetworkSpawn()/OnNetworkDespawn()— аналоги Start/OnDestroy в сетевом контексте.- Доступ к
NetworkManager.Singleton.
NetworkVariable — синхронизированное поле
Хотите, чтобы здоровье врага было одинаковым у всех клиентов? Объявите его как NetworkVariable<T>:
public class NetEnemy : NetworkBehaviour
{
public NetworkVariable<int> Health = new NetworkVariable<int>(
100,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
public override void OnNetworkSpawn() {
Health.OnValueChanged += (oldVal, newVal) => {
Debug.Log($"Health: {oldVal} → {newVal}");
if (newVal <= 0) Die();
};
}
public void TakeDamage(int amount) {
if (!IsServer) return; // только сервер может писать
Health.Value = Mathf.Max(0, Health.Value - amount);
}
}
Параметры:
readPerm— кто может читать (Everyone,Owner).writePerm— кто может писать (ServerилиOwner).
NetworkVariable передаётся каждый “tick” (по умолчанию ~30 раз/с), если значение поменялось.
RPC — удалённые вызовы
NGO 2.x (поставляется с Unity 6) поддерживает унифицированный атрибут [Rpc] с параметром
SendTo. Это рекомендованный синтаксис для новых проектов:
using Unity.Netcode;
using UnityEngine;
public class Shooter : NetworkBehaviour
{
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private AudioClip shotClip;
// Клиент → сервер. Сервер создаёт пулю и рассылает звук.
[Rpc(SendTo.Server)]
public void FireRpc(Vector3 origin, Vector3 direction) {
var bullet = Instantiate(bulletPrefab, origin, Quaternion.LookRotation(direction));
bullet.GetComponent<NetworkObject>().Spawn();
PlayShotSoundRpc(origin);
}
// Сервер → все клиенты (включая host'а). Презентационный звук.
[Rpc(SendTo.ClientsAndHost)]
private void PlayShotSoundRpc(Vector3 position) {
AudioSource.PlayClipAtPoint(shotClip, position);
}
}
Значения SendTo:
SendTo.Server— на сервер (любой клиент может вызвать).SendTo.ClientsAndHost— на всех клиентов + host’а.SendTo.NotServer— на всех, кроме сервера.SendTo.Owner/SendTo.NotOwner— владельцу / всем кроме владельца.SendTo.Me,SendTo.Everyone,SendTo.SpecifiedInParams— гибкие варианты.
Старые атрибуты [ServerRpc] / [ClientRpc] (с обязательными суффиксами ServerRpc / ClientRpc
в имени метода) поддерживаются для совместимости с NGO 1.x, но в новом коде используйте [Rpc] —
он гибче и без жёсткого требования к имени метода. Также с NGO 2.9+
[ServerRpc(RequireOwnership=...)] deprecated в пользу параметра InvokePermission со значениями
RpcInvokePermission.Everyone / Authority.
// Старый стиль (всё ещё валиден):
[ServerRpc]
public void FireServerRpc(Vector3 origin) { /* ... */ }
[ClientRpc]
private void PlayShotSoundClientRpc(Vector3 position) { /* ... */ }
ClientRpc — для презентационных вещей (звуки, эффекты, всплывающие надписи). Игровое состояние всегда на сервере, синхронизируется через NetworkVariable. Иначе клиенты разойдутся.
NetworkTransform — синхронизация Transform
Простой случай — синхронизировать позицию и поворот объекта. Поставьте на GameObject компонент
NetworkTransform. Опции:
- Sync Position X/Y/Z — какие оси синхронизировать.
- Sync Rotation X/Y/Z — то же для поворота.
- Interpolation — сглаживать ли движение на клиенте.
- Threshold — насколько изменилась позиция, чтобы отправить (минимизирует трафик).
Для контролируемого игроком объекта в NGO 2.x используйте встроенное поле
NetworkTransform.AuthorityMode = Owner — это main path. По умолчанию AuthorityMode = Server
(только сервер пишет). ClientNetworkTransform из Multiplayer Samples
— legacy/sample из 1.x эпохи, в NGO 2.x избегайте.
Distributed Authority — альтернатива чистому client-server
NGO 2.x также поддерживает модель Distributed Authority (через Unity Cloud Multiplayer Services / Multiplayer Services Package). Идея: владение узлами распределено между клиентами, а не сосредоточено на сервере. Один клиент владеет своим игроком, другой — общим инвентарём, третий — боссом.
Это даёт:
- Меньшую задержку для действий локального игрока (не нужно ждать сервер).
- Дешевле в инфраструктуре (relay-сервер, не authoritative host).
- Сложнее в plot-проверке (anti-cheat) — нет одного источника правды.
Подходит для кооперативных и party-игр, не подходит для соревновательных competitive shooter’ов.
Активируется через NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority на
NetworkManager + подключение к Unity Cloud Multiplayer Services (Multiplayer Services Package).
RPC-синтаксис [Rpc] с SendTo остаётся тот же, но семантика “у кого authority” меняется.
Транспорт и соединение
UnityTransport — стандартный транспортный слой. Для прямого подключения по IP:
public class ConnectMenu : MonoBehaviour
{
public void HostGame() {
NetworkManager.Singleton.StartHost();
}
public void JoinGame(string ip) {
var transport = NetworkManager.Singleton.GetComponent<Unity.Netcode.Transports.UTP.UnityTransport>();
transport.SetConnectionData(ip, 7777);
NetworkManager.Singleton.StartClient();
}
}
Для реальной игры IP-адреса неудобны (NAT, фаерволы). Решение — Unity Relay + Lobby: Unity Services дают relay-сервер и матчмейкинг, клиенты соединяются через “join code” вроде “ABCD12”.
Что обычно идёт не так
- Логика в
Updateбез проверки IsServer/IsOwner. Спавн врагов, расчёт урона, движение AI — только на сервере. Иначе у каждого клиента свой “враг” и они не совпадают. - Передача больших объектов через RPC. Параметры RPC должны быть простыми (примитивы, простые
struct’ы с
INetworkSerializable). Большие данные шлются плохо. - Слишком частые NetworkVariable updates. Каждое поле, меняющееся 60 раз в секунду — это нагрузка. Группируйте, throttling, либо отправляйте через RPC по событию.
- Игнорирование лагов и предсказания. На 200 мс пинге без предсказания всё ощущается резиновым. Изучите Client-Side Prediction и Server Reconciliation (это уже advanced topic).
В следующей главе — Addressables: современная система загрузки ассетов.