Godot Multiplayer (4.x high-level)
Connect peers, call functions remotely with @rpc, assign authority, and replicate state
with MultiplayerSpawner/MultiplayerSynchronizer. Targets Godot 4.7 (ENet). Treat
all client input as untrusted; keep the server authoritative.
When to use
- Use when adding networked multiplayer: hosting/joining over ENet, calling RPCs,
assigning per-node authority, or auto-spawning/syncing nodes across peers.
When not to use: local split-screen (no networking); raw TCP/UDP/WebSocket protocol
work (low-level PacketPeer); HTTP requests. For save/persistence → save-systems.
Core workflow
- Create a peer (
ENetMultiplayerPeer), call create_server(port, max) or
create_client(ip, port), and assign it to multiplayer.multiplayer_peer. The
server's unique ID is always 1; clients get random positive IDs.
- Handle connection signals on
multiplayer: peer_connected(id),
peer_disconnected(id), connected_to_server, connection_failed,
server_disconnected.
- Define RPCs with
@rpc(...). Call them on a Callable via rpc() (all peers) or
rpc_id(peer_id) (one peer). Inside, multiplayer.get_remote_sender_id() tells you who
sent it.
- Keep RPC signatures identical on every peer that runs the script — Godot checksums
all
@rpc methods in a script; mismatches break silently.
- Assign authority per node with
set_multiplayer_authority(id); gate input/RPCs by
is_multiplayer_authority().
- Replicate state with
MultiplayerSpawner (auto-instances scenes on clients) and
MultiplayerSynchronizer (auto-syncs selected properties).
- Validate on the server. Don't trust client-reported positions/results.
Patterns
1. Host or join (ENet)
const PORT := 7000
const MAX_PLAYERS := 8
func host() -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(PORT, MAX_PLAYERS)
if err != OK:
push_error("Cannot host: %s" % err); return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
func join(ip := "127.0.0.1") -> void:
var peer := ENetMultiplayerPeer.new()
peer.create_client(ip, PORT)
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(func(): print("connected"))
func leave() -> void:
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
2. RPCs: client sends input to the server (any_peer, call_local)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("fire") and is_multiplayer_authority():
request_fire.rpc_id(1) # send only to the server (id 1)
# Clients may call this; it runs on the server (and locally if server is a player).
@rpc("any_peer", "call_local", "reliable")
func request_fire() -> void:
var sender := multiplayer.get_remote_sender_id()
if not _can_fire(sender): # server-side validation
return
spawn_projectile.rpc(sender) # tell everyone to spawn it
@rpc("authority", "call_local", "reliable")
func spawn_projectile(owner_id: int) -> void:
_do_spawn(owner_id)
3. Per-node authority (each player controls their own avatar)
extends CharacterBody2D
func _ready() -> void:
# The node name is the owning peer's id; that peer is the authority.
set_multiplayer_authority(name.to_int())
func _physics_process(delta: float) -> void:
if not is_multiplayer_authority():
return # only the owner reads input & moves
velocity = Input.get_vector("left", "right", "up", "down") * 200.0
move_and_slide()
4. MultiplayerSynchronizer config (editor + replication)
# Add a MultiplayerSynchronizer child; in its Replication editor add the properties to
# sync (e.g. position, velocity). Set "Sync"/"Spawn" flags per property. From code you
# can scope visibility:
@onready var sync: MultiplayerSynchronizer = $MultiplayerSynchronizer
func _ready() -> void:
# Only replicate this node to a specific peer (e.g. private info).
sync.set_visibility_for(target_peer_id, true)
Pitfalls
- RPC signature checksum. Every
@rpc method in a script must exist with the same
declaration on both client and server builds — even unused ones. A mismatch causes
errors that may point at the wrong function. Argument names/count are not checked, but
the set of RPCs and their annotations are.
- Default
@rpc is "authority". Clients calling it are ignored unless you set
"any_peer". Use "call_local" so the host (also a player) runs it too.
- NodePaths must match across peers. RPC routing uses the node's path/name; spawn nodes
with identical names on all peers (use
MultiplayerSpawner or add_child(node, true) for
readable, deterministic names).
- Trusting the client. Never let clients set authoritative state (health, position,
hits) directly. Send intent, validate on the server, then broadcast results.
- RPC on non-Node classes fails.
@rpc methods must be on Node-derived classes, not
plain Resource/RefCounted.
- RPCs don't serialize Objects/Callables. Pass plain data (ints, strings, arrays,
dictionaries, PackedArrays).
- Forgetting to reset the peer. To disconnect cleanly, set
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new().
- Android needs INTERNET permission in the export preset or all networking is blocked.
References
- For
MultiplayerSpawner setup, transfer modes/channels, SceneMultiplayer
authentication (auth_callback/complete_auth), a lobby skeleton, and dedicated-server
export notes, read references/replication-and-rpc.md.
Related skills
godot-nodes-scenes — instancing the scenes that get spawned/synced.
godot-signals-groups — connection signals and event flow.
godot-export — exporting a headless dedicated server build.
1---2name: godot-multiplayer3description: Build networked games with Godot 4.7 high-level multiplayer: set up an ENetMultiplayerPeer server/client, define RPCs with the @rpc annotation (call via rpc()/rpc_id()), set per-node multiplayer authority, and replicate state with MultiplayerSpawner and MultiplayerSynchronizer. Use when adding multiplayer/networking to a Godot project, writing @rpc functions, or syncing player/world state across peers.4---5
6# Godot Multiplayer (4.x high-level)
7
8Connect peers, call functions remotely with `@rpc`, assign authority, and replicate state
9with `MultiplayerSpawner`/`MultiplayerSynchronizer`. Targets **Godot 4.7** (ENet). Treat
10all client input as untrusted; keep the server authoritative.
11
12## When to use
13
14- Use when adding networked multiplayer: hosting/joining over ENet, calling RPCs,
15 assigning per-node authority, or auto-spawning/syncing nodes across peers.
16
17**When *not* to use:** local split-screen (no networking); raw TCP/UDP/WebSocket protocol
18work (low-level `PacketPeer`); HTTP requests. For save/persistence → `save-systems`.
19
20## Core workflow
21
221. **Create a peer** (`ENetMultiplayerPeer`), call `create_server(port, max)` or
23 `create_client(ip, port)`, and assign it to `multiplayer.multiplayer_peer`. The
24 server's unique ID is always `1`; clients get random positive IDs.
252. **Handle connection signals** on `multiplayer`: `peer_connected(id)`,
26 `peer_disconnected(id)`, `connected_to_server`, `connection_failed`,
27 `server_disconnected`.
283. **Define RPCs** with `@rpc(...)`. Call them on a `Callable` via `rpc()` (all peers) or
29 `rpc_id(peer_id)` (one peer). Inside, `multiplayer.get_remote_sender_id()` tells you who
30 sent it.
314. **Keep RPC signatures identical** on every peer that runs the script — Godot checksums
32 all `@rpc` methods in a script; mismatches break silently.
335. **Assign authority** per node with `set_multiplayer_authority(id)`; gate input/RPCs by
34 `is_multiplayer_authority()`.
356. **Replicate state** with `MultiplayerSpawner` (auto-instances scenes on clients) and
36 `MultiplayerSynchronizer` (auto-syncs selected properties).
377. **Validate on the server.** Don't trust client-reported positions/results.
38
39## Patterns
40
41### 1. Host or join (ENet)
42
43```gdscript
44const PORT := 7000
45const MAX_PLAYERS := 8
46
47func host() -> void:
48 var peer := ENetMultiplayerPeer.new()
49 var err := peer.create_server(PORT, MAX_PLAYERS)
50 if err != OK:
51 push_error("Cannot host: %s" % err); return
52 multiplayer.multiplayer_peer = peer
53 multiplayer.peer_connected.connect(_on_peer_connected)
54
55func join(ip := "127.0.0.1") -> void:
56 var peer := ENetMultiplayerPeer.new()
57 peer.create_client(ip, PORT)
58 multiplayer.multiplayer_peer = peer
59 multiplayer.connected_to_server.connect(func(): print("connected"))
60
61func leave() -> void:
62 multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
63```
64
65### 2. RPCs: client sends input to the server (any_peer, call_local)
66
67```gdscript
68func _unhandled_input(event: InputEvent) -> void:
69 if event.is_action_pressed("fire") and is_multiplayer_authority():
70 request_fire.rpc_id(1) # send only to the server (id 1)
71
72# Clients may call this; it runs on the server (and locally if server is a player).
73@rpc("any_peer", "call_local", "reliable")
74func request_fire() -> void:
75 var sender := multiplayer.get_remote_sender_id()
76 if not _can_fire(sender): # server-side validation
77 return
78 spawn_projectile.rpc(sender) # tell everyone to spawn it
79
80@rpc("authority", "call_local", "reliable")
81func spawn_projectile(owner_id: int) -> void:
82 _do_spawn(owner_id)
83```
84
85### 3. Per-node authority (each player controls their own avatar)
86
87```gdscript
88extends CharacterBody2D
89
90func _ready() -> void:
91 # The node name is the owning peer's id; that peer is the authority.
92 set_multiplayer_authority(name.to_int())
93
94func _physics_process(delta: float) -> void:
95 if not is_multiplayer_authority():
96 return # only the owner reads input & moves
97 velocity = Input.get_vector("left", "right", "up", "down") * 200.0
98 move_and_slide()
99```
100
101### 4. MultiplayerSynchronizer config (editor + replication)
102
103```gdscript
104# Add a MultiplayerSynchronizer child; in its Replication editor add the properties to
105# sync (e.g. position, velocity). Set "Sync"/"Spawn" flags per property. From code you
106# can scope visibility:
107@onready var sync: MultiplayerSynchronizer = $MultiplayerSynchronizer
108
109func _ready() -> void:
110 # Only replicate this node to a specific peer (e.g. private info).
111 sync.set_visibility_for(target_peer_id, true)
112```
113
114## Pitfalls
115
116- **RPC signature checksum.** Every `@rpc` method in a script must exist with the same
117 declaration on both client and server builds — *even unused ones*. A mismatch causes
118 errors that may point at the wrong function. Argument names/count are not checked, but
119 the set of RPCs and their annotations are.
120- **Default `@rpc` is `"authority"`.** Clients calling it are ignored unless you set
121 `"any_peer"`. Use `"call_local"` so the host (also a player) runs it too.
122- **NodePaths must match across peers.** RPC routing uses the node's path/name; spawn nodes
123 with identical names on all peers (use `MultiplayerSpawner` or `add_child(node, true)` for
124 readable, deterministic names).
125- **Trusting the client.** Never let clients set authoritative state (health, position,
126 hits) directly. Send *intent*, validate on the server, then broadcast results.
127- **RPC on non-Node classes fails.** `@rpc` methods must be on `Node`-derived classes, not
128 plain `Resource`/`RefCounted`.
129- **RPCs don't serialize Objects/Callables.** Pass plain data (ints, strings, arrays,
130 dictionaries, PackedArrays).
131- **Forgetting to reset the peer.** To disconnect cleanly, set
132 `multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()`.
133- **Android needs INTERNET permission** in the export preset or all networking is blocked.
134
135## References
136
137- For `MultiplayerSpawner` setup, transfer modes/channels, `SceneMultiplayer`
138 authentication (`auth_callback`/`complete_auth`), a lobby skeleton, and dedicated-server
139 export notes, read `references/replication-and-rpc.md`.
140
141## Related skills
142
143- `godot-nodes-scenes` — instancing the scenes that get spawned/synced.
144- `godot-signals-groups` — connection signals and event flow.
145- `godot-export` — exporting a headless dedicated server build.