gamedev-skills/godot-multiplayer
> ENetMultiplayerPeer server/client, define RPCs with the @rpc annotation (call via rpc()/rpc_id()), set per-node multiplayer authority, and replicate state with MultiplayerSpawner and MultiplayerSynchronizer. Use when adding multiplayer/networking to a Godot project, writing @rpc functions, or syncing player/world state across peers.
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer
Connect peers, call functions remotely with @rpc, assign authority, and replicate state
with MultiplayerSpawner/MultiplayerSynchronizer. Targets Godot 4.3+ (ENet). Treat
all client input as untrusted; keep the server authoritative.
assigning per-node authority, or auto-spawning/syncing nodes across peers.
When *not* to use: local split-screen (no networking); raw TCP/UDP/WebSocket protocol
work (low-level PacketPeer); HTTP requests. For save/persistence → save-systems.
ENetMultiplayerPeer), call create_server(port, max) orcreate_client(ip, port), and assign it to multiplayer.multiplayer_peer. The
server's unique ID is always 1; clients get random positive IDs.
multiplayer: peer_connected(id),peer_disconnected(id), connected_to_server, connection_failed,
server_disconnected.
@rpc(...). Call them on a Callable via rpc() (all peers) orrpc_id(peer_id) (one peer). Inside, multiplayer.get_remote_sender_id() tells you who
sent it.
all @rpc methods in a script; mismatches break silently.
set_multiplayer_authority(id); gate input/RPCs byis_multiplayer_authority().
MultiplayerSpawner (auto-instances scenes on clients) andMultiplayerSynchronizer (auto-syncs selected properties).
const PORT := 7000
const MAX_PLAYERS := 8
func host() -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(PORT, MAX_PLAYERS)
if err != OK:
push_error("Cannot host: %s" % err); return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
func join(ip := "127.0.0.1") -> void:
var peer := ENetMultiplayerPeer.new()
peer.create_client(ip, PORT)
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(func(): print("connected"))
func leave() -> void:
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("fire") and is_multiplayer_authority():
request_fire.rpc_id(1) # send only to the server (id 1)
# Clients may call this; it runs on the server (and locally if server is a player).
@rpc("any_peer", "call_local", "reliable")
func request_fire() -> void:
var sender := multiplayer.get_remote_sender_id()
if not _can_fire(sender): # server-side validation
return
spawn_projectile.rpc(sender) # tell everyone to spawn it
@rpc("authority", "call_local", "reliable")
func spawn_projectile(owner_id: int) -> void:
_do_spawn(owner_id)
extends CharacterBody2D
func _ready() -> void:
# The node name is the owning peer's id; that peer is the authority.
set_multiplayer_authority(name.to_int())
func _physics_process(delta: float) -> void:
if not is_multiplayer_authority():
return # only the owner reads input & moves
velocity = Input.get_vector("left", "right", "up", "down") * 200.0
move_and_slide()
# Add a MultiplayerSynchronizer child; in its Replication editor add the properties to
# sync (e.g. position, velocity). Set "Sync"/"Spawn" flags per property. From code you
# can scope visibility:
@onready var sync: MultiplayerSynchronizer = $MultiplayerSynchronizer
func _ready() -> void:
# Only replicate this node to a specific peer (e.g. private info).
sync.set_visibility_for(target_peer_id, true)
@rpc method in a script must exist with the samedeclaration on both client and server builds — *even unused ones*. A mismatch causes
errors that may point at the wrong function. Argument names/count are not checked, but
the set of RPCs and their annotations are.
@rpc is "authority". Clients calling it are ignored unless you set"any_peer". Use "call_local" so the host (also a player) runs it too.
with identical names on all peers (use MultiplayerSpawner or add_child(node, true) for
readable, deterministic names).
hits) directly. Send *intent*, validate on the server, then broadcast results.
@rpc methods must be on Node-derived classes, notplain Resource/RefCounted.
dictionaries, PackedArrays).
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new().
MultiplayerSpawner setup, transfer modes/channels, SceneMultiplayerauthentication (auth_callback/complete_auth), a lobby skeleton, and dedicated-server
export notes, read references/replication-and-rpc.md.
godot-nodes-scenes — instancing the scenes that get spawned/synced.godot-signals-groups — connection signals and event flow.godot-export — exporting a headless dedicated server build.Take gamedev-skills/godot-multiplayer from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.