Quick answer: Spawn on the multiplayer authority (server). Add the scene to the Spawnable Scenes list on the MultiplayerSpawner. Set spawn_path to a node that exists on every peer. Add a MultiplayerSynchronizer to the spawned scene for ongoing state.
Server adds an enemy to a Node2D and expects all clients to see it. Clients see nothing. Or they see a node but no animation, position updates, or behavior. Spawning is one feature; synchronizing state is another.
The Symptom
Server runs fine; everything visible. Clients see the world but freshly spawned enemies don’t appear. Or they appear at the spawn location and never move. RPC calls work; spawn-driven replication does not.
The Required Setup
1. MultiplayerSpawner with Spawnable Scenes. Add a MultiplayerSpawner node anywhere in the scene. In its inspector, expand “Auto Spawn List” and add the .tscn paths of every scene you intend to spawn through it.
2. spawn_path points at a real node on every peer. Default is empty — you must set it. The path is relative to the MultiplayerSpawner’s parent and resolves on each peer to the same node.
Main (Node)
— Enemies (Node) # spawn_path = "Enemies"
— MultiplayerSpawner # next to it
3. Spawn on the authority. Default authority is peer 1 (server). On the server:
extends Node
@export var enemy_scene: PackedScene
func spawn_enemy(at: Vector2) -> void:
if not multiplayer.is_server():
return
var enemy := enemy_scene.instantiate()
enemy.position = at
$Enemies.add_child(enemy, true) # true = use unique name
The MultiplayerSpawner watches the Enemies node. When a child is added on the server, the same scene instantiates on every client at the same path with the same name.
Synchronizing State
Spawning replicates existence and initial values set before add_child. Subsequent changes (position, health, animation) need a MultiplayerSynchronizer.
Enemy.tscn
— Enemy (CharacterBody2D)
— Sprite2D
— MultiplayerSynchronizer
Replication Config:
.position
.rotation
health
Add the synchronizer to the spawned scene. In its Replication panel, list properties to sync. By default these are server-authoritative; the client receives.
Authority Per Player
For player-controlled spawned scenes (the player’s own character), set authority to the owning peer:
var player := player_scene.instantiate()
player.set_multiplayer_authority(peer_id)
$Players.add_child(player, true)
Now that peer’s movement is authoritative for that player object, while other peers see synchronized values.
Diagnosing
Enable Project Settings → Network → Multiplayer → Server Relay = true while debugging. In the Remote tree, you can inspect the live tree on each peer and confirm the spawned node exists. If the node is missing on a client, the spawn never propagated — check Spawnable Scenes list and spawn_path.
“Spawnable list. spawn_path. Server-side add_child. Synchronizer for state. Clients see the world.”
Related Issues
For RPC not firing, see RPC not firing. For player auth confusion, see authority basics.
Spawn list. Path. Authority. Synchronizer. Clients see.