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