The Lobby node handles Godot 4.x multiplayer game session management, including hosting, joining, and basic player state synchronization.
You can use this to
- Copy all 3 files (Lobby.gd, LobbyClient.gd, and LobbyServer.gd) into your project
- Make Lobby.gd a Global in Project Settings and name it "Lobby"
- Open
Lobby.gd
and configure LOBBY_SETTINGS
Generally speaking, in your game you'll be subscribing to the Lobby's signals to capture events like players joining and leaving and more.
Note that pid
is your local player's ID, and sid
is the ID of the player who sent the message. If either is equal to Lobby.server_id()
, then the message is from the server itself.
connection_succeeded(pid: int)
- Emitted when successfully connected to a serverconnection_failed()
- Emitted when connection attempt failsconnection_refused(message: String)
- Emitted when server refuses connection (e.g. version mismatch)connection_ended()
- Emitted when connection to server endsplayer_connected(pid: int)
- Emitted when any player connectsplayer_joined(pid: int, player: Dictionary)
- Emitted when player completes joining processi_joined(pid: int)
- Emitted when local player completes joining processplayer_updated(pid: int, player: Dictionary)
- Emitted when player info is updatedplayer_disconnected(pid: int)
- Emitted when player disconnectsplayer_left(pid: int, player: Dictionary, message: String)
- Emitted when player properly leavesi_left(pid: int, message: String)
- Emitted when local player leavesplayer_messaged(sid: int, data: Dictionary)
- Emitted when receiving a message from another player.sid
is the ID of the player who sent the message.host_commanded(data: Dictionary)
- Emitted when host sends commandgame_state_updated(new_state: Dictionary)
- Emitted when non-player state is updated. You can access just the new state in the event argument, or the full merged state inLobby.game_state
.all_players_ready()
- Emitted when all players become readyall_players_not_ready()
- Emitted when any player becomes not ready
server_started()
- Emitted when server starts successfullyserver_shutdown(message: String)
- Emitted when server shuts downserver_discovery_started()
- Emitted when server starts broadcasting presenceserver_discovery_failed(error: int)
- Emitted when server fails to start broadcastingserver_discovery_stopped()
- Emitted when server stops broadcastingplayer_messaged_host(sid: int, data: Dictionary)
- Emitted when the host receives a message from a player
func host_game() -> void
Start hosting a game server. Also joins the server as the host.
func end_hosted_game() -> void
End the currently hosted game. Only works if called by the host.
func refresh_servers(callback: Callable) -> void
Search for local servers. Callback will receive Dictionary of found servers that looks like this:
{
"192.168.0.52": {
"game_name": "Thunderbird",
"game_version": "1.4.2",
"server_name": "Jamon's Server",
"port": "4923"
}
"192.168.0.53": {
"game_name": "Thunderbird",
"game_version": "1.4.3",
"server_name": "Mike's Server",
"port": "4925"
}
}
You can then use this info to display servers:
func refresh():
Lobby.refresh_servers(func (new_servers: Dictionary):
refresh_server_list(new_servers)
)
func refresh_server_list(servers: Dictionary):
var info = "Servers:\n"
if servers.size() == 0:
info += "No servers found"
return
for ip in servers:
var server = servers[ip]
var compatible = server.game_version == ProjectSettings.get_setting("application/config/version")
if compatible:
info += "" + server.server_name + ": " + ip + "\n"
else:
info += "" + server.server_name + " (wrong game version: " + server.game_version + ")" + "\n"
This, of course, is a very simplistic example.
func local_servers() -> Dictionary
Get the same info as above, but without refreshing first.
func join_server(server_address: String) -> void
Connect to server at given IP address.
func leave_server(message: String) -> void
Leave current server with goodbye message, such as "Jamon left the lobby."
func update(new_info: Dictionary) -> void
Update local player information. Changes will automatically sync to all clients and the server.
func ready_up() -> void
Mark local player as ready to start the game. Changes will automatically sync to all clients and the server.
func not_ready() -> void
Mark local player as not ready. Changes will automatically sync to all clients and the server.
func broadcast(data: Dictionary) -> void
Send message to all connected players. Use this for things like chat messages and other non-state events.
func command(data: Dictionary) -> void
Host only: Send command to all players. Use this for things like "Start game" or "Change scene". It takes a dictionary.
Example:
Lobby.command({ "scene": "World" })
You'd handle this on the client by watching for on_host_command
signals (see below) and updating the current scene or whatever.
func command_id(pid: int, data: Dictionary) -> void
Host only: Same as command
, but to a specific player ID.
func id() -> int
Get local player's ID.
func server_id() -> int
Get server's ID.
func is_host() -> bool
Check if local player is the host.
func is_in_server() -> bool
Check if connected to a server.
func is_client() -> bool
Check if connected as client (not host).
func all_ready() -> bool
Check if all players are marked as ready.
This is where you can access your player's synchronized state.
{
"name": "Unknown Player",
"ready": false,
"game_version": String # Automatically set from project settings
}
Additional fields can be added in Lobby.gd
and will sync automatically.
You usually wouldn't modify this directly, as it won't sync automatically if you do. Instead, use Lobby.update(info)
to update your player state.
This holds all connected players' states (including a copy of your local player state), keyed by peer ID:
var players: Dictionary = {
1: {
"name": "Player 1",
"ready": false,
"game_version": "1.0.0"
},
2: {
"name": "Player 2",
"ready": true,
"game_version": "1.0.0"
}
}
This holds any other game state that isn't player-specific. You can sync this state using Lobby.update_game_state(new_state)
.
var game_state: Dictionary = {
# Add any game-specific state here
"current_map": "forest",
"game_mode": "capture_the_flag",
# etc...
}
const LOBBY_SETTINGS := {
server_name = "Lobby", # Default server name
game_port = 6464, # Main game server port
discovery_port = 6463, # Server discovery broadcast port
max_players = 8, # Maximum allowed players
client_discovery_port = 6462, # Port for client discovery
refresh_packet = "LOBBY_CLIENT", # Discovery packet identifier
connection_timeout = 3 # Seconds to wait for connection
}