9.9 KiB
9.9 KiB
| name | description | license | compatibility | metadata | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| godot-networking | Networking and multiplayer for Godot 4.x: Multiplayer API with RPC system, authority models (server-authoritative vs client-predictive), replication patterns (rpc(), rset(), rset_group()), connection management, lag compensation, and Steam Frame integration. Use when building multiplayer games, networked simulations, or online features in Godot. | MIT |
|
|
Networking and Multiplayer for Godot 4.x
Comprehensive patterns for multiplayer, RPC communication, and network synchronization in Godot 4.x.
Multiplayer API (Godot 4.x RPC System)
Setting Up Multiplayer
# In _ready(), set the peer and peer ID
func _ready():
var peer = ENetMultiplayerPeer.new()
peer.create_client("127.0.0.1", 9000)
multiplayer.multiplayer_peer = peer
multiplayer.server_id = 1 # Server is always ID 1
RPC Methods
# Remote: Call only from non-owner
@rpc("reliable", "call_remote")
func rpc_move_player(position: Vector3):
global_position = position
# Reliable RPC (ordered, guaranteed delivery)
@rpc("reliable", "authority", "call_remote")
func rpc_take_damage(amount: int, source_id: int):
health -= amount
if health <= 0:
rpc("rpc_die", source_id)
# Unreliable RPC (faster, may drop packets — good for position updates)
@rpc("unreliable", "authority", "call_remote")
func rpc_update_position(position: Vector3, rotation: float):
global_position = position
rotation_degrees = rotation
RPC Reliability Options
| Mode | Use Case |
|---|---|
"reliable" |
Damage, state changes, game events |
"unreliable" |
Position updates, player movement |
"unreliable_ordered" |
Ordered but fast (chat messages) |
"call_local" |
Only on the node owner |
"call_remote" |
On all non-owner nodes |
"call_authority" |
Only on the authority node |
Authority Models
Server-Authoritative Model
The server has final say on all game state. Clients send inputs, server validates and broadcasts results.
class_name ServerAuthPlayer extends CharacterBody3D
@export var player_id: int = 0
var _server := false
func _ready():
if multiplayer.is_server():
_server = true
set_multiplayer_authority(1)
else:
# Client-owned players are predicted locally
set_multiplayer_authority(multiplayer.get_unique_id())
# Client sends input to server
func _input(event: InputEvent) -> void:
if not multiplayer.is_server():
rpc("rpc_request_move", event)
# Server receives and validates input
@rpc("authority")
func rpc_request_move(event: InputEvent):
# Validate the input (check timing, etc.)
_process_input(event)
Client-Predictive Model
Clients predict their own actions locally and correct on server acknowledgment.
class_name ClientPredictivePlayer extends CharacterBody3D
@export var prediction_enabled: bool = true
var _input_buffer := []
var _last_confirmed_input: int = 0
func _physics_process(delta: float):
# Apply buffered inputs up to current state
for input_data in _input_buffer:
if input_data.sequence > _last_confirmed_input:
_apply_input(input_data)
# Send position to server
rpc("rpc_update_position", global_position, rotation_degrees)
When to Use Which
| Model | Best For |
|---|---|
| Server-authoritative | Competitive games, anti-cheat priority |
| Client-predictive | Fast-paced action, smooth feel priority |
| Hybrid (recommended) | Most multiplayer games |
Replication Patterns
rpc() — Remote Procedure Call
# Call a method on all other nodes
rpc("rpc_on_enemy_hit", damage, target_id)
# With parameters
@rpc("reliable")
func rpc_spawn_pickup(pickup_type: String, position: Vector3):
var pickup = preload("res://scenes/pickups/" + pickup_type + ".tscn").instantiate()
pickup.global_position = position
get_tree().root.add_child(pickup)
rset() — Remote Set
Set a property on remote nodes.
# Set health bar on all clients
rset("health", new_health)
rset("max_health", max_health)
rset("visible", true)
# Bulk set with rset_group()
rset_group("players", "set_state", state_name)
rset_group() — Broadcast to Group
# Add nodes to a group for batch updates
func _ready():
multiplayer.add_to_group("players")
# Update all players at once
func broadcast_position():
rset_group("players", "sync_position", global_position, rotation_degrees)
# Server-only group
func _ready():
if multiplayer.is_server():
add_to_group("server_only")
Networked Autoload
Use autoloads for network-wide state.
# NetworkManager.gd (Autoload)
class_name NetworkManager extends Node
signal player_connected(player_id: int)
signal player_disconnected(player_id: int)
signal game_started()
var players := {}
func _ready():
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
func _on_peer_connected(id: int):
players[id] = PlayerInfo.new(id)
player_connected.emit(id)
func _on_peer_disconnected(id: int):
players.erase(id)
player_disconnected.emit(id)
Connection Management
Server Setup
class_name MultiplayerServer extends Node
const PORT = 9000
const MAX_PLAYERS = 16
var server: HTTPServer
func start_server():
var peer = ENetMultiplayerPeer.new()
peer.create_server(PORT, MAX_PLAYERS)
multiplayer.multiplayer_peer = peer
print("Server started on port ", PORT)
func stop_server():
if multiplayer.multiplayer_peer:
multiplayer.multiplayer_peer.disconnect_from_server()
Client Connection
class_name MultiplayerClient extends Node
signal connected
signal connection_failed
func connect_to_server(host: String, port: int):
var peer = ENetMultiplayerPeer.new()
var error = peer.create_client(host, port)
if error != OK:
connection_failed.emit()
return
multiplayer.multiplayer_peer = peer
# Wait for server to assign unique ID
await get_tree().create_timer(0.5).timeout
if multiplayer.is_connected_to_server():
connected.emit()
Lag Compensation
# Store player positions for rewind capability
class_name PositionHistory extends Node
@export var history_length: int = 3.0
var _history := []
func _physics_process(delta: float):
_history.push_back({
"position": global_position,
"rotation": rotation_degrees,
"time": Time.get_ticks_msec()
})
# Prune old entries
var cutoff = Time.get_ticks_msec() - int(history_length * 1000)
while _history.size() > 0 and _history[0].time < cutoff:
_history.pop_front()
func get_position_at(time_ms: int) -> Variant:
for entry in _history:
if entry.time >= time_ms:
return entry
return _history.back()
Steam Frame Integration (Godot 4.7+)
Using Steam Networking Sockets
# Requires Steamworks SDK and godot-steam plugin
func connect_steam_server(app_id: int, steam_id: String):
var peer = SteamMultiplayerPeer.new()
peer.create_client(steam_id)
multiplayer.multiplayer_peer = peer
Frame-Synced Updates
For competitive games requiring frame-accurate sync:
@export var tick_rate: int = 60
var _tick_counter: int = 0
func _physics_process(delta: float):
_tick_counter += 1
if _tick_counter % (int(60 / tick_rate)) == 0:
rpc("rpc_tick_update", _tick_counter, get_state_snapshot())
Steam Lobby Integration
func create_lobby(max_players: int = 4):
# Create lobby via Steamworks API
var lobby_id = Steam.matchmaking.create_lobby(
max_players,
Steam.LobbyType.LobbyPrivate
)
return lobby_id
func join_lobby(lobby_id: String):
Steam.matchmaking.join_lobby(lobby_id)
Best Practices
Do
- Use
@rpc("unreliable")for frequent position updates — saves bandwidth - Validate all client input on the server before applying
- Keep RPC payloads small — serialize only what changes
- Use
multiplayer.is_server()guards to prevent client-side authority bugs - Implement a heartbeat/ping system for connection quality monitoring
Don't
- Don't call
rpc()in_processwithout throttling — floods the network - Don't trust client-reported health/damage — always recalculate server-side
- Don't send full state snapshots every frame — use delta compression
- Don't forget
set_multiplayer_authority()when spawning remote nodes - Don't mix reliable and unreliable RPCs for the same logical update
Debugging Tips
# Enable network debugging in Project Settings:
network/enable_packet_logging = true
network/max_packet_size = 1400
# Print all RPC traffic:
multiplayer.peer_connected.connect(func(id): print("Peer connected: ", id))
multiplayer.peer_disconnected.connect(func(id): print("Peer disconnected: ", id))
MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools enhance networking debugging:
network_peer_list— List connected peers and their unique IDsrpc_trace— Log all RPC calls with sender/receiver info for debuggingnetwork_stats— View bandwidth usage, packet loss, and latency metricslobby_inspect— Query Steam lobby state and player list
Note: MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.