Retour aux skills
gamedev-skills/awesome-gamedev-agent-skillsContrôle réussi

SKILL DETAIL

godot-multiplayer

gamedev-skills/awesome-gamedev-agent-skills/godot-multiplayer

Build networked games with Godot 4.7 high-level multiplayer: set up an 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.

Installations · 237Voir la source

Installation

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer

Fichiers du skill

SKILL.md

Dernière synchronisation · 24 oct. 2026

references/replication-and-rpc.md›
# Replication & RPC reference (Godot 4.7)

Depth companion to `godot-multiplayer`.

## The @rpc annotation in full

```gdscript
@rpc(mode, sync, transfer_mode, transfer_channel)
```

Defaults — `@rpc` is equivalent to `@rpc("authority", "call_remote", "reliable", 0)`:

- **mode**: `"authority"` (only the node's multiplayer authority may call remotely) or
  `"any_peer"` (any client may call — needed for player input).
- **sync**: `"call_remote"` (does not run on the local/sending peer) or `"call_local"`
  (also runs locally — use when the host is a player).
- **transfer_mode**: `"reliable"` (acknowledged, ordered, slower), `"unreliable"`
  (fire-and-forget, may drop/reorder — good for frequent position updates), or
  `"unreliable_ordered"` (drops late packets to preserve order).
- **transfer_channel**: integer channel index; the first three args can be in any order
  but the channel must be last. Use separate channels to keep unrelated streams from
  blocking each other.

Call forms (on a `Callable` for the method):

```gdscript
my_method.rpc(args...)            # call on all peers (per mode/sync rules)
my_method.rpc_id(peer_id, args)   # call on one peer (1 = server)
multiplayer.get_remote_sender_id()  # inside the method: who invoked it
```

## MultiplayerSpawner

Auto-instantiates scenes on remote peers when the authority spawns them.

1. Add a `MultiplayerSpawner` node; set its **Spawn Path** to the parent that will hold
   spawned instances.
2. Add the scenes it may spawn to its **Auto Spawn List** (or use a custom spawn function
   via `spawn_function`).
3. On the authority, call `spawner.spawn(data)` or simply `add_child()` a scene under the
   spawn path — the spawner replicates it to clients with a matching name.

```gdscript
@onready var spawner: MultiplayerSpawner = $MultiplayerSpawner

func server_spawn_player(id: int) -> void:
    var p := preload("res://player.tscn").instantiate()
    p.name = str(id)                 # deterministic name = authority id
    $Players.add_child(p, true)      # true = readable, replicated name
```

## MultiplayerSynchronizer

Replicates chosen properties from the authority to other peers each network tick.

- Add it as a child of the node to sync. In the **Replication** dock, add property paths
  (e.g. `:position`, `:velocity`, `Sprite2D:modulate`).
- Per property: **Spawn** (sent once at spawn) and **Sync** (sent continuously) flags, plus
  a replication interval.
- `replication_interval` / `delta_interval` tune bandwidth.
- Visibility filtering for per-peer data:
  ```gdscript
  sync.set_visibility_for(peer_id, true)
  sync.add_visibility_filter(func(peer): return _can_see(peer))
  sync.update_visibility()
  ```

Only the authority's values are sent; remote peers receive read-only updates. Combine with
per-node authority so each player's synchronizer pushes that player's state.

## SceneMultiplayer authentication

Protect RPCs from unauthenticated peers using the built-in handshake:

```gdscript
# Server, after assigning the peer:
multiplayer.auth_timeout = 3.0
multiplayer.auth_callback = func(peer_id: int, data: PackedByteArray):
    var info := JSON.parse_string(data.get_string_from_utf8())
    if _valid(info):
        multiplayer.complete_auth(peer_id)

# Client:
multiplayer.peer_authenticating.connect(func(peer_id: int):
    var payload := JSON.stringify({"token": _token}).to_utf8_buffer()
    multiplayer.send_auth(1, payload)
)
```

`connected_to_server` / `peer_connected` only fire once both sides call `complete_auth()`.

## Transfer mode guidance

- **Player input / commands** → reliable (must not be lost).
- **Position/rotation updates** → unreliable or unreliable_ordered via a
  `MultiplayerSynchronizer` (frequent, latest-wins).
- **Chat / state changes** → reliable, possibly on a dedicated channel.

## Dedicated server export

Export a **headless** build (no GPU) for servers; see `godot-export`. Modify the lobby so
the server is not counted as a player and the first joining client can start the match. Run
with the server feature tag and `--headless`.

## Minimal lobby flow

1. Host: `create_server`; add self to `players` keyed by id 1.
2. On `peer_connected(id)`: send your player info to that id via `rpc_id`.
3. Each peer registers others (`@rpc("any_peer")` register function using
   `get_remote_sender_id()`).
4. On `peer_disconnected(id)`: erase from `players`, emit a UI signal.
5. Server starts the game by RPC'ing a `load_game(path)` (`@rpc("call_local","reliable")`)
   so all peers `change_scene_to_file` together.
SKILL.md›
---
name: godot-multiplayer
description: >
  Build networked games with Godot 4.7 high-level multiplayer: set up an
  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.
---

# Godot Multiplayer (4.x high-level)

Connect peers, call functions remotely with `@rpc`, assign authority, and replicate state
with `MultiplayerSpawner`/`MultiplayerSynchronizer`. Targets **Godot 4.7** (ENet). Treat
all client input as untrusted; keep the server authoritative.

## When to use

- Use when adding networked multiplayer: hosting/joining over ENet, calling RPCs,
  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`.

## Core workflow

1. **Create a peer** (`ENetMultiplayerPeer`), call `create_server(port, max)` or
   `create_client(ip, port)`, and assign it to `multiplayer.multiplayer_peer`. The
   server's unique ID is always `1`; clients get random positive IDs.
2. **Handle connection signals** on `multiplayer`: `peer_connected(id)`,
   `peer_disconnected(id)`, `connected_to_server`, `connection_failed`,
   `server_disconnected`.
3. **Define RPCs** with `@rpc(...)`. Call them on a `Callable` via `rpc()` (all peers) or
   `rpc_id(peer_id)` (one peer). Inside, `multiplayer.get_remote_sender_id()` tells you who
   sent it.
4. **Keep RPC signatures identical** on every peer that runs the script — Godot checksums
   all `@rpc` methods in a script; mismatches break silently.
5. **Assign authority** per node with `set_multiplayer_authority(id)`; gate input/RPCs by
   `is_multiplayer_authority()`.
6. **Replicate state** with `MultiplayerSpawner` (auto-instances scenes on clients) and
   `MultiplayerSynchronizer` (auto-syncs selected properties).
7. **Validate on the server.** Don't trust client-reported positions/results.

## Patterns

### 1. Host or join (ENet)

```gdscript
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()
```

### 2. RPCs: client sends input to the server (any_peer, call_local)

```gdscript
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)
```

### 3. Per-node authority (each player controls their own avatar)

```gdscript
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()
```

### 4. MultiplayerSynchronizer config (editor + replication)

```gdscript
# 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)
```

## Pitfalls

- **RPC signature checksum.** Every `@rpc` method in a script must exist with the same
  declaration 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.
- **Default `@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.
- **NodePaths must match across peers.** RPC routing uses the node's path/name; spawn nodes
  with identical names on all peers (use `MultiplayerSpawner` or `add_child(node, true)` for
  readable, deterministic names).
- **Trusting the client.** Never let clients set authoritative state (health, position,
  hits) directly. Send *intent*, validate on the server, then broadcast results.
- **RPC on non-Node classes fails.** `@rpc` methods must be on `Node`-derived classes, not
  plain `Resource`/`RefCounted`.
- **RPCs don't serialize Objects/Callables.** Pass plain data (ints, strings, arrays,
  dictionaries, PackedArrays).
- **Forgetting to reset the peer.** To disconnect cleanly, set
  `multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()`.
- **Android needs INTERNET permission** in the export preset or all networking is blocked.

## References

- For `MultiplayerSpawner` setup, transfer modes/channels, `SceneMultiplayer`
  authentication (`auth_callback`/`complete_auth`), a lobby skeleton, and dedicated-server
  export notes, read `references/replication-and-rpc.md`.

## Related skills

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