godot-networking
Skill8 starsChanged 5 months agoMITView on GitHub
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.
In this repository23 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- godot-47-migrationSkill
- godot-animationSkill
- godot-architectureSkill
- godot-audioSkill
- godot-brainstormingSkill
- godot-code-reviewSkill
- godot-csharp-patternsSkill
- godot-debuggingSkill
- godot-dialog-systemsSkill
- godot-gdscript-patternsSkill
- godot-headless-workflowSkill
- godot-i18nSkill
- godot-inputSkill
- godot-inventory-economySkill
- godot-performanceSkill
- godot-physicsSkill
- godot-project-setupSkill
- godot-save-systemsSkill
- godot-shaders-vfxSkill
- godot-state-managementSkill
- godot-uiSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 6 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 185
Use autoloads for network-wide state.
- Very specific: line 322
Use @rpc("unreliable") for frequent position updates — saves bandwidth
- Distinctive: line 324
Keep RPC payloads small — serialize only what changes
- Very specific: line 325
Use multiplayer.is_server() guards to prevent client-side authority bugs
- Very specific: line 329
Don't call rpc() in _process without throttling — floods the network
- Distinctive: line 330
Don't trust client-reported health/damage — always recalculate server-side
- Distinctive: line 331
Don't send full state snapshots every frame — use delta compression
- Very specific: line 332
Don't forget set_multiplayer_authority() when spawning remote nodes
- Distinctive: line 333
Don't mix reliable and unreliable RPCs for the same logical update
- Distinctive: line 347
When the MCP bridge is running (Phase 2), these tools enhance networking debugging:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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 1RPC 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 = rotationRPC 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 = peerFrame-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.
References
- Multiplayer API Documentation
- RPC Patterns Deep Dive
- Authority Models Guide
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

