Quick answer: Confirm the object has a NetworkObject component and NetworkObject.Spawn() has been called on the server, verify you are calling the [ClientRpc] method from the server side (check NetworkManager.Singleton.IsServer), and move any RPC calls from Start() to OnNetworkSpawn() to avoid the pre-spawn timing issue.
You write a [ClientRpc] method, call it from the server, and nothing happens on the client. No error in the Console, no exception, just silence. Unity Netcode for GameObjects has several hard requirements that must all be satisfied simultaneously for an RPC to be delivered, and failing any one of them causes the call to be silently dropped. This guide works through every requirement in order so you can diagnose exactly which one your setup is missing.
The Symptom
A [ClientRpc] method body never executes on connected clients. Log statements inside the method confirm it runs on the server (or host) but never on the client. Alternatively, calling the method throws an exception on the calling side: NetworkBehaviour.ClientRpc called on client confirms the call direction is wrong. A subtler variant is an RPC that works for the host-client but not for remote clients, which usually indicates a spawning or ownership issue.
In Netcode for GameObjects (NGO) 1.x and 2.x, RPCs are transmitted as part of a NetworkMessage tied to a specific NetworkObject by its NetworkObjectId. If the client does not have that NetworkObject in its local scene graph, the message is received but cannot be dispatched — it is silently discarded.
What Causes This
Missing NetworkObject component. Every NetworkBehaviour must be on a GameObject that has a NetworkObject component. Without it, the networking system cannot assign a NetworkObjectId and RPCs cannot be routed. Add a NetworkObject component to the same GameObject (or any parent) as your NetworkBehaviour script.
NetworkObject not spawned. Even with the component present, the object must be registered with the NetworkManager by calling NetworkObject.Spawn() on the server before any RPC can be sent or received for it:
public class NetworkedEnemy : NetworkBehaviour
{
// Called server-side to create a networked enemy
public static void SpawnEnemy(GameObject prefab, Vector3 position)
{
if (!NetworkManager.Singleton.IsServer) return;
GameObject instance = Instantiate(prefab, position, Quaternion.identity);
NetworkObject netObj = instance.GetComponent<NetworkObject>();
// Must call Spawn() before any ClientRpc on this object
netObj.Spawn(); // server owns the object
}
[ClientRpc]
public void TriggerAlertClientRpc()
{
// This only works after Spawn() has been called
Debug.Log("Alert received on client");
}
}
Calling ClientRpc from a client. [ClientRpc] methods must be invoked on the server. If a client script calls a [ClientRpc] directly, NGO throws an exception in development builds and drops the call silently in release builds. The correct pattern is: client calls [ServerRpc] → server executes logic → server calls [ClientRpc]:
public class PlayerController : NetworkBehaviour
{
// Client calls this to request a sound effect
[ServerRpc]
public void RequestJumpSoundServerRpc()
{
// Now on the server — safe to broadcast to all clients
PlayJumpSoundClientRpc();
}
[ClientRpc]
private void PlayJumpSoundClientRpc()
{
// Runs on every client
audioSource.PlayOneShot(jumpClip);
}
void Update()
{
if (!IsOwner) return;
if (Input.GetKeyDown(KeyCode.Space))
{
RequestJumpSoundServerRpc(); // client calls ServerRpc — correct
// PlayJumpSoundClientRpc(); // would throw — never do this from client
}
}
}
Calling RPC in Start() before NetworkObject is spawned. Start() is a Unity lifecycle method that runs before the network spawning protocol completes. If you call an RPC in Start(), the NetworkObject may not yet have a valid NetworkObjectId on the client side, causing the message to be unroutable. Always use OnNetworkSpawn() for any logic that depends on the object being registered with the network:
public class NetworkedPickup : NetworkBehaviour
{
// Wrong — Start runs before network spawn is complete
void Start()
{
if (IsServer) InitializePickupClientRpc(); // may be dropped
}
// Correct — OnNetworkSpawn fires after the object is fully registered
public override void OnNetworkSpawn()
{
if (IsServer) InitializePickupClientRpc(); // guaranteed to be routable
}
[ClientRpc]
private void InitializePickupClientRpc()
{
SetupVisuals();
}
}
The Fix: Verifying Server Context
Before calling any [ClientRpc], guard the call with an explicit server check. This prevents accidental client-side calls and makes the intent clear to anyone reading the code:
public class GameRoundManager : NetworkBehaviour
{
public void EndRound(int winnerId)
{
// Always guard ClientRpc calls with an IsServer check
if (!NetworkManager.Singleton.IsServer)
{
Debug.LogWarning("EndRound called on non-server — ignoring");
return;
}
ShowRoundResultClientRpc(winnerId);
}
[ClientRpc]
private void ShowRoundResultClientRpc(int winnerId)
{
UI.ShowWinner(winnerId);
}
}
“Use
NetworkManager.Singleton.IsServerrather thanIsHostfor server-side guards. A host is both a server and a client, soIsHostis true on the host machine.IsServeris true on both dedicated servers and hosts, which is almost always what you mean when you write server-side logic.”
Ownership and ServerRpc Permissions
NetworkObject.Spawn() assigns server ownership by default. NetworkObject.SpawnWithOwnership(clientId) assigns ownership to a specific client. Ownership is critical for [ServerRpc] calls because the default RequireOwnership = true means only the owner can invoke the ServerRpc. A non-owner client calling an owner-required ServerRpc is silently rejected:
// ServerRpc with RequireOwnership = false allows any client to call it
[ServerRpc(RequireOwnership = false)]
public void RequestInteractionServerRpc(ulong requestingClientId)
{
// Any client can call this — validate manually if needed
if (IsValidInteraction(requestingClientId))
{
ConfirmInteractionClientRpc();
}
}
// SpawnWithOwnership example — give the player object to the connecting client
void SpawnPlayerForClient(ulong clientId)
{
GameObject playerObj = Instantiate(playerPrefab);
NetworkObject netObj = playerObj.GetComponent<NetworkObject>();
netObj.SpawnWithOwnership(clientId);
// Player can now call owner-required ServerRpcs on this object
}
Targeting Specific Clients with ClientRpcParams
By default, a [ClientRpc] broadcasts to all connected clients. To send to specific clients only, use ClientRpcParams:
[ClientRpc]
public void SendSecretMessageClientRpc(string message, ClientRpcParams rpcParams = default)
{
// 'message' is received only by the targeted clients
Debug.Log("Secret: " + message);
}
// Server-side call targeting a specific client
void NotifySpecificClient(ulong targetClientId, string secret)
{
ClientRpcParams rpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = new ulong[] { targetClientId }
}
};
SendSecretMessageClientRpc(secret, rpcParams);
}
Related Issues
If RPCs are received but arrive out of order or with stale data, check whether the data would be better served by a NetworkVariable<T>, which guarantees eventual consistency without requiring the caller to manage ordering. RPCs are fire-and-forget with no delivery guarantee by default in NGO — they can be dropped on an unreliable transport. For critical state changes (health, position, score), prefer NetworkVariable. RPCs are best suited for events that are acceptable to miss, like sound effect triggers or one-time particle effects. For crashes and unexpected disconnects in multiplayer games, attaching a crash reporter that captures the NetworkManager state, connected client count, and last RPC call context will save hours of debugging.
Multiplayer bugs that only reproduce with three or more players in a specific order are exactly the ones that need automated crash reporting — you will never reproduce them reliably in a local test session.