Godot 4 Multiplayer Networking Guide
You already have a single-player scene. A character moves, collisions work, and an interaction such as a door already plays when you press a key. This guide adds a host, a second instance, and one rule: a peer may ask for a change, and only the authority applies it. It is not a matchmaking setup, and it is not a prediction tutorial.
Godot’s high-level multiplayer types have been renamed across versions. The pattern below is the one in the Godot 4 high-level multiplayer docs: a MultiplayerPeer, remote calls marked with @rpc, and authority checked on the receiving side. Confirm class names, annotation arguments, and node names against the docs for the version you have open. A good starting page is the current “High-level multiplayer” tutorial in the official manual.
Split input from results
In single player, the same function reads the key and moves the body or opens the door. That has to split.
Keep a local function that reads the keyboard or the joypad. It should not change shared state when a multiplayer peer is active, except for purely cosmetic things that never matter to the other player, such as a menu highlight on your own machine.
Shared results live in a second function. The door’s open flag, the character’s position if the server owns movement, and anything that can be won or lost go there. The local input function’s new job is to call that result function through an RPC when it does not itself have authority.
If you skip this split, both windows will read whatever keyboard focus they have and also apply networked copies. The usual symptom is two characters sliding together, or a door that toggles twice and ends where it started.
Start a host and a client
Add a tiny control, even two buttons, that does not touch the gameplay scene. One creates a server peer. One creates a client peer aimed at 127.0.0.1 and a port you chose.
Godot’s docs show ENetMultiplayerPeer for this. You create the peer, call the create-server or create-client method documented for your version, and assign the peer to multiplayer.multiplayer_peer. The docs also list WebSocket and WebRTC peers. Use ENet for two processes on one computer. Switch transports later if you have a reason.
When the host is up, multiplayer.is_server() is true there and false on the client. Use that check. Also print multiplayer.get_unique_id() on both sides once, so you can see the ids your version actually assigns. The server id is documented. Read it there if you need to target the server, and do not trust a hard-coded number from an old tutorial if it disagrees with get_unique_id() on your host.
You do not need to rebuild the character for this test. The existing scene can stay. Networking is a peer plus a few functions on the nodes you already have.
Authority and one RPC
By default, the server is the authority for the scene tree once a peer is assigned. The MultiplayerAPI docs describe that. A node can also be given a different multiplayer authority with set_multiplayer_authority, so the owning peer is the one allowed to make authority-mode calls on that node. Confirm the method name and arguments in the Node docs. Giving a client authority over its avatar is not the same as trusting the position it sends. A practical split is:
- The client is allowed to send an intent: “I pressed interact” or “here is my input vector.”
- The server decides the result: the door toggles or it does not, the body moves at a legal speed or it does not.
Here is a door, assuming the server keeps authority over the door node. Annotation strings must match your version. The manual says a bare @rpc means authority, call remote, reliable. "any_peer" is the mode that lets a client invoke the request. "call_local" also runs the function on the caller, which is what you want when the server applies a result and should see it too.
@rpc("any_peer", "reliable")
func request_toggle_door() -> void:
if not multiplayer.is_server():
return
var sender := multiplayer.get_remote_sender_id()
if not _sender_is_close_enough(sender):
return
_set_open.rpc(not _open)
@rpc("authority", "call_local", "reliable")
func _set_open(is_open: bool) -> void:
_open = is_open
_play_door_visual(is_open)
The client calls request_toggle_door.rpc_id(server_id). The server ignores the call if it is somehow running on a client, checks distance using positions it already trusts, and then calls _set_open in a way that replicates. Clients do not pass is_open in the request. If they can pass the result, they can pass whatever result they want.
Both peers need the same RPC declarations on that script. Godot compares RPC configuration between peers. If you add @rpc on one export and forget it on the other, the session can fail in a way that looks like a dead connection. Keep one project and run two instances of it while you learn. Do not maintain two copies of the script.
_sender_is_close_enough is your code. Use the same distance test you already trust in single player. If you do not have one, a simple range check against the character that belongs to sender is enough. Unknown senders fail the check.
Test two instances on purpose
Run two processes. Godot’s editor docs describe running more than one instance from the editor. The menu name has changed before, so search the manual for multiple instances instead of memorizing a path. If you cannot find it, export a debug build and launch that as the client while the editor is the host.
Use this script, in order, and do not skip ahead:
- Host only. The door still works locally. The character still moves.
- Join from the second process. Both stay open. Nobody has to move yet.
- Move only the host. The client must not move the host’s character.
- Move only the client. If both characters move, you are still applying local input on every peer.
- Toggle the door from the client while the client’s character is far away. Nothing should change on either window.
- Walk the client into range and toggle again. The door should change once, on both windows.
- Close the client. The host should keep running. Toggle the door on the host again.
Only one operating-system window receives the keyboard. That is what you want. If you need to drive both, click a window before you press keys. Do not “fix” this by reading the same keys on both peers.
Localhost hides delay. After the seven steps pass, run the client on another computer on your home network, or enable whatever latency simulation your version documents. If you only ever test on 127.0.0.1, you will not see a door that opens late or an input that arrives twice.
Leave these for later
Property replication nodes, spawners, and interpolation exist in Godot 4. Confirm the current node names in the docs before you add them. They are the wrong first step if your RPC and authority checks are still fuzzy. A synchronizer that copies a position will cheerfully copy a cheat if the client is the authority for that position.
Public internet reach, NAT, and accounts are later too. A listen server on your PC can work on a LAN and fail for a friend elsewhere. Do not rent a relay for a door that still fails step 6.
When the seven-step test passes, stop. You have a single-player scene with a real authority boundary. Add the next interaction the same way: intent in, checked result out.