Multiplayer Lobby not updating local Ready state

Godot Version

4.2.2

Question

I’m currently trying to create a lobby that works that I can later implement into my game. I feel like I’m on the cusp of it working. When the game loads there’s a menu with two buttons, Host and Join. When a player presses the Host button, it creates a lobby, and in that lobby is a list of players in the lobby with their ready state, and a Ready Button. By default, players are not ready, and when they press ready it should change their ready state to ready, then if they press the button again it should make them not ready.

What is happening is that when a player presses the Ready button, it shows that they are ready on the other players screen, but not on their own! This means I never fulfill the conditions of having all of the players ready to start the game. Here is the related code.

Menu script:

extends Control

@onready var host_button = $HostButton
@onready var join_button = $JoinButton

func _ready():
	host_button.pressed.connect(_on_host_button_pressed)
	join_button.pressed.connect(_on_join_button_pressed)

func _on_host_button_pressed():
	NetworkManager.host_game()
	var lobby = preload("res://scenes/Lobby.tscn").instantiate()
	get_tree().root.add_child(lobby)
	queue_free()

func _on_join_button_pressed():
	NetworkManager.join_game("127.0.0.1")  # Use localhost for testing
	var lobby = preload("res://scenes/Lobby.tscn").instantiate()
	get_tree().root.add_child(lobby)
	queue_free()

NetworkManager Script:

extends Node

const PORT = 7000
const MAX_PLAYERS = 2

signal player_connected(peer_id)
signal player_disconnected(peer_id)
signal serverFull()
signal connection_failed()
signal connection_succeeded()
signal player_ready_changed(peer_id, is_ready)

var peer = ENetMultiplayerPeer.new()
@export var players_ready = {}

func host_game():
	var error = peer.create_server(PORT, MAX_PLAYERS)
	if error == OK:
		multiplayer.multiplayer_peer = peer
		print("Server started")
	else:
		print("Failed to create server")

func join_game(ip_address):
	var error = peer.create_client(ip_address, PORT)
	if error == OK:
		multiplayer.multiplayer_peer = peer
		print("Attempting to connect to server")
	else:
		print("Failed to create client")
		connection_failed.emit()

func _ready():
	multiplayer.peer_connected.connect(_on_peer_connected)
	multiplayer.peer_disconnected.connect(_on_peer_disconnected)
	multiplayer.connected_to_server.connect(_on_connected_to_server)
	multiplayer.connection_failed.connect(_on_connection_failed)
	multiplayer.server_disconnected.connect(_on_server_disconnected)

func _on_peer_connected(id):
	print("Peer connected: " + str(id))
	if multiplayer.is_server() and multiplayer.get_peers().size() > MAX_PLAYERS - 1:
	# Server is full, disconnect the peer
		peer.disconnect_peer(id)
	else:
		player_connected.emit(id)
		players_ready[id] = false

func _on_peer_disconnected(id):
	print("Peer disconnected: " + str(id))
	player_disconnected.emit(id)
	players_ready.erase(id)

func _on_connected_to_server():
	print("Successfully connected to server")
	connection_succeeded.emit()

func _on_connection_failed():
	print("Failed to connect to server")
	connection_failed.emit()

func _on_server_disconnected():
	print("Server disconnected")
	# Handle server disconnection (e.g., return to main menu)

@rpc("any_peer", "reliable")
func player_ready(is_ready):
	var id = multiplayer.get_remote_sender_id()
	players_ready[id] = is_ready
	player_ready_changed.emit(id, is_ready)
	if multiplayer.is_server() and all_players_ready():
		start_game.rpc()

func get_player_ready(peer_id):
	return players_ready.get(peer_id, false)

func all_players_ready():
	return players_ready.size() == MAX_PLAYERS and players_ready.values().all(func(x): return x)

@rpc("authority", "reliable")
func start_game():
	get_tree().change_scene_to_file("res://scenes/Game.tscn")

@rpc("authority", "reliable")
func server_full():
	serverFull.emit()

Lobby Script:

extends Control

@onready var player_list = $PlayerList
@onready var ready_button = $ReadyButton

var players = {}

func _ready():
	NetworkManager.player_connected.connect(_on_player_connected)
	NetworkManager.player_disconnected.connect(_on_player_disconnected)
	NetworkManager.player_ready_changed.connect(_on_player_ready_changed)
	ready_button.pressed.connect(_on_ready_button_pressed)
	
	# Add local player
	var local_id = multiplayer.get_unique_id()
	_on_player_connected(local_id)

func _on_player_connected(peer_id):
	players[peer_id] = {
	"name": "Player " + str(peer_id),
	"ready": false
	}
	update_player_list()

func _on_player_disconnected(peer_id):
	players.erase(peer_id)
	update_player_list()

func update_player_list():
	player_list.clear()
	for peer_id in players:
		var player = players[peer_id]
		var status = " (Ready)" if NetworkManager.get_player_ready(peer_id) else " (Not Ready)"
		player_list.add_item(player["name"] + status)
	
	# Update local ready button
	var local_id = multiplayer.get_unique_id()
	if local_id in players:
		if players[local_id]["ready"]:
			ready_button.text = "Not Ready"
		else:
			ready_button.text = "Ready"

func _on_ready_button_pressed():
	var local_id = multiplayer.get_unique_id()
	var new_ready_state = !players[local_id]["ready"]
	NetworkManager.player_ready.rpc(new_ready_state)

func _on_player_ready_changed(peer_id, is_ready):
	if peer_id in players:
		players[peer_id]["ready"] = is_ready
		update_player_list()
	
	# If it's the local player, update the button text immediately
	if peer_id == multiplayer.get_unique_id():
		ready_button.text = "Not Ready" if is_ready else "Ready"

Oh, and the NetworkManager script is autoloaded in the project.

I feel like I’m missing something simple here. What am I missing?

I attempted to run your code, and I noticed that when the host presses ‘Ready’, there’s no response. This is because in your player_ready(is_ready) function annotated with rpc, you’re missing the call_local invocation which should look something like ‘rpc(“any_peer”,“call_local”, “reliable”)’.

Furthermore, I observed that when a client connects to the server, it doesn’t automatically synchronize the state of all players who are already present in the room. You can design a method to request synchronization of all Player Ready statuses upon connection.

The networking part is truly the headache-inducing segment /(ㄒoㄒ)/~~

It truly is! My small (hobbyist) team has a webdev that was supposed to be handling all of this, but he went on vacation! Now I got to a part that requires a functioning lobby, which is incompatible with our old networking set-up, so I had to remake a new one myself!

Thanks a ton for finding the source of my grief, as well as finding that little bug I didn’t even notice! Both are fixed now, you’re my savior!

1 Like