Godot 4.4
Hello! I’m working on a retro styled boomer shooter FPS game and i want it to have a fun multiplayer feature built in where you can either play the campaign solo or host a server for others to see on their server list and join.
I’ve got it set up so you can type in a server name, your own username, and host it, and you’ll be put into the game just fine.
(When you host it you’re put into the game and your player and the basic map is spawned, your username you entered is also displayed for you in the screen for debug to show if you have the correct assigned name.)
The problem comes when i have another game of it running through 2 instances, the first player can start a server and get in-game, but the other player either: sees no server on their list at all, completely empty server list, or, they see two of the same server, both with only the default text, not the text it’s supposed to have for individual server info.
Here is how i’ve set up my scene:
Basically when you host a server i need it to send it to all of the other players to update the server list and show it so they can join it and join in the same world, so multiple people can be hosting servers and playing with different groups of people.
Here is my MultiplayerLAN.gd:
@export var default_server_port := 6767
@export var player_scene := preload("res://Player/player.tscn")
@onready var server_browser := $ServerBrowser
@onready var username_input := $Username
@onready var servername_input := $ServerName
var peer : ENetMultiplayerPeer
var my_player
var server_port := 0
func _ready():
multiplayer.peer_connected.connect(peer_connected)
multiplayer.peer_disconnected.connect(peer_disconnected)
multiplayer.connected_to_server.connect(connected_to_server)
multiplayer.connection_failed.connect(connection_failed)
server_browser.joinGame.connect(_on_join_server_selected)
# --- HOST ---
func host_game():
var server_name = servername_input.text
var player_name = username_input.text
server_port = default_server_port
peer = ENetMultiplayerPeer.new()
var err = peer.create_server(server_port, 8)
if err != OK:
print("Cannot create server: port may be in use")
return
multiplayer.set_multiplayer_peer(peer)
print("Server started on port:", server_port, "Name:", server_name)
# Start broadcasting
server_browser.set_up_broadcast(server_name, server_port)
# Spawn host player
spawn_player(multiplayer.get_unique_id(), player_name)
visible = false
# --- SPAWN PLAYER ---
func spawn_player(peer_id: int, player_name: String):
if get_tree().current_scene.has_node(str(peer_id)):
return
var player_instance = player_scene.instantiate()
player_instance.name = str(peer_id)
player_instance.set_multiplayer_authority(peer_id)
if peer_id == multiplayer.get_unique_id():
player_instance.is_local_player = true
my_player = player_instance
var viewport = $"../Screen/SubViewport"
viewport.add_child(player_instance)
if player_instance.has_node("Username"):
player_instance.get_node("Username").text = player_name
# --- RPC ---
@rpc("any_peer")
func rpc_spawn_player(peer_id: int, player_name: String):
spawn_player(peer_id, player_name)
# --- CONNECT TO SERVER ---
func _on_join_server_selected(ip: String, port: int):
peer = ENetMultiplayerPeer.new()
var err = peer.create_client(ip, port)
if err != OK:
print("Failed to create client!")
return
multiplayer.set_multiplayer_peer(peer)
func connected_to_server():
var player_name = username_input.text
rpc_id(1, "rpc_spawn_player", multiplayer.get_unique_id(), player_name)
print("Connected to server!")
func connection_failed():
print("Connection failed!")
# --- PLAYER CONNECT/DISCONNECT ---
func peer_connected(id):
print("Player connected:", id)
func peer_disconnected(id):
print("Player disconnected:", id)
var player = get_tree().current_scene.get_node_or_null(str(id))
if player:
player.queue_free()
My ServerBrowser.gd:
signal joinGame(ip: String, port: int)
@export var listen_port: int = 7777
@export var broadcast_port: int = 6767
@export var broadcast_address: String = "255.255.255.255"
@export var broadcast_interval: float = 1.0
@export var server_port: int = 6767
@onready var server_list: VBoxContainer = $Panel/ServerList
@export var server_info_scene = load("res://Multiplayer/server_info.tscn")
var listener: PacketPeerUDP
var broadcaster: PacketPeerUDP
var discovered_servers := {} # key = "ip:port"
var is_hosting := false
var server_name := "Server"
var room_info := {
"name": "Server",
"playerCount": 0,
"port": 6767
}
func _ready() -> void:
print("[ServerBrowser] Ready, setting up listener...")
_setup_listener()
_start_broadcast_timer()
# ----------------------------
# Listener Setup
# ----------------------------
func _setup_listener() -> void:
listener = PacketPeerUDP.new()
var err := listener.bind(listen_port, "0.0.0.0")
if err == OK:
print("[ServerBrowser] Bound to listener port", listen_port)
else:
print("[ServerBrowser] Failed to bind listener port", listen_port, "Error:", err)
set_process(true)
# ----------------------------
# Broadcaster Setup
# ----------------------------
func set_up_broadcast(name: String, port: int) -> void:
server_name = name
server_port = port
room_info.name = name
room_info.port = port
is_hosting = true
print("[ServerBrowser] Setting up broadcaster for server: %s on port %d" % [name, port])
broadcaster = PacketPeerUDP.new()
broadcaster.set_broadcast_enabled(true)
print("[ServerBrowser] Broadcast timer started")
func _on_BroadcastTimer_timeout() -> void:
if not is_hosting or broadcaster == null:
print("[ServerBrowser] Broadcaster not initialized yet!")
return
# Update player count
room_info.playerCount = GameManager.Players.size() if Engine.has_singleton("GameManager") else 1
var data := JSON.stringify(room_info)
# Set broadcast destination
broadcaster.set_dest_address(broadcast_address, listen_port)
broadcaster.put_packet(data.to_utf8_buffer())
print("[ServerBrowser] Broadcast sent:", data)
# ----------------------------
# Process Incoming Broadcasts
# ----------------------------
func _process(delta: float) -> void:
if listener == null:
return
while listener.get_available_packet_count() > 0:
var server_ip: String = listener.get_packet_ip()
var packet_bytes: PackedByteArray = listener.get_packet()
if packet_bytes.size() == 0:
continue
var packet_str: String = packet_bytes.get_string_from_utf8()
var incoming_info = JSON.parse_string(packet_str) # returns Dictionary
if typeof(incoming_info) != TYPE_DICTIONARY:
print("[ServerBrowser] Failed to parse server broadcast:", packet_str)
continue
var server_port: int = int(incoming_info.get("port", broadcast_port))
var server_key: String = "%s:%d" % [server_ip, server_port]
if not discovered_servers.has(server_key):
if server_info_scene == null:
push_error("[ServerBrowser] server_info_scene is null! Assign your ServerInfo.tscn in the editor.")
continue
var entry: Control = server_info_scene.instantiate()
# Immediately set info BEFORE adding to scene
entry.update_server_info({
"name": incoming_info.get("name", "Unknown"),
"players": incoming_info.get("playerCount", 0),
"max_players": 16,
"ip": server_ip,
"port": server_port
})
# Connect join signal
entry.joinGame.connect(join_by_ip)
# Add to scene AFTER info is set
server_list.add_child(entry)
discovered_servers[server_key] = entry
print("[ServerBrowser] Added server:", incoming_info.get("name", "Unknown"), "IP:", server_ip, "Port:", server_port)
# No need to update entries if info is set correctly at spawn
# ----------------------------
# Join Server Signal
# ----------------------------
func join_by_ip(ip: String, port: int) -> void:
print("[ServerBrowser] Join requested for IP:", ip, "Port:", port)
emit_signal("joinGame", ip, port)
# ----------------------------
# Timer Setup
# ----------------------------
func _start_broadcast_timer() -> void:
var timer: Timer
if has_node("BroadcastTimer"):
timer = $BroadcastTimer
else:
timer = Timer.new()
timer.name = "BroadcastTimer"
timer.wait_time = broadcast_interval
timer.autostart = true
timer.one_shot = false
add_child(timer)
timer.timeout.connect(_on_BroadcastTimer_timeout)
# ----------------------------
# Clean up
# ----------------------------
func _exit_tree() -> void:
if listener != null:
listener.close()
if broadcaster != null:
broadcaster.close()
ServerDiscovery.gd:
signal server_found(info: Dictionary)
@export var listen_port: int = 7777
var listener: PacketPeerUDP
var discovered_servers := {}
func _ready():
listener = PacketPeerUDP.new()
var result = listener.bind(listen_port)
if result != OK:
push_error("[ServerDiscovery] ❌ Failed to bind UDP listener on port %d" % listen_port)
else:
print("[ServerDiscovery] Listening on port %d for broadcasts..." % listen_port)
set_process(true)
func _process(_delta):
if listener == null:
return
while listener.get_available_packet_count() > 0:
var sender_ip := listener.get_packet_ip()
var bytes := listener.get_packet()
if bytes.is_empty():
continue
var json := JSON.new()
var parse_result := json.parse(bytes.get_string_from_utf8())
if parse_result != OK:
print("[ServerDiscovery] ⚠️ Invalid JSON from %s" % sender_ip)
continue
var info: Dictionary = json.get_data()
if typeof(info) != TYPE_DICTIONARY:
print("[ServerDiscovery] ⚠️ Non-dictionary broadcast from %s" % sender_ip)
continue
# Get port from broadcast or default
var port := int(info.get("port", listen_port))
var key := "%s:%d" % [sender_ip, port]
if not discovered_servers.has(key):
info["ip"] = sender_ip # Add IP field manually
discovered_servers[key] = info
print("[ServerDiscovery] 🟢 Found new server: %s (%s:%d)" %
[info.get("name", "Unknown"), sender_ip, port])
emit_signal("server_found", info)
else:
# Optional: update existing entry
discovered_servers[key] = info
And my ServerInfo.gd: (the scene that needs to be instantiated in the server list to show active servers and their info)
extends Control
signal joinGame(ip: String, port: int)
var server_info := {}
@onready var server_name_label = $ServerName
@onready var players_label = $PlayersLabel
@onready var join_button = $Button
func _ready():
join_button.pressed.connect(_on_join_button_pressed)
# Properly update the server info dictionary, including IP & port
func update_server_info(info: Dictionary) -> void:
server_info = info
if server_name_label:
server_name_label.text = str(server_info.get("name", "Unknown"))
if players_label:
var players = int(server_info.get("playerCount", 0))
var max_players = int(server_info.get("maxPlayers", 16))
players_label.text = "%d/%d" % [players, max_players]
# Emit signal with correct IP & port
func _on_join_button_pressed() -> void:
if server_info.size() == 0:
return
var ip = str(server_info.get("ip", ""))
var port = int(server_info.get("port", 6767))
if ip == "":
push_error("Server IP missing! Cannot join server.")
return
emit_signal("joinGame", ip, port)
If anyone can help me out that would be amazing!!! Thank you so much

