ai-behavior-trees-util…
Engine-neutral behavior tree and utility AI code for NPCs in any language: Blackboard, reactive composites, decorators and…
Godot 4.7 high-level multiplayer: ENet host and join, @rpc with rpc_id(), MultiplayerSpawner and MultiplayerSynchronizer, with a two-instance headless test. Use for online or LAN co-op in Godot, or a rejected RPC, spawned nodes missing on clients, or state that does not sync.
Claude Code
$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer --agent claude-code
Codex
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer -a codex -y
Cursor
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer -a cursor -y
Gemini CLI
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer -a gemini-cli -y
GitHub Copilot
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer -a github-copilot -y
OpenCode
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer -a opencode -y
Windsurf
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-multiplayer -a windsurf -y
name: godot-multiplayer description: > Godot 4.7 high-level multiplayer: ENet host and join, @rpc with rpc_id(), MultiplayerSpawner and MultiplayerSynchronizer, with a two-instance headless test. Use for online or LAN co-op in Godot, or a rejected RPC, spawned nodes missing on clients, or state that does not sync. Not for Roblox (`roblox-networking`), server exports (`godot-export`) or local split-screen (`camera-systems`).
Connect peers, call functions remotely with `@rpc`, assign authority, and replicate state with `MultiplayerSpawner`/`MultiplayerSynchronizer`. Targets **Godot 4.7** with ENet (see Version notes). Treat all client input as untrusted; keep the server authoritative.
auto-spawning/syncing nodes across peers.
**When *not* to use:** local split-screen (no networking); raw TCP/UDP/WebSocket protocol work (`PacketPeer`); HTTP requests; Roblox → `roblox-networking`; persistence → `save-systems`.
1. **Create a peer** (`ENetMultiplayerPeer`), call `create_server(port, max_clients)` or `create_client(address, port)`, check the returned `Error`, and assign it to `multiplayer.multiplayer_peer`. The server's 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`. Print each one; the smoke test (step 8) reads those lines. 3. **Define RPCs** with `@rpc(...)` and call them on the method's `Callable` with `rpc()` (all peers) or `rpc_id(peer_id)` (one peer). Inside, `multiplayer.get_remote_sender_id()` says who sent it. 4. **Keep the RPC set identical** on every build that runs the script: Godot checksums all `@rpc` methods of a script, and a mismatch prints errors that can name an unrelated RPC. 5. **Assign authority** per node with `set_multiplayer_authority(id)`; gate input and movement on `is_multiplayer_authority()`. 6. **Replicate state** with `MultiplayerSpawner` (instances scenes on clients) and `MultiplayerSynchronizer` (syncs chosen properties). Both can be configured in code (Pattern 4), so no Replication-dock clicks are needed. 7. **Validate on the server.** Clients send intent; the server checks it and broadcasts results. 8. **Check with two headless instances before you finish.** Make the game host or join from command-line user args (Pattern 5), then:
timeout 20 godot --headless --path . -- --host > server.log 2>&1 & sleep 2 timeout 15 godot --headless --path . -- --join 127.0.0.1 > client.log 2>&1 wait grep -nE "^ *(SCRIPT ERROR|ERROR):" server.log client.log # no output = pass grep -n "connected" server.log client.log # your step-2 prints
Pass means both logs show the handshake prints and no error lines (RPC "not allowed", checksum and "Node not found" errors all print as `ERROR:`). With no Godot binary (`godot`, `godot4` or `$GODOT_BIN`), ask the user to run these lines and paste both logs. Gameplay feel still needs a human: ask the user to start two or more windows (editor menu Debug > Customize Run Instances...) and report what desyncs.
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" % error_string(err)); return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(func(id: int): print("peer_connected ", id))
func join(address := "127.0.0.1") -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_client(address, PORT)
if err != OK:
push_error("Cannot join: %s" % error_string(err)); return
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(func(): print("connected_to_server"))
func leave() -> void:
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()This script sits on a player avatar whose authority is the owning client (Pattern 3), so the server is *not* the node's authority. A server→clients RPC on such a node must be `"any_peer"` and check that the sender is the server.
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:
if not multiplayer.is_server(): # ignore copies relayed to other clients
return
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
# "authority" here would make clients reject the server's call (the client is the
# authority), so accept any peer and keep only calls that come from the server.
@rpc("any_peer", "call_local", "reliable")
func spawn_projectile(owner_id: int) -> void:
if multiplayer.get_remote_sender_id() != 1:
return
_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_an<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.
Engine-neutral behavior tree and utility AI code for NPCs in any language: Blackboard, reactive composites, decorators and…
Engine-neutral game audio technique: bus layout, volume sliders in decibels, loudness, ducking music under dialogue, adaptive…
Builds and tunes game cameras, engine-neutral with Godot and Cinemachine 3 mappings: 2D follow with deadzone and look-ahead,…
Plans, generates or sources, and validates consistent game art: style guides, sprites, pixel art, tilesets, icons and textures,…
Builds engine-neutral branching dialogue with Ink, Yarn Spinner or a small JSON graph runner: lines, choices, conditions,…
Designs engine-neutral enemy and NPC AI: picks FSM, behavior tree, utility or GOAP, and builds perception, steering and A*…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.