Loading...
Loading...
Expert blueprint for multiplayer networking (Among Us, Brawlhalla, Terraria) using Godot's high-level API covering RPCs, state synchronization, authoritative servers, client prediction, and lobby systems. Use when building online multiplayer, LAN co-op, or networked games. Keywords multiplayer, RPC, ENetMultiplayerPeer, MultiplayerSynchronizer, authority, client prediction, rollback.
npx skill4agent add thedivergentai/gd-agentic-skills godot-multiplayer-networkingif not multiplayer.is_server(): return@rpc("any_peer")@rpc("authority")any_peer"reliable""unreliable"set_multiplayer_authority(peer_id)is_multiplayer_authority()peer.create_server()multiplayer.multiplayer_peer = peerpeer_connectedlerp()extends Node
var peer := ENetMultiplayerPeer.new()
func host_game(port: int = 7777) -> void:
peer.create_server(port, 4) # Max 4 players
multiplayer.multiplayer_peer = peer
print("Server started on port ", port)
func join_game(ip: String, port: int = 7777) -> void:
peer.create_client(ip, port)
multiplayer.multiplayer_peer = peer
print("Connecting to ", ip)func _ready() -> void:
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.connected_to_server.connect(_on_connected)
multiplayer.connection_failed.connect(_on_connection_failed)
func _on_peer_connected(id: int) -> void:
print("Player connected: ", id)
func _on_peer_disconnected(id: int) -> void:
print("Player disconnected: ", id)
func _on_connected() -> void:
print("Connected to server!")
func _on_connection_failed() -> void:
print("Connection failed")extends CharacterBody2D
# Called on all peers
@rpc("any_peer", "call_local")
func take_damage(amount: int) -> void:
health -= amount
if health <= 0:
die()
# Usage: Call on specific peer
take_damage.rpc_id(1, 50) # Call on server (ID 1)
take_damage.rpc(50) # Call on all peers# Only server can call, runs on all clients
@rpc("authority", "call_remote")
func server_spawn_enemy(pos: Vector2) -> void:
pass
# Any peer can call, runs locally too
@rpc("any_peer", "call_local")
func player_chat(message: String) -> void:
pass
# Reliable (TCP-like) vs Unreliable (UDP-like)
@rpc("any_peer", "call_local", "reliable")
func important_event() -> void:
pass
@rpc("any_peer", "call_local", "unreliable")
func position_update(pos: Vector2) -> void:
pass# Add MultiplayerSpawner node
# Set spawn path and scenes
extends Node
@onready var spawner := $MultiplayerSpawner
func _ready() -> void:
spawner.spawn_function = spawn_player
func spawn_player(data: Variant) -> Node:
var player := preload("res://player.tscn").instantiate()
player.name = str(data) # Use peer ID as name
return player# Add to synchronized node
# Set properties to sync
# Scene structure:
# Player (CharacterBody2D)
# ├─ MultiplayerSynchronizer
# │ └─ Replication config:
# │ - position (sync)
# │ - velocity (sync)
# │ - health (sync)
# └─ Sprite2D# lobby_manager.gd (AutoLoad)
extends Node
signal player_joined(id: int, info: Dictionary)
signal player_left(id: int)
var players: Dictionary = {}
func _ready() -> void:
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
func _on_peer_connected(id: int) -> void:
# Request player info
request_player_info.rpc_id(id)
func _on_peer_disconnected(id: int) -> void:
players.erase(id)
player_left.emit(id)
@rpc("any_peer", "reliable")
func request_player_info() -> void:
var sender_id := multiplayer.get_remote_sender_id()
receive_player_info.rpc_id(sender_id, {
"name": PlayerSettings.player_name,
"color": PlayerSettings.player_color
})
@rpc("any_peer", "reliable")
func receive_player_info(info: Dictionary) -> void:
var sender_id := multiplayer.get_remote_sender_id()
players[sender_id] = info
player_joined.emit(sender_id, info)extends CharacterBody2D
var puppet_position: Vector2
var puppet_velocity: Vector2
func _physics_process(delta: float) -> void:
if is_multiplayer_authority():
# Local player: process input
_handle_input(delta)
move_and_slide()
# Send position to others
sync_position.rpc(global_position, velocity)
else:
# Remote player: interpolate
global_position = global_position.lerp(puppet_position, 10.0 * delta)
@rpc("any_peer", "unreliable")
func sync_position(pos: Vector2, vel: Vector2) -> void:
puppet_position = pos
puppet_velocity = vel# Check who owns this node
func _ready() -> void:
# Set authority to owner peer
set_multiplayer_authority(peer_id)
func _process(delta: float) -> void:
if not is_multiplayer_authority():
return # Skip if not owner
# Only authority processes this@rpc("any_peer", "call_local")
func player_action(action: String) -> void:
if not multiplayer.is_server():
return # Only server validates
var sender := multiplayer.get_remote_sender_id()
if not _is_valid_action(sender, action):
return
_apply_action.rpc(sender, action)# Position: unreliable (frequent)
@rpc("any_peer", "unreliable")
func sync_position(pos: Vector2) -> void:
pass
# Damage: reliable (important)
@rpc("authority", "reliable")
func apply_damage(amount: int) -> void:
passvar target_position: Vector2
func _process(delta: float) -> void:
if not is_multiplayer_authority():
position = position.lerp(target_position, 15.0 * delta)