MU

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.zip

Installs 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 model
99 chars✓ has a “when” trigger
Intermediate

Key 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

You give it
Network configuration and node authority settings
You get back
Multiplayer-ready game nodes with synchronized state

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 IDRole
1The 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:

SignalFires onWhen
peer_connectedServer + clientsA new peer finishes connecting
peer_disconnectedServer + clientsA peer disconnects or times out
connected_to_serverClient onlyThis client successfully connected
connection_failedClient onlyThis 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

ModeWho may call itExecutes on
"authority" (default)Only the authority peerThe peer(s) it is sent to
"any_peer"Any connected peerThe peer(s) it is sent to

Transfer Modes

ModeDeliveryOrderUse For
"reliable"GuaranteedIn-orderChat, spawn events, important state
"unreliable"Best-effortUnorderedHigh-frequency position updates
"unreliable_ordered"Best-effortIn-order per channelSmooth 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

Godot 4.3 or higher

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.

SkillInstallsUpdatedSafetyDifficulty
multiplayer-basics (this skill)014dNo flagsIntermediate
telegram-bot-builder1066moReviewIntermediate
workflow-orchestration-patterns102moNo flagsAdvanced
bullmq-specialist256moNo flagsIntermediate

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.

106130

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.

10117

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.

2595

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.

1795

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.

1299

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.

587

Search skills

Search the agent skills registry