๐ก Lesson 6.4: RPCs โ Remote Procedure Calls
NetworkVariables carry state; RPCs carry actions. A Remote Procedure Call runs a method on other machines โ a client asking the server "I want to fire", or the server telling every client "play this explosion." In Unity 6, Netcode uses a single universal [Rpc] attribute, and this lesson teaches it precisely โ because it's the piece older tutorials get wrong.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Declare an RPC with the universal
[Rpc(SendTo.โฆ)]attribute and theRpcnaming rule - Send client โ server requests and server โ client broadcasts
- Choose the right
SendTotarget - Pass parameters and target specific clients with
RpcParams - Validate requests on the server to preserve authority
- Recognise the legacy
[ServerRpc]/[ClientRpc]attributes and why not to use them
Estimated Time: 60 minutes ยท Prerequisite: Lessons 6.1โ6.3
In This Lesson
What an RPC Is
A Remote Procedure Call is a method you call on your machine that actually executes on other machines in the session. You call it like a normal method; Netcode serialises the call and its arguments, sends them across the network, and invokes the method on the target machines. It's the mechanism for the two directions NetworkVariables don't cover: a client sending a request up to the server, and the server broadcasting a momentary event down to clients.
RPCs are for events โ "this happened just now." Unlike NetworkVariables, they aren't retained: a client who joins later won't receive RPCs sent before they connected. That's the litmus test from Lesson 6.3 โ if a late joiner needs it, use a NetworkVariable; if it's a fleeting action, use an RPC.
The Universal [Rpc] Attribute
In current Netcode (Unity 6), you declare an RPC by marking a method with [Rpc(SendTo.โฆ)] and giving it a name ending in Rpc. The SendTo value sets the default destination:
using Unity.Netcode;
// Runs on the SERVER when any client (or the server) calls it.
[Rpc(SendTo.Server)]
void SubmitScoreRpc(int points) { /* ... executes on the server ... */ }
// Runs on ALL clients (and the host) when the server calls it.
[Rpc(SendTo.ClientsAndHost)]
void AnnounceWinnerRpc(FixedString32Bytes name) { /* ... executes on clients ... */ }
โ ๏ธ Don't use [ServerRpc] / [ClientRpc] โ they're legacy
Older tutorials declare [ServerRpc] and [ClientRpc] (with ...ServerRpc/...ClientRpc suffixes). Those are the previous API and are superseded by the single universal [Rpc(SendTo.x)] attribute. Use the universal one โ it's clearer, more flexible (client-to-client too), and the current standard. If a guide tells you to write [ServerRpc], it's out of date.
The Rpc suffix is mandatory โ the source generator uses it to identify RPC methods, and you'll get a compile error without it. Think of it as part of the syntax, not a style choice.
SendTo Targets
SendTo names where the RPC runs. The common values:
| SendTo | Runs onโฆ | Use for |
|---|---|---|
SendTo.Server | the server only | a client's request to the server |
SendTo.ClientsAndHost | every client, including the host's client | a broadcast event everyone should see |
SendTo.NotServer | all clients except a dedicated server | visual events (server has no view) |
SendTo.Everyone | server and all clients | logic + visuals everywhere |
SendTo.Owner | the owning client only | a private message to one player |
SendTo.SpecifiedInParams | whoever you pass at call time | dynamic targeting |
For a dedicated server with no display, prefer NotServer for pure visual/audio effects so the headless server doesn't waste time on them; use ClientsAndHost when you're developing with a host and want the host's own client to see the effect too.
Client โ Server Requests
This is the upward direction โ a client asks the server to do something. The owning client calls a SendTo.Server RPC; the method body runs on the server, where authority lives:
public class PlayerActions : NetworkBehaviour
{
void Update()
{
if (!IsOwner) return; // only my avatar reads my input
if (Input.GetKeyDown(KeyCode.Space))
RequestFireRpc(); // call โ runs on the server
}
[Rpc(SendTo.Server)]
void RequestFireRpc()
{
// This body executes on the SERVER.
// ... validate (cooldown, ammo), spawn the projectile (server-authoritative) ...
SpawnTracerRpc(); // then tell everyone to show it
}
}
Server โ Client Broadcasts
The downward direction โ the server tells clients about a momentary event they should present. The server calls a SendTo.ClientsAndHost (or NotServer/Everyone) RPC:
[Rpc(SendTo.ClientsAndHost)]
void SpawnTracerRpc()
{
// This body executes on EVERY client (and the host).
// Purely presentational: play the muzzle flash, tracer, and sound locally.
// The actual projectile/damage is server-authoritative state, not here.
}
Keep broadcast RPCs presentational where possible โ effects, sounds, animations. The consequences (a projectile that deals damage, a score change) belong in server-authoritative state and NetworkVariables, so a dropped RPC never desyncs the actual game, only a cosmetic flourish.
Parameters & Targeting
RPCs take parameters, which must be network-serialisable (the same types NetworkVariables allow โ primitives, Unity structs, FixedString, INetworkSerializable). For dynamic targeting, add a trailing RpcParams parameter and use SendTo.SpecifiedInParams:
// Send to exactly one client, chosen at call time.
[Rpc(SendTo.SpecifiedInParams)]
void PrivateNoticeRpc(FixedString128Bytes message, RpcParams rpcParams = default)
{
// runs only on the targeted client
ShowToast(message.ToString());
}
// caller (on the server):
PrivateNoticeRpc("Your turn!", RpcTarget.Single(targetClientId, RpcTargetUse.Temp));
You can also read who sent an RPC on the receiving side via the RpcParams receive info โ useful on the server to know which client made a request. But keep parameters lean: every RPC is a network message, and big or frequent ones cost bandwidth.
Validate on the Server
This is where authority (Lesson 6.1) becomes code. A SendTo.Server RPC is a request from a machine you don't trust โ so the server must validate it before acting:
[Rpc(SendTo.Server)]
void RequestPickupRpc(ulong coinId)
{
// Executes on the server โ check the rules before granting anything.
if (!TryGetCoin(coinId, out var coin)) return; // exists?
if (Vector3.Distance(transform.position, coin.Position) > 2f) return; // in range?
if (coin.Collected) return; // not already taken?
coin.Collect(); // authoritative state change
Score.Value += 10; // NetworkVariable โ replicates to all
}
Never assume a request is legitimate just because it arrived. A cheat-client can call RequestPickupRpc with any coin id from anywhere โ the distance and existence checks are what keep the game fair. An RPC from a client is an ask, not a command; the server decides. This single habit is the difference between a robust multiplayer game and an exploitable one.
Hands-on Challenge
๐๏ธ Exercise 1: Request-and-broadcast
Objective: Wire both RPC directions.
- On your player, add a
[Rpc(SendTo.Server)] RequestPingRpc()the owner calls on a key press. - In its body (running on the server), call a
[Rpc(SendTo.ClientsAndHost)] ShowPingRpc(Vector3 pos)passing the player's position. - In
ShowPingRpc(running on every client), spawn a local ping marker atpos. - Run host + client. Press the key on the client โ confirm the ping appears in both windows.
๐ก Hint
If the ping only appears on one machine, check the suffixes end in Rpc and the attributes are the universal [Rpc(SendTo.โฆ)] (not [ServerRpc]). If the client's own ping is missing, you used NotServer from a host โ try ClientsAndHost so the host's client sees it too.
๐๏ธ Exercise 2: Close the exploit
A teammate's [Rpc(SendTo.Server)] AddScoreRpc(int points) just does Score.Value += points;. Explain the exploit and rewrite it safely.
โ Answer
A cheat-client can call AddScoreRpc(1000000) and the server blindly adds it โ the client is dictating score. Fix: don't let the client send the amount at all. The server should compute the score from validated events (e.g. RequestPickupRpc(coinId) that checks range/existence, then adds a fixed 10). Rule: never let a client-supplied number directly drive authoritative state.
๐ฏ Quick Quiz
Question 1: In Unity 6 Netcode, how do you declare an RPC that runs on the server when a client calls it?
Question 2: Why validate a SendTo.Server RPC's request?
Question 3: Should the damage from a shot live in a broadcast RPC or in server state?
Summary
๐ Key Takeaways
- An RPC runs a method on other machines โ for momentary events, not retained state (late joiners miss them).
- Use the universal
[Rpc(SendTo.โฆ)]attribute with anRpc-suffixed name;[ServerRpc]/[ClientRpc]are legacy. SendTo.Serverfor client โ server requests;ClientsAndHost/NotServer/Everyonefor server โ client broadcasts.- Parameters must be network-serialisable; target specific clients with
RpcParams+SendTo.SpecifiedInParams. - Validate every client request on the server โ an RPC from a client is an ask, not a command.
- Keep broadcasts presentational; put consequences in server-authoritative state so a dropped RPC never desyncs gameplay.
๐ What's Next?
You now have the whole toolkit โ sessions, spawning, ownership, NetworkVariables, and RPCs. In Lesson 6.5 you'll assemble them into the module's mini-project: a small authoritative multiplayer prototype where players move, act, and share synced state, host-and-client, end to end.
๐ก Actions across the wire
Request up with SendTo.Server, broadcast down with SendTo.ClientsAndHost, and validate everything in between. RPCs plus NetworkVariables are the full vocabulary of Netcode gameplay.