Multiplayer — Netcode for GameObjects
NetworkObject, NetworkVariable, ServerRpc/ClientRpc — the basics of a networked game.
Multiplayer is a separate universe of complexity, but Unity Netcode for GameObjects (NGO) greatly simplifies getting started. It’s Unity’s official package for synchronizing GameObjects between the server and clients in a client-server architecture.
A WebSocket server with a custom protocol + your state management. Every field that needs to be visible to everyone you synchronize by hand: emit → on → update Redux/Zustand.
NetworkObject automatically synchronizes the Transform and NetworkVariable between the server and clients. RPCs are method calls over the network, like a remote emit. The transport is abstracted away (Unity Transport, Relay, Steam, etc.).
NGO architecture
NGO works in authoritative server mode (the server is the source of truth). The role options:
- Server — a dedicated server with no local player. For competitive games and dedicated servers.
- Host — a client that is also the server at the same time. Convenient for P2P and co-op.
- Client — a connected player.
NetworkManager is the main singleton that starts the connection: StartHost(), StartServer(),
StartClient(). There’s one per scene.
NetworkObject — the main component
For a GameObject to live on the network it needs a NetworkObject. This gives:
- A unique network ID, identical on the server and the clients.
- A binding to an owner (usually the client that “controls” this object).
- Spawning —
NetworkObject.Spawn()on the server creates the object on all clients too.
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();
}
}
A prefab you spawn over the network must be registered in the NetworkPrefabsList on the NetworkManager. Otherwise the clients don’t know how to assemble it on their side.
NetworkBehaviour — the networked MonoBehaviour
Instead of MonoBehaviour, derive networked components from NetworkBehaviour. This gives:
IsServer,IsClient,IsOwner,IsHost— where the code is currently running.OnNetworkSpawn()/OnNetworkDespawn()— the network-context equivalents of Start/OnDestroy.- Access to
NetworkManager.Singleton.
NetworkVariable — a synchronized field
Want the enemy’s health to be identical on all clients? Declare it as a 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; // only the server can write
Health.Value = Mathf.Max(0, Health.Value - amount);
}
}
Parameters:
readPerm— who can read (Everyone,Owner).writePerm— who can write (ServerorOwner).
A NetworkVariable is transmitted every “tick” (by default ~30 times/s) if the value has changed.
RPC — remote calls
NGO 2.x (shipped with Unity 6) supports a unified [Rpc] attribute with a SendTo
parameter. This is the recommended syntax for new projects:
using Unity.Netcode;
using UnityEngine;
public class Shooter : NetworkBehaviour
{
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private AudioClip shotClip;
// Client → server. The server creates the bullet and broadcasts the sound.
[Rpc(SendTo.Server)]
public void FireRpc(Vector3 origin, Vector3 direction) {
var bullet = Instantiate(bulletPrefab, origin, Quaternion.LookRotation(direction));
bullet.GetComponent<NetworkObject>().Spawn();
PlayShotSoundRpc(origin);
}
// Server → all clients (including the host). Presentational sound.
[Rpc(SendTo.ClientsAndHost)]
private void PlayShotSoundRpc(Vector3 position) {
AudioSource.PlayClipAtPoint(shotClip, position);
}
}
The SendTo values:
SendTo.Server— to the server (any client can call it).SendTo.ClientsAndHost— to all clients + the host.SendTo.NotServer— to everyone except the server.SendTo.Owner/SendTo.NotOwner— to the owner / to everyone except the owner.SendTo.Me,SendTo.Everyone,SendTo.SpecifiedInParams— flexible options.
The old [ServerRpc] / [ClientRpc] attributes (with the mandatory ServerRpc / ClientRpc
suffixes in the method name) are supported for compatibility with NGO 1.x, but in new code use [Rpc] —
it’s more flexible and has no strict naming requirement. Also, as of NGO 2.9+
[ServerRpc(RequireOwnership=...)] is deprecated in favor of the InvokePermission parameter with the
RpcInvokePermission.Everyone / Authority values.
// The old style (still valid):
[ServerRpc]
public void FireServerRpc(Vector3 origin) { /* ... */ }
[ClientRpc]
private void PlayShotSoundClientRpc(Vector3 position) { /* ... */ }
ClientRpc is for presentational things (sounds, effects, floating labels). Game state is always on the server and synchronized via NetworkVariable. Otherwise the clients will diverge.
NetworkTransform — synchronizing the Transform
The simple case is to synchronize an object’s position and rotation. Add the
NetworkTransform component to a GameObject. The options:
- Sync Position X/Y/Z — which axes to synchronize.
- Sync Rotation X/Y/Z — the same for rotation.
- Interpolation — whether to smooth the movement on the client.
- Threshold — how much the position has to change to send an update (minimizes traffic).
For a player-controlled object in NGO 2.x use the built-in field
NetworkTransform.AuthorityMode = Owner — that’s the main path. By default AuthorityMode = Server
(only the server writes). ClientNetworkTransform from the Multiplayer Samples
is a legacy/sample from the 1.x era; avoid it in NGO 2.x.
Distributed Authority — an alternative to pure client-server
NGO 2.x also supports the Distributed Authority model (via Unity Cloud Multiplayer Services / the Multiplayer Services Package). The idea: ownership of nodes is distributed among the clients rather than concentrated on the server. One client owns its player, another the shared inventory, a third the boss.
This gives:
- Lower latency for the local player’s actions (no need to wait for the server).
- Cheaper infrastructure (a relay server, not an authoritative host).
- Harder to validate (anti-cheat) — there’s no single source of truth.
It’s suited for co-op and party games, and not for competitive shooters.
It’s activated via NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority on the
NetworkManager + connecting to Unity Cloud Multiplayer Services (the Multiplayer Services Package).
The [Rpc] syntax with SendTo stays the same, but the semantics of “who has authority” change.
Transport and connection
UnityTransport is the standard transport layer. For a direct connection by 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();
}
}
For a real game, IP addresses are inconvenient (NAT, firewalls). The solution is Unity Relay + Lobby: Unity Services provide a relay server and matchmaking, and clients connect via a “join code” like “ABCD12”.
What usually goes wrong
- Logic in
Updatewithout an IsServer/IsOwner check. Spawning enemies, calculating damage, AI movement — server only. Otherwise each client has its own “enemy” and they don’t match. - Passing large objects through RPCs. RPC parameters should be simple (primitives, simple
structs with
INetworkSerializable). Large data transmits poorly. - Too-frequent NetworkVariable updates. Every field that changes 60 times per second is a load. Group them, throttle, or send them via an RPC on an event.
- Ignoring lag and prediction. At 200 ms ping, without prediction everything feels rubbery. Study Client-Side Prediction and Server Reconciliation (that’s already an advanced topic).
In the next chapter — Addressables: the modern asset-loading system.