Multiplayer & Networking
Godot's high-level multiplayer is built on MultiplayerAPI (multiplayer property of every node, by default SceneMultiplayer) and a MultiplayerPeer transport:
33.1 Architecture
Godot's high-level multiplayer is built on MultiplayerAPI (multiplayer property of every node, by default SceneMultiplayer) and a MultiplayerPeer transport:
UDP (desktop/mobile) WebSockets (works in browsers) Default single-player stub Third-party (GDExtension) e.g., Steam networking
Each peer has a unique ID; the server is always ID 1. Every node has a multiplayer authority (default: server) that owns its state.
33.2 Hosting and joining
# network.gd (autoload)
extends Node
const PORT := 7000
const MAX_CLIENTS := 8
signal player_joined(id: int)
signal player_left(id: int)
func host() -> Error:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(PORT, MAX_CLIENTS)
if err != OK:
return err
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(func(id): player_joined.emit(id))
multiplayer.peer_disconnected.connect(func(id): player_left.emit(id))
player_joined.emit(1) # the host is a player too
return OK
func join(address: String) -> Error:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_client(address, PORT)
if err != OK:
return err
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(func(): print("Connected as ", multiplayer.get_unique_id()))
multiplayer.connection_failed.connect(func(): print("Connection failed"))
multiplayer.server_disconnected.connect(func(): get_tree().change_scene_to_file("res://menu.tscn"))
return OK
func leave() -> void:
multiplayer.multiplayer_peer.close()
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()33.3 RPCs
@rpc argument Options (default first) "authority" (only the authority may call), "any_peer"
"call_remote", "call_local" (also run on the caller) "unreliable", "unreliable_ordered", "reliable" integer (e.g., 1) to separate traffic
RPC functions must exist with the same node path and the same @rpc configuration on all peers.
@rpc("any_peer", "call_local", "reliable")
func send_chat(text: String) -> void:
var sender := multiplayer.get_remote_sender_id() # 0 when called locally
$Chat.add_text("%d: %s\n" % [sender, text])
@rpc("authority", "call_remote", "unreliable_ordered")
func update_position(pos: Vector2) -> void:
position = pos
@rpc("any_peer", "reliable")
func request_damage(target_path: NodePath, amount: int) -> void:
if not multiplayer.is_server(): return
# validate! never trust clients blindly
var target := get_node_or_null(target_path)
if target and amount <= 20:
target.apply_damage(amount)
func _on_send_pressed() -> void:
send_chat.rpc($LineEdit.text) # call on all peers (and locally, due to call_local)
request_damage.rpc_id(1, enemy.get_path(), 10) # call only on the server33.4 Authority
# player.gd — the player node is named after its peer ID
func _enter_tree() -> void:
set_multiplayer_authority(name.to_int())
func _physics_process(delta: float) -> void:
if not is_multiplayer_authority():
return # only the owning peer reads input
velocity = Input.get_vector("left", "right", "up", "down") * 200.0
move_and_slide()33.5 MultiplayerSpawner and MultiplayerSynchronizer
MultiplayerSpawner: replicates node creation. Set spawn_path (the parent node) and add scenes to its *Auto Spawn List*. When the authority (server) adds a child of those scenes under spawn_path, clients get it too. For custom data use spawn_function + spawn(data).
MultiplayerSynchronizer: replicates properties (position, health, animation) from the authority to others. Configure properties in the *Replication* bottom panel (*Spawn*, *Sync*/*Replicate always or on change*), set replication_interval, and control visibility per peer.
# world.gd (server side)
const PlayerScene := preload("res://player.tscn")
func _ready() -> void:
if multiplayer.is_server():
Network.player_joined.connect(add_player)
Network.player_left.connect(remove_player)
func add_player(id: int) -> void:
var p := PlayerScene.instantiate()
p.name = str(id)
$Players.add_child(p, true) # MultiplayerSpawner watching $Players replicates it
func remove_player(id: int) -> void:
var p := $Players.get_node_or_null(str(id))
if p: p.queue_free()33.6 Practical notes
Test locally: *Debug → Customize Run Instances* (4.3+) to launch several instances; host on one, join 127.0.0.1 on others.
Use the server as the source of truth; clients send input, the server simulates, results are synchronized. Add client-side prediction and interpolation for fast-paced games.
Dedicated servers: export with the "Dedicated Server" option and run with --headless.
The Network Profiler in the debugger shows RPC and sync bandwidth.
Other networking: HTTPRequest/HTTPClient for REST APIs, StreamPeerTCP, PacketPeerUDP, WebSocketPeer.
Godot 3 note: remote, master, puppet, remotesync keywords and rpc("func_name") became @rpc(...) and func_name.rpc(); is_network_master() became is_multiplayer_authority(); NetworkedMultiplayerENet became ENetMultiplayerPeer.
func fetch_leaderboard() -> void:
var http := HTTPRequest.new()
add_child(http)
http.request("https://example.com/api/scores")
var result: Array = await http.request_completed # [result, response_code, headers, body]
if result[1] == 200:
var data = JSON.parse_string(result[3].get_string_from_utf8())
print(data)
http.queue_free()