feat: Introduce a comprehensive game lobby system including UI, room management, and player interactions.

This commit is contained in:
Yogi Wiguna
2026-03-17 10:32:14 +08:00
parent eb018903aa
commit 49c8d794c2
5 changed files with 156 additions and 163 deletions
+60 -93
View File
@@ -2,7 +2,6 @@ extends Control
# UI References - Main Menu
@onready var main_menu_panel = $MainMenuPanel
@onready var player_name_input = $MainMenuPanel/VBoxContainer/InputSection/PlayerNameInput
@onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn
@onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn
@onready var main_menu_profile_btn = $MainMenuPanel/VBoxContainer/ButtonSection/ProfileBtn
@@ -12,10 +11,6 @@ extends Control
# UI References - Server Selection
@onready var server_option = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerOption
@onready var server_ip_input = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerIPInput
@onready var lan_section = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection
@onready var lan_ip_input = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection/LANIPInput
@onready var lan_host_btn = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection/LANHostBtn
@onready var lan_join_btn = $MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection/LANJoinBtn
# Leaderboard Reference
@onready var leaderboard_btn = $MainMenuPanel/VBoxContainer/ButtonSection/LeaderboardBtn
@@ -119,24 +114,11 @@ func _ready():
# Setup Game Mode specific UI dynamically
_create_custom_settings_ui()
# Set player name from profile and configure input visibility
if player_name_input:
# Get the parent container for the input to hide/show properly
var input_section = player_name_input.get_parent()
if AuthManager.is_guest:
# Guest user - show name input and let them enter a name
if input_section:
input_section.visible = true
player_name_input.text = "Guest"
player_name_input.editable = true
else:
# Logged-in user - hide name input and use profile name automatically
if input_section:
input_section.visible = false
player_name_input.text = UserProfileManager.get_display_name()
# Also set the LobbyManager name immediately
LobbyManager.local_player_name = UserProfileManager.get_display_name()
# Set player name from profile
if AuthManager.is_guest:
LobbyManager.local_player_name = "Guest"
else:
LobbyManager.local_player_name = UserProfileManager.get_display_name()
# Connect button signals - Main Menu
create_room_btn.pressed.connect(_on_create_room_pressed)
@@ -158,10 +140,6 @@ func _ready():
if server_ip_input:
server_ip_input.text_submitted.connect(_on_server_ip_submitted)
server_ip_input.focus_exited.connect(func(): _on_server_ip_submitted(server_ip_input.text))
if lan_host_btn:
lan_host_btn.pressed.connect(_on_lan_host_pressed)
if lan_join_btn:
lan_join_btn.pressed.connect(func(): _on_lan_join_pressed(lan_ip_input.text if lan_ip_input else "127.0.0.1"))
# Connect button signals - Room List
refresh_btn.pressed.connect(_on_refresh_pressed)
@@ -258,56 +236,22 @@ func _on_server_option_selected(index: int) -> void:
if index == 0:
# Nakama Localhost
if server_ip_input: server_ip_input.visible = false
if lan_section: lan_section.visible = false
NakamaManager.set_server("localhost")
LobbyManager.is_lan_mode = false
elif index == 1:
# Nakama Remote
if server_ip_input: server_ip_input.visible = true
if lan_section: lan_section.visible = false
if server_ip_input: NakamaManager.set_server(server_ip_input.text)
LobbyManager.is_lan_mode = false
else:
# LAN Direct
if server_ip_input: server_ip_input.visible = false
if lan_section: lan_section.visible = true
LobbyManager.is_lan_mode = true
func _on_server_ip_submitted(new_text: String) -> void:
if server_option and server_option.selected == 1:
NakamaManager.set_server(new_text.strip_edges())
func _on_lan_host_pressed() -> void:
"""Host a LAN game without Nakama."""
var player_name = player_name_input.text.strip_edges() if player_name_input else ""
if player_name.is_empty():
player_name = "Host"
LobbyManager.local_player_name = player_name
if connection_status:
connection_status.text = "Starting LAN server..."
var ok = await LobbyManager.create_room_lan()
if not ok:
if connection_status:
connection_status.text = "Failed to start LAN server. Check port 7777."
func _on_lan_join_pressed(host_ip: String) -> void:
"""Join a LAN game by entering the host's IP."""
var ip = host_ip.strip_edges()
if ip.is_empty():
if connection_status:
connection_status.text = "Enter the host's IP address."
return
var player_name = player_name_input.text.strip_edges() if player_name_input else ""
if player_name.is_empty():
player_name = "Player"
LobbyManager.local_player_name = player_name
if connection_status:
connection_status.text = "Connecting to %s..." % ip
var ok = LobbyManager.join_room_lan(ip)
if not ok:
if connection_status:
connection_status.text = "Failed to connect to %s. Is host running?" % ip
func _setup_game_modes() -> void:
if not game_mode_option: return
game_mode_option.clear()
@@ -362,21 +306,35 @@ func _show_panel(panel_name: String) -> void:
# =============================================================================
func _on_create_room_pressed() -> void:
# Use profile name for logged-in users, or input name for guests
# Use profile name for logged-in users, or guest for others
if AuthManager.is_guest:
LobbyManager.local_player_name = player_name_input.text.strip_edges()
if LobbyManager.local_player_name.is_empty():
if LobbyManager.local_player_name.is_empty() or LobbyManager.local_player_name == "Player":
LobbyManager.local_player_name = "Guest"
else:
LobbyManager.local_player_name = UserProfileManager.get_display_name()
connection_status.text = "Creating room..."
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
if LobbyManager.is_lan_mode:
connection_status.text = "Starting LAN room..."
var ok = await LobbyManager.create_room_lan("LAN Room " + str(randi_range(100, 999)))
if not ok:
connection_status.text = "Failed to start LAN room. Check port 7777."
else:
connection_status.text = "Creating Nakama room..."
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
func _on_browse_rooms_pressed() -> void:
_show_panel("room_list")
connection_status.text = "Loading rooms..."
LobbyManager.refresh_room_list()
if LobbyManager.is_lan_mode:
connection_status.text = "LAN Mode - Enter Host IP to join"
match_id_input.placeholder_text = "Enter Host IP (e.g. 192.168.1.10)..."
$RoomListPanel/VBoxContainer/MatchIdLabel.text = "DIRECT CONNECT (HOST IP)"
_on_refresh_pressed() # Try to discover rooms if implemented
else:
connection_status.text = "Loading Nakama rooms..."
match_id_input.placeholder_text = "Paste match ID here..."
$RoomListPanel/VBoxContainer/MatchIdLabel.text = "DIRECT CONNECT (MATCH ID)"
LobbyManager.refresh_room_list()
# =============================================================================
# Room List Button Handlers
@@ -390,7 +348,7 @@ func _on_refresh_pressed() -> void:
func _on_join_pressed() -> void:
var match_id = match_id_input.text.strip_edges()
if match_id.is_empty():
if match_id.is_empty() and not LobbyManager.is_lan_mode:
var selected_items = room_list.get_selected_items()
if selected_items.size() == 0:
connection_status.text = "Please select a room or enter Match ID"
@@ -400,20 +358,36 @@ func _on_join_pressed() -> void:
if selected_idx < LobbyManager.available_rooms.size():
match_id = LobbyManager.available_rooms[selected_idx].get("match_id", "")
if match_id.is_empty():
connection_status.text = "No room selected"
return
# Use profile name for logged-in users, or input name for guests
# Determine player name
if AuthManager.is_guest:
LobbyManager.local_player_name = player_name_input.text.strip_edges()
if LobbyManager.local_player_name.is_empty():
LobbyManager.local_player_name = "Guest"
else:
LobbyManager.local_player_name = UserProfileManager.get_display_name()
connection_status.text = "Joining room..."
LobbyManager.join_room(match_id)
if LobbyManager.is_lan_mode:
if match_id.is_empty():
# If nothing entered but something selected in list (discovered), use it
var selected_items = room_list.get_selected_items()
if selected_items.size() > 0:
var idx = selected_items[0]
if idx < LobbyManager.available_rooms.size():
match_id = LobbyManager.available_rooms[idx].get("ip", "")
if match_id.is_empty():
connection_status.text = "Enter Host IP to join"
return
connection_status.text = "Connecting to %s..." % match_id
var ok = LobbyManager.join_room_lan(match_id)
if not ok:
connection_status.text = "Failed to connect to %s" % match_id
else:
if match_id.is_empty():
connection_status.text = "No room selected"
return
connection_status.text = "Joining Nakama room..."
LobbyManager.join_room(match_id)
func _on_back_pressed() -> void:
_show_panel("main_menu")
@@ -728,13 +702,6 @@ func _on_room_joined(room_data: Dictionary) -> void:
_update_player_slots()
connection_status.text = "Connected to room"
# LAN solo mode: host is auto-ready, enable Start Game immediately
if LobbyManager.is_lan_mode and is_host:
ready_btn.button_pressed = true
ready_btn.text = "READY ✓"
LobbyManager.force_solo_ready()
status_label.text = "LAN Solo — press Start Game when ready!"
func _on_room_left() -> void:
_show_panel("main_menu")
@@ -758,8 +725,12 @@ func _on_ready_state_changed(_player_id: int, _is_ready: bool) -> void:
func _on_all_players_ready() -> void:
if LobbyManager.is_host:
start_game_btn.disabled = false
status_label.text = "All ready! Start the match!"
if LobbyManager.is_lan_mode and LobbyManager.players_in_room.size() == 1:
# Auto-start for solo LAN testing
LobbyManager.start_game()
else:
start_game_btn.disabled = false
status_label.text = "All ready! Start the match!"
else:
status_label.text = "All ready! Waiting for host..."
@@ -801,10 +772,6 @@ func _on_profile_updated() -> void:
"""Handle profile updates (name/avatar change)."""
var new_name = UserProfileManager.get_display_name()
# Update input if visible
if player_name_input:
player_name_input.text = new_name
# Sync to LobbyManager if we are in a room or just locally
LobbyManager.set_player_name(new_name)
+12 -64
View File
@@ -43,10 +43,10 @@ anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -220.0
offset_top = -240.0
offset_right = 220.0
offset_bottom = 240.0
offset_left = -221.0
offset_top = -320.0
offset_right = 219.0
offset_bottom = 286.0
grow_horizontal = 2
grow_vertical = 2
@@ -76,22 +76,6 @@ horizontal_alignment = 1
[node name="Separator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=126990892]
layout_mode = 2
[node name="InputSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=1865748579]
layout_mode = 2
theme_override_constants/separation = 10
[node name="PlayerNameLabel" type="Label" parent="MainMenuPanel/VBoxContainer/InputSection" unique_id=1017736748]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 13
text = "YOUR NAME"
[node name="PlayerNameInput" type="LineEdit" parent="MainMenuPanel/VBoxContainer/InputSection" unique_id=1668571796]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
text = "Player"
placeholder_text = "Enter your name..."
[node name="ServerSelectionSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer" unique_id=748392101]
layout_mode = 2
theme_override_constants/separation = 10
@@ -121,43 +105,6 @@ layout_mode = 2
text = "127.0.0.1"
placeholder_text = "Enter Nakama Server IP..."
[node name="LANSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection" unique_id=748392110]
visible = false
layout_mode = 2
theme_override_constants/separation = 8
[node name="LANInfo" type="Label" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392111]
layout_mode = 2
theme_override_colors/font_color = Color(0.7, 0.7, 0.7, 1)
theme_override_font_sizes/font_size = 12
text = "Play over LAN without any server.\nFirewall may need to allow port 7777."
autowrap_mode = 3
[node name="LANHostBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392112]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "HOST LAN GAME"
[node name="LANOrLabel" type="Label" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392113]
layout_mode = 2
theme_override_colors/font_color = Color(0.5, 0.5, 0.5, 1)
theme_override_font_sizes/font_size = 11
text = "── or join a friend ──"
horizontal_alignment = 1
[node name="LANIPInput" type="LineEdit" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392114]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
text = "127.0.0.1"
placeholder_text = "Host IP (e.g. 192.168.1.10)"
[node name="LANJoinBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ServerSelectionSection/LANSection" unique_id=748392115]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "JOIN LAN GAME"
[node name="ServerSeparator" type="HSeparator" parent="MainMenuPanel/VBoxContainer" unique_id=748392105]
layout_mode = 2
@@ -177,7 +124,7 @@ layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "BROWSE ROOMS"
[node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection"]
[node name="LeaderboardBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=216339260]
custom_minimum_size = Vector2(0, 48)
layout_mode = 2
theme_override_font_sizes/font_size = 16
@@ -190,14 +137,14 @@ theme_override_font_sizes/font_size = 16
text = "SETTINGS"
[node name="ProfileBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=1640960506]
layout_mode = 2
custom_minimum_size = Vector2(0, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "PROFILE"
[node name="QuitBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=123456780]
layout_mode = 2
custom_minimum_size = Vector2(0, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "QUIT GAME"
@@ -278,6 +225,7 @@ layout_mode = 2
text = "PROFILE"
[node name="LobbyPanel" type="Control" parent="." unique_id=1745714811]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
@@ -1188,10 +1136,10 @@ anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 464.0
offset_top = -93.0
offset_right = -461.0
offset_bottom = -44.0
offset_left = 466.0
offset_top = -65.0
offset_right = -459.0
offset_bottom = -16.0
grow_horizontal = 2
grow_vertical = 0
-3
View File
@@ -1,7 +1,5 @@
[gd_scene format=3 uid="uid://cggmcgvdj6wxt"]
[ext_resource type="ArrayMesh" uid="uid://dtr46jmckif0p" path="res://assets/models/meshes/block.res" id="1_block"]
[sub_resource type="BoxShape3D" id="BoxShape3D_wall"]
size = Vector3(1.68, 1.5, 0.05)
@@ -10,7 +8,6 @@ collision_mask = 0
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1405008923]
transform = Transform3D(1.68, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
mesh = ExtResource("1_block")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1446599023]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35764623, 0)