multiplayer-basics
Basics of Godot multiplayer. Implement client-server networking, RPCs, and authority management.
Install
mkdir -p .claude/skills/multiplayer-basics && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19513" && unzip -o skill.zip -d .claude/skills/multiplayer-basics && rm skill.zipInstalls to .claude/skills/multiplayer-basics
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use when implementing multiplayer — MultiplayerAPI, ENet/WebSocket peers, RPCs, and authority modelKey capabilities
- →Establish client-server connections using ENet
- →Manage node authority in multiplayer sessions
- →Route RPCs between network peers
- →Synchronize game state across clients
- →Handle peer connection and disconnection signals
How it works
The system uses a client-server model where the server (peer 1) holds authority over nodes. Developers use MultiplayerAPI to assign authority, route RPCs, and manage peer connectivity via ENet.
Inputs & outputs
When to use multiplayer-basics
- →Setup ENet server and client connections
- →Manage node authority in a multiplayer session
- →Implement server-side validation
- →Route RPCs between peers
About this skill
Multiplayer Basics in Godot 4.3+
All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, C# follows.
Related skills: See multiplayer-sync for state synchronization and interpolation. See dedicated-server for headless export and server deployment.
1. Multiplayer Architecture
Godot uses a client-server model built on top of MultiplayerAPI. One peer acts as the server; all others are clients. Every peer has a unique integer ID assigned by the network layer:
| Peer ID | Role |
|---|---|
1 | The server (always) |
2, 3, … | Connected clients |
Multiplayer authority is the concept of ownership over a node. Only the authoritative peer should read input and drive that node's state. By default the server (peer 1) is the authority for every node. Call set_multiplayer_authority(peer_id) to transfer ownership to a client.
Server (peer 1)
├── Owns game state by default
├── Spawns and validates objects
└── Routes RPCs
Client (peer 2, 3, …)
├── Sends input to server via RPC
└── Receives state updates from server
2. Setting Up ENetMultiplayerPeer
GDScript
# network_manager.gd — add as autoload named NetworkManager
extends Node
const DEFAULT_PORT := 7777
const MAX_CLIENTS := 16
var peer: ENetMultiplayerPeer
func host_game(port: int = DEFAULT_PORT) -> void:
peer = ENetMultiplayerPeer.new()
var err := peer.create_server(port, MAX_CLIENTS)
if err != OK:
push_error("NetworkManager: create_server failed — error %d" % err)
return
multiplayer.multiplayer_peer = peer
_connect_signals()
print("NetworkManager: hosting on port %d" % port)
func join_game(address: String, port: int = DEFAULT_PORT) -> void:
peer = ENetMultiplayerPeer.new()
var err := peer.create_client(address, port)
if err != OK:
push_error("NetworkManager: create_client failed — error %d" % err)
return
multiplayer.multiplayer_peer = peer
_connect_signals()
print("NetworkManager: connecting to %s:%d" % [address, port])
func disconnect_from_game() -> void:
if peer:
peer.close()
multiplayer.multiplayer_peer = null
func _connect_signals() -> void:
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
func _on_peer_connected(id: int) -> void:
print("NetworkManager: peer connected — id %d" % id)
func _on_peer_disconnected(id: int) -> void:
print("NetworkManager: peer disconnected — id %d" % id)
func _on_connected_to_server() -> void:
print("NetworkManager: connected to server — my id is %d" % multiplayer.get_unique_id())
func _on_connection_failed() -> void:
push_error("NetworkManager: connection failed")
Key signal summary:
| Signal | Fires on | When |
|---|---|---|
peer_connected | Server + clients | A new peer finishes connecting |
peer_disconnected | Server + clients | A peer disconnects or times out |
connected_to_server | Client only | This client successfully connected |
connection_failed | Client only | This client could not connect |
C#
// NetworkManager.cs — add as autoload named NetworkManager
using Godot;
public partial class NetworkManager : Node
{
private const int DefaultPort = 7777;
private const int MaxClients = 16;
private ENetMultiplayerPeer _peer;
public void HostGame(int port = DefaultPort)
{
_peer = new ENetMultiplayerPeer();
var err = _peer.CreateServer(port, MaxClients);
if (err != Error.Ok)
{
GD.PushError($"NetworkManager: CreateServer failed — error {err}");
return;
}
Multiplayer.MultiplayerPeer = _peer;
ConnectSignals();
GD.Print($"NetworkManager: hosting on port {port}");
}
public void JoinGame(string address, int port = DefaultPort)
{
_peer = new ENetMultiplayerPeer();
var err = _peer.CreateClient(address, port);
if (err != Error.Ok)
{
GD.PushError($"NetworkManager: CreateClient failed — error {err}");
return;
}
Multiplayer.MultiplayerPeer = _peer;
ConnectSignals();
GD.Print($"NetworkManager: connecting to {address}:{port}");
}
public void DisconnectFromGame()
{
_peer?.Close();
Multiplayer.MultiplayerPeer = null;
}
private void ConnectSignals()
{
Multiplayer.PeerConnected += OnPeerConnected;
Multiplayer.PeerDisconnected += OnPeerDisconnected;
Multiplayer.ConnectedToServer += OnConnectedToServer;
Multiplayer.ConnectionFailed += OnConnectionFailed;
}
private void OnPeerConnected(long id)
=> GD.Print($"NetworkManager: peer connected — id {id}");
private void OnPeerDisconnected(long id)
=> GD.Print($"NetworkManager: peer disconnected — id {id}");
private void OnConnectedToServer()
=> GD.Print($"NetworkManager: connected — my id is {Multiplayer.GetUniqueId()}");
private void OnConnectionFailed()
=> GD.PushError("NetworkManager: connection failed");
}
3. RPCs
@rpc (GDScript) / [Rpc] (C#) marks a method as callable across the network. Choose the mode and transfer settings carefully — they affect both security and performance.
RPC Modes
| Mode | Who may call it | Executes on |
|---|---|---|
"authority" (default) | Only the authority peer | The peer(s) it is sent to |
"any_peer" | Any connected peer | The peer(s) it is sent to |
Transfer Modes
| Mode | Delivery | Order | Use For |
|---|---|---|---|
"reliable" | Guaranteed | In-order | Chat, spawn events, important state |
"unreliable" | Best-effort | Unordered | High-frequency position updates |
"unreliable_ordered" | Best-effort | In-order per channel | Smooth movement streams |
GDScript
# chat.gd
extends Node
# Any peer can call; server validates then broadcasts to all peers.
@rpc("any_peer", "reliable")
func send_chat_message(text: String) -> void:
if not multiplayer.is_server():
return
var sender_id := multiplayer.get_remote_sender_id()
_broadcast_chat.rpc(sender_id, text)
# Only the authority (server) can call this; runs on every peer.
@rpc("authority", "reliable", "call_local")
func _broadcast_chat(sender_id: int, text: String) -> void:
print("[%d]: %s" % [sender_id, text])
# Client → server: request to spawn an object.
@rpc("any_peer", "reliable")
func request_spawn(scene_path: String, spawn_position: Vector2) -> void:
if not multiplayer.is_server():
return
# Server validates and performs the actual spawn.
var scene: PackedScene = load(scene_path)
if scene == null:
return
var instance := scene.instantiate()
instance.global_position = spawn_position
get_tree().root.add_child(instance)
# High-frequency sync; unreliable_ordered + a channel keeps this off other RPC traffic.
@rpc("authority", "unreliable_ordered", "call_local", 1)
func sync_position(pos: Vector2) -> void:
global_position = pos
Sending to specific peers:
# Send to everyone (including self if call_local is set):
send_chat_message.rpc("Hello!")
# Send to one specific peer:
send_chat_message.rpc_id(target_peer_id, "Hello!")
C#
// Chat.cs
using Godot;
public partial class Chat : Node
{
// Any peer can call; executes on the server only.
[Rpc(MultiplayerApi.RpcMode.AnyPeer, TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
public void SendChatMessage(string text)
{
if (!Multiplayer.IsServer()) return;
int senderId = Multiplayer.GetRemoteSenderId();
Rpc(MethodName.BroadcastChat, senderId, text);
}
// Authority only; runs on every peer including the caller.
[Rpc(MultiplayerApi.RpcMode.Authority,
CallLocal = true,
TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
private void BroadcastChat(int senderId, string text)
=> GD.Print($"[{senderId}]: {text}");
// Client → server: request a spawn.
[Rpc(MultiplayerApi.RpcMode.AnyPeer, TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
public void RequestSpawn(string scenePath, Vector2 spawnPosition)
{
if (!Multiplayer.IsServer()) return;
var scene = GD.Load<PackedScene>(scenePath);
if (scene == null) return;
var instance = scene.Instantiate<Node2D>();
instance.GlobalPosition = spawnPosition;
GetTree().Root.AddChild(instance);
}
// High-frequency position sync.
[Rpc(MultiplayerApi.RpcMode.Authority,
CallLocal = true,
TransferMode = MultiplayerPeer.TransferModeEnum.UnreliableOrdered,
TransferChannel = 1)]
public void SyncPosition(Vector2 pos)
=> GlobalPosition = pos;
}
Sending to specific peers in C#:
// Broadcast to all:
Rpc(MethodName.SendChatMessage, "Hello!");
// Send to one peer:
RpcId(targetPeerId, MethodName.SendChatMessage, "Hello!");
4. Authority Model
Every node has exactly one authoritative peer — the peer that is permitted to send state updates for that node. Other peers should treat incoming state as read-only.
GDScript
# player.gd
extends CharacterBody2D
func _ready() -> void:
# multiplayer.get_unique_id() = this peer's ID; server assigns authority during spawn (see Section 6).
pass
func _physics_process(delta: float) -> void:
# Guard: authority-only input and movement.
if not is_multiplayer_authority():
return
var direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = direction * 200.0
move_and_slide()
sync_position.rpc(global_position)
@rpc("authority", "unreliable_ordered", "call_local", 1)
func sync_position(pos: Ve
---
*Content truncated.*
When not to use it
- →Single-player game development
- →Applications requiring peer-to-peer mesh networking without a server
Prerequisites
Limitations
- →Server must be authoritative for all nodes by default
- →RPCs must be guarded by authority checks to prevent unauthorized input
How it compares
This approach centralizes game state management on the server rather than relying on distributed client-side logic.
Compared to similar skills
multiplayer-basics side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| multiplayer-basics (this skill) | 0 | 14d | No flags | Intermediate |
| telegram-bot-builder | 106 | 6mo | Review | Intermediate |
| workflow-orchestration-patterns | 10 | 2mo | No flags | Advanced |
| bullmq-specialist | 25 | 6mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
telegram-bot-builder
davila7
Expert in building Telegram bots that solve real problems - from simple automation to complex AI-powered bots. Covers bot architecture, the Telegram Bot API, user experience, monetization strategies, and scaling bots to thousands of users. Use when: telegram bot, bot api, telegram automation, chat bot telegram, tg bot.
workflow-orchestration-patterns
wshobson
Design durable workflows with Temporal for distributed systems. Covers workflow vs activity separation, saga patterns, state management, and determinism constraints. Use when building long-running processes, distributed transactions, or microservice orchestration.
bullmq-specialist
davila7
BullMQ expert for Redis-backed job queues, background processing, and reliable async execution in Node.js/TypeScript applications. Use when: bullmq, bull queue, redis queue, background job, job queue.
unity-mcp-orchestrator
CoplayDev
Orchestrate Unity Editor via MCP (Model Context Protocol) tools and resources. Use when working with Unity projects through MCP for Unity - creating/modifying GameObjects, editing scripts, managing scenes, running tests, or any Unity Editor automation. Provides best practices, tool schemas, and workflow patterns for effective Unity-MCP integration.
async-python-patterns
wshobson
Master Python asyncio, concurrent programming, and async/await patterns for high-performance applications. Use when building async APIs, concurrent systems, or I/O-bound applications requiring non-blocking operations.
modal
davila7
Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.