gamedev-skills/awesome-gamedev-agent-skills검사 통과
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.
설치 수 · 237출처 보기
Installation
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer
스킬 파일
SKILL.md
최근 동기화 · 2026. 10. 24.
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.