In-progress. We are here exploring ways to get to the core of Multiplayer setup on Godot.
This thread will be refined many times, you are not suppose to read all this in the current state.
If you find something missing that is foundational and need to be addressed, simply post the change in the comments here. I’m not a professional, and only exploring the topic of multiplayer at best of my capabilities and minimal understanding.
Motivation:
MultiplayerSpawner can feel confusing without implementation reference.
The nodes themselves do nothing unless instructed using code which I found to be frustrating.
The Spawn Path and Auto Spawn List parameters found in the MultiplayerSpawner were very confusing, manual clear demonstration of Auto Spawn List was missing on the internet.
Also relationship with MultiplayerSynchronizer and where does entire system fit in the multiplayer along the RPC and other methods.
I’m sharing this example in hopes that it will make more clear on how to use the MultiplayerSpawner and achieve multiplayer. It’s 2D Example but applicable to 3D as well.
I tried to write as little code as possible.
Based on this example I may write a tutorial in the future.
Minimal unscripted MultiplayerSpawner demonstration
This would allow to see the working core of Multiplayer and MultiplayerSpawner setup with least confusion. However: MultiplayerSpawner is designed to dynamically spawn/despawn new scene instances, not manage existing instatiated scenes. Meaning you have to instatiate entity/player scenes under spawn_path node by script, there is most likely no other way.
Project 0: Basic Synchronized Server
Demonstrates: Multiplayer, MultiplayerSpawner, MultiplayerSynchronizer, Auto Spawn List.
Multiplayer Node with monolithic script that initialize scenes and add them as a child of SpawnContainer.
MultiplayerSpawner Spawn Path selects SpawnContainer node to monitor for initialized scenes.
Auto Spawn List marks child of SpawnContainer for replication.
MultiplayerSynchronizer synchronizes position property of Entity_Scene.
File: project.godot
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="godot-multiplayer-spawner-test"
run/main_scene="uid://dslh1c7rihy52"
config/features=PackedStringArray("4.4", "GL Compatibility")
run/low_processor_mode=true
config/icon="res://icon.svg"
File: main_scene.tscn
[gd_scene load_steps=3 format=3 uid="uid://dslh1c7rihy52"]
[ext_resource type="Texture2D" uid="uid://bxoyje2pq7nna" path="res://icon.svg" id="1_0f027"]
[sub_resource type="GDScript" id="GDScript_2c62f"]
resource_name = "Multiplayer"
script/source = "extends Node
const PORT=4443
func _ready() -> void:
if \"--server\" in OS.get_cmdline_args():
print(\"Creating server\")
var peer = ENetMultiplayerPeer.new()
peer.create_server(PORT)
multiplayer.multiplayer_peer = peer
await get_tree().process_frame
var player = load(\"res://entity_scene.tscn\").instantiate()
player.name = str(1)
player.position = Vector2(100, 100)
$\"../SpawnContainer\".add_child(player)
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
else:
print(\"Creating Client\")
var peer = ENetMultiplayerPeer.new()
peer.create_client(\"127.0.0.1\", PORT)
multiplayer.multiplayer_peer = peer
func _on_player_connected(id):
print(\"Player connected: \", id)
if multiplayer.is_server():
var player = load(\"res://entity_scene.tscn\").instantiate()
player.name = str(id)
$\"../SpawnContainer\".add_child(player)
func _on_player_disconnected(id):
print(\"Player disconnected: \", id)
if multiplayer.is_server():
var player = $\"../SpawnContainer\".get_node_or_null(str(id))
if player:
player.queue_free()
"
[node name="Node2D" type="Node2D"]
[node name="Multiplayer" type="Node" parent="."]
script = SubResource("GDScript_2c62f")
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."]
_spawnable_scenes = PackedStringArray("uid://dub7iv64uw2iq")
spawn_path = NodePath("../SpawnContainer")
[node name="SpawnContainer" type="Node2D" parent="."]
position = Vector2(171, 173)
[node name="Sprite2D" type="Sprite2D" parent="SpawnContainer"]
texture = ExtResource("1_0f027")
File: entity_scene.tscn
[gd_scene load_steps=4 format=3 uid="uid://dub7iv64uw2iq"]
[sub_resource type="GDScript" id="GDScript_qggms"]
resource_name = "entity_Scene"
script/source = "# Method 1: Simple position-based movement
#entity_scene node
extends Node2D
func _enter_tree():
# Set authority BEFORE _ready() - this is critical for MultiplayerSynchronizer
var player_id = int(name)
set_multiplayer_authority(player_id)
@export var speed = 300.0
func _physics_process(delta: float) -> void:
if is_multiplayer_authority():
var velocity = Input.get_vector(\"ui_left\", \"ui_right\", \"ui_up\", \"ui_down\") * speed
position += velocity * delta
"
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_qsx0g"]
size = Vector2(50, 50)
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_qsx0g"]
properties/0/path = NodePath(".:position")
properties/0/spawn = true
properties/0/replication_mode = 1
[node name="entity_scene" type="Node2D"]
script = SubResource("GDScript_qggms")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = SubResource("PlaceholderTexture2D_qsx0g")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_qsx0g")
Godot setting run/low_processor_mode=true
was used to achieve better debug instancing performance. (More players = better testing)
Personal Notes
extends MultiplayerSpawner
func _ready() -> void:
for i in range(get_spawnable_scene_count()):
#load(get_spawnable_scene(i)).instantiate()
print(get_spawnable_scene(i))
Project 0.1: Basic Synchronized Server with clean separation concerns
Demonstrates: Multiplayer, MultiplayerSpawner, MultiplayerSynchronizer, Auto Spawn List.
Now Multiplayer Node script and MultiplayerSpawner script do their own thing, instead of Multiplayer Node handling everything (scene spawning too) and MultiplayerSpawner only setting up spawn_path
to select where to spawn and marking replication scenes with auto_spawn_list
that should be replicated.
MultiplayerSpawner script actually spawns the scenes now, instead of Multiplayer Node script.
File: project.godot
[Your project.godot content here]
File: main_scene.tscn
[Your main_scene.tscn content here]
File: entity_scene.tscn
[Your entity_scene.tscn content here]
Changes from 0.1 version:
- Transfer spawning logic from Multiplayer node script to MultiplayerSpawner node script
Future
Project 1: Basic Synchronized Distributed Authority Server.
Allows clients to do whatever they want on the server.
… Planned (maybe as a separate post/example)
Project 2: Authoritative Server with RPC privilegies.
Allows to limit clients on what they can do and can’t do.
… Planned (maybe as a separate post/example)
Next Steps:
- Refine the source code to be more minimal and introduce even less confusion.
- UPNP Portforwarding
- RPC Use.
- The synchronized location of the player will need to be verified against anticheat and game logic.
- …Something more?
TODO:
- Introduce Authoritative Server (RPC based) version instead of current distributed authority synchronization.
- Improve quality of all the notes
Done:
- Do not care about renderer. (Remove my own choice of Compatibility renderer)
In this project we used Compatibility renderer, that’s my personal choice of supporting as many platforms as possible. (There are simply no reasons to use Forward+ since this is only a small example) Reason: Forward+ is default renderer, removes complexity in the project.godot file - Fix minor code style and formatting issues (2025-08-09)
Personal notes
Notes:
Multiplayer Spawner re-explained
MultiplayerSpawner Spawn_Path
is a node that will be used as position to relatively spawn on.
MultiplayerSpawner Auto_Spawn_List
are the Spawn_Path nodes that are allowed to spawn and synchronize.
There are no default spawning processes.
The spawning process have to be created manually through scripting.
Auto_Spawn_List are the list of entities scenes to be spawned.
Spawning happens only by initiating scene by code that is referenced in Auto_Spawn_List.
It is often said that MultiplayerSpawner is automatic: what they mean by that is that spawning happens by initializing a scene and it gets replicated. It is not fully automatic, you still need to do scripting to “spawn” anything: either by using spawn() function for custom spawning or by initializing scenes without need for additional functions to cause the spawining.
Once you instatiate these scenes from Auto_Spawn_List, they are replicated and spawned.
Auto_Spawn_List is basicly marking for replication.
After replication you need a synchronizer to synchronize position or other attributes.
Lastly multiplayer class have to be used to assign authority over controls, so that each game instance would be able to move on their own.