Networking & Multiplayer
Description
This skill covers Godot 4's high-level multiplayer API, including ENet and WebSocket transports, RPCs, the MultiplayerSpawner, MultiplayerSynchronizer, and authority management for building networked games.
When To Use
- •Building online or LAN multiplayer games
- •Setting up client-server or peer-to-peer architecture
- •Synchronising player state, transforms, and game events
- •Implementing lobbies, matchmaking, and player management
- •Using RPCs for remote function calls
Prerequisites
- •Godot 4.3+ multiplayer nodes
- •Understanding of SceneTree and scene architecture
- •Player Controller skill (for synchronised movement)
Instructions
1. Network Architecture
Godot 4 uses MultiplayerAPI with pluggable transports:
- •ENetMultiplayerPeer — UDP, fast, best for real-time games
- •WebSocketMultiplayerPeer — WebSocket, works in browsers
- •WebRTCMultiplayerPeer — peer-to-peer without a relay server
Peer ID 1 is always the server (authority). Each client gets a unique ID.
2. Hosting and Joining
gdscript
# Autoload: NetworkManager
class_name NetworkManager
extends Node
signal player_connected(peer_id: int)
signal player_disconnected(peer_id: int)
signal connection_succeeded
signal connection_failed
const DEFAULT_PORT := 7000
const MAX_CLIENTS := 8
var players: Dictionary = {} # peer_id -> player data
func host_game(port: int = DEFAULT_PORT) -> Error:
var peer := ENetMultiplayerPeer.new()
var error := peer.create_server(port, MAX_CLIENTS)
if error != OK:
push_error("Failed to create server: %s" % error_string(error))
return error
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
players[1] = _local_player_data()
print("Server started on port %d" % port)
return OK
func join_game(address: String, port: int = DEFAULT_PORT) -> Error:
var peer := ENetMultiplayerPeer.new()
var error := peer.create_client(address, port)
if error != OK:
push_error("Failed to connect: %s" % error_string(error))
return error
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
multiplayer.server_disconnected.connect(_on_server_disconnected)
return OK
func _on_peer_connected(peer_id: int) -> void:
player_connected.emit(peer_id)
func _on_peer_disconnected(peer_id: int) -> void:
players.erase(peer_id)
player_disconnected.emit(peer_id)
func _on_connected_to_server() -> void:
connection_succeeded.emit()
func _on_connection_failed() -> void:
multiplayer.multiplayer_peer = null
connection_failed.emit()
func _on_server_disconnected() -> void:
multiplayer.multiplayer_peer = null
func _local_player_data() -> Dictionary:
return { "name": "Player" }
3. RPCs (Remote Procedure Calls)
gdscript
extends CharacterBody2D
# Server-authoritative movement
@rpc("any_peer", "call_local", "reliable")
func request_action(action: String) -> void:
if not multiplayer.is_server():
return
# Validate and process on server
_execute_action.rpc(action)
@rpc("authority", "call_local", "reliable")
func _execute_action(action: String) -> void:
# Runs on all peers
match action:
"jump":
velocity.y = -300.0
RPC annotations:
- •Authority:
"authority"(only authority can call) or"any_peer"(anyone can call) - •Call mode:
"call_local"(also runs locally) or"call_remote"(only on remotes) - •Transfer mode:
"reliable","unreliable", or"unreliable_ordered"
4. MultiplayerSpawner
Automatically replicates scene instantiation across peers:
- •Add a
MultiplayerSpawnernode to the scene. - •In the Inspector, add spawnable scenes to the
Auto Spawn List. - •Set the
Spawn Pathto the parent node where spawned scenes are added. - •When the server adds a child matching a registered scene, it's automatically spawned on all clients.
gdscript
# Server spawns a player — clients receive it automatically
func _spawn_player(peer_id: int) -> void:
if not multiplayer.is_server():
return
var player := preload("res://player/player.tscn").instantiate()
player.name = str(peer_id)
$Players.add_child(player, true) # spawner replicates this
5. MultiplayerSynchronizer
Automatically syncs properties across the network:
- •Add a
MultiplayerSynchronizeras a child of the node to sync. - •In the Inspector, add properties to the
Replication Config. - •Set each property's mode:
- •On Change: Sent when value changes (reliable)
- •Always: Sent every frame (unreliable, for transforms)
gdscript
extends CharacterBody2D
# These properties are synced via MultiplayerSynchronizer
@export var synced_position: Vector2
@export var synced_animation: String
func _physics_process(delta: float) -> void:
if is_multiplayer_authority():
# Owner processes input and sets values
_handle_input(delta)
synced_position = global_position
else:
# Non-owners interpolate to synced values
global_position = global_position.lerp(synced_position, 10.0 * delta)
6. Authority Management
gdscript
func _spawn_player(peer_id: int) -> void:
var player := preload("res://player/player.tscn").instantiate()
player.name = str(peer_id)
# Set the owning peer as the multiplayer authority
player.set_multiplayer_authority(peer_id)
$Players.add_child(player, true)
The authority peer controls the node's gameplay logic. Use is_multiplayer_authority() to gate input processing:
gdscript
func _physics_process(delta: float) -> void:
if not is_multiplayer_authority():
return
# Only process input for the local player
Best Practices
- •Use server authority for game-critical state (health, scores, inventory).
- •Use
unreliabletransfer for high-frequency data (position, rotation). - •Use
reliabletransfer for important events (damage, item pickup). - •Interpolate remote player positions to smooth out network jitter.
- •Validate all client inputs on the server — never trust the client.
- •Use
MultiplayerSpawnerandMultiplayerSynchronizerinstead of writing custom sync code.
Common Pitfalls
- •Not setting multiplayer authority. Without
set_multiplayer_authority(), all nodes default to server authority and clients can't control their characters. - •Calling RPCs before connection. Ensure the peer is connected before sending RPCs.
- •Syncing too much data. Only sync what's needed — position, rotation, key state. Derive everything else locally.
- •Not handling disconnections. Always clean up player nodes and state when a peer disconnects.
- •Using
call_localeverywhere. Only usecall_localwhen the function should also run on the caller.