Godot’s high-level multiplayer system provides tools for automatic synchronization, networked object spawning, and scene replication. This simplifies creating multiplayer games by handling common networking patterns automatically.
High-level multiplayer is implemented through SceneMultiplayer, the default MultiplayerAPI implementation.
Automatically spawns and despawns objects across the network:
# Setup spawner in server sceneextends Node2Dfunc _ready(): if multiplayer.is_server(): # Create spawner var spawner = MultiplayerSpawner.new() spawner.spawn_path = get_path() # Where to spawn children # Register spawnable scenes spawner.add_spawnable_scene("res://player.tscn") spawner.add_spawnable_scene("res://enemy.tscn") add_child(spawner) # Connect to peer events multiplayer.peer_connected.connect(_on_peer_connected)func _on_peer_connected(id: int): # Spawn a player for the new peer var player = preload("res://player.tscn").instantiate() player.name = str(id) # Use peer ID as name player.set_multiplayer_authority(id) add_child(player)
public partial class GameWorld : Node2D{ public override void _Ready() { if (Multiplayer.IsServer()) { // Create spawner var spawner = new MultiplayerSpawner(); spawner.SpawnPath = GetPath(); // Where to spawn children // Register spawnable scenes spawner.AddSpawnableScene("res://player.tscn"); spawner.AddSpawnableScene("res://enemy.tscn"); AddChild(spawner); // Connect to peer events Multiplayer.PeerConnected += OnPeerConnected; } } private void OnPeerConnected(long id) { // Spawn a player for the new peer var player = GD.Load<PackedScene>("res://player.tscn").Instantiate<Node>(); player.Name = id.ToString(); // Use peer ID as name player.SetMultiplayerAuthority((int)id); AddChild(player); }}
Use the peer ID as the node name for easy identification and authority management.
Automatically synchronizes node properties across the network:
# Player sceneextends CharacterBody2Dfunc _ready(): # Only add synchronizer on server if multiplayer.is_server(): var synchronizer = MultiplayerSynchronizer.new() # Synchronize these properties synchronizer.add_property("position") synchronizer.add_property("velocity") synchronizer.add_property("rotation") # Set update interval (in seconds) synchronizer.replication_interval = 0.05 # 20 updates per second add_child(synchronizer)func _process(delta): # Only authority moves the player if is_multiplayer_authority(): handle_input() move_and_slide() # Properties auto-sync to other peers
public partial class Player : CharacterBody2D{ public override void _Ready() { // Only add synchronizer on server if (Multiplayer.IsServer()) { var synchronizer = new MultiplayerSynchronizer(); // Synchronize these properties synchronizer.AddProperty("position"); synchronizer.AddProperty("velocity"); synchronizer.AddProperty("rotation"); // Set update interval (in seconds) synchronizer.ReplicationInterval = 0.05f; // 20 updates per second AddChild(synchronizer); } } public override void _Process(double delta) { // Only authority moves the player if (IsMultiplayerAuthority()) { HandleInput(); MoveAndSlide(); } // Properties auto-sync to other peers }}
extends Node2D# Properties to synchronizevar health: int = 100var max_health: int = 100var team: int = 0var player_name: String = ""func _ready(): if multiplayer.is_server(): var sync = MultiplayerSynchronizer.new() # Add properties with different sync modes sync.add_property("health") # Always sync sync.add_property("max_health") # Sync once at spawn sync.add_property("team") sync.add_property("player_name") # Configure sync behavior sync.replication_interval = 0.1 # 10 times per second sync.delta_interval = 0.0 # Send every update (no delta compression) add_child(sync)
var sync: MultiplayerSynchronizerfunc _ready(): sync = MultiplayerSynchronizer.new() sync.add_property("position") # Set visibility filter sync.visibility_update_mode = MultiplayerSynchronizer.VISIBILITY_PROCESS_IDLE sync.set_visibility_for(1, true) # Visible to peer 1 sync.set_visibility_for(2, false) # Hidden from peer 2 add_child(sync)# Dynamic visibility (e.g., fog of war)func update_visibility(): var peers = multiplayer.get_peers() for peer_id in peers: var distance = position.distance_to(get_peer_position(peer_id)) var visible = distance < 500.0 # Visible within 500 units sync.set_visibility_for(peer_id, visible)
# Transfer vehicle authority when player entersextends Vehiclefunc _on_player_entered(player: Node): # Get the player's authority var player_id = player.get_multiplayer_authority() # Transfer vehicle authority to that player set_multiplayer_authority(player_id) print("Vehicle now controlled by peer ", player_id)func _on_player_exited(player: Node): # Return authority to server set_multiplayer_authority(1) print("Vehicle returned to server control")
extends CharacterBody2Dvar server_position: Vector2var server_velocity: Vector2func _ready(): if not is_multiplayer_authority(): # Setup interpolation for non-authority server_position = position server_velocity = velocityfunc _process(delta): if is_multiplayer_authority(): # Authority: normal movement handle_input() move_and_slide() # Send updates to server update_server.rpc(position, velocity) else: # Non-authority: interpolate to server position position = position.lerp(server_position, delta * 10.0) velocity = velocity.lerp(server_velocity, delta * 10.0)@rpc("any_peer", "unreliable")func update_server(pos: Vector2, vel: Vector2): if multiplayer.is_server(): # Server receives and validates server_position = pos server_velocity = vel # Broadcast to other clients update_clients.rpc(pos, vel)@rpc("authority", "unreliable")func update_clients(pos: Vector2, vel: Vector2): if not is_multiplayer_authority(): server_position = pos server_velocity = vel
Client-side prediction makes movement feel responsive even with network latency.
extends CharacterBody2Dfunc _process(delta): if is_multiplayer_authority(): # Client sends input to server var input = get_input_vector() request_move.rpc_id(1, input) # All clients update based on server state move_and_slide()@rpc("any_peer", "unreliable_ordered")func request_move(input: Vector2): if not multiplayer.is_server(): return # Server validates and applies movement velocity = input.normalized() * 300 # Synchronizer broadcasts position automatically