feat: Implement a new lobby system with configurable match duration, game over screen, and core game state management.

This commit is contained in:
2025-12-20 01:10:49 +08:00
parent 75eb398649
commit b0d45d4569
12 changed files with 1241 additions and 338 deletions
+203 -2
View File
@@ -30,6 +30,7 @@ func _ready():
ui_manager.setup_leaderboard_ui(self)
ui_manager.setup_powerup_bar_ui(self)
_setup_obstacle_ui()
_setup_global_match_timer_ui()
# Auto-start game if coming from lobby (already connected to match)
if NakamaManager.is_connected_to_nakama() and multiplayer.get_unique_id() != 0:
@@ -59,6 +60,8 @@ func _init_managers():
goals_cycle_manager.timer_updated.connect(_on_timer_updated)
goals_cycle_manager.score_updated.connect(_on_score_updated)
goals_cycle_manager.leaderboard_updated.connect(_on_leaderboard_updated)
goals_cycle_manager.global_timer_updated.connect(_on_global_timer_updated)
goals_cycle_manager.match_ended.connect(_on_match_ended)
# Message Bar Configuration
const MAX_MESSAGES := 5
@@ -181,6 +184,54 @@ func _setup_obstacle_ui():
)
$ActionMenu/ActionButtonContainer.add_child(type_button)
func _setup_global_match_timer_ui():
"""Create the global match timer display at the top of the screen."""
var existing = get_node_or_null("GlobalMatchTimer")
if existing:
return
# Create timer panel
var panel = PanelContainer.new()
panel.name = "GlobalMatchTimer"
# Position at top center
panel.set_anchors_preset(Control.PRESET_CENTER_TOP)
panel.offset_left = -80
panel.offset_right = 80
panel.offset_top = 10
panel.offset_bottom = 60
# Style
var style = StyleBoxFlat.new()
style.bg_color = Color(0.1, 0.1, 0.15, 0.9)
style.border_width_left = 2
style.border_width_top = 2
style.border_width_right = 2
style.border_width_bottom = 2
style.border_color = Color(0.647, 0.996, 0.224, 0.8)
style.corner_radius_top_left = 8
style.corner_radius_top_right = 8
style.corner_radius_bottom_right = 8
style.corner_radius_bottom_left = 8
panel.add_theme_stylebox_override("panel", style)
# VBox for content
var vbox = VBoxContainer.new()
vbox.name = "VBox"
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
panel.add_child(vbox)
# Label
var label = Label.new()
label.name = "TimerLabel"
label.text = "3:00"
label.add_theme_font_size_override("font_size", 28)
label.add_theme_color_override("font_color", Color(0.647, 0.996, 0.224))
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(label)
add_child(panel)
func _process(delta):
if multiplayer.is_server() and GameStateManager.is_game_started():
if TurnManager.turn_based_mode:
@@ -322,9 +373,10 @@ func _start_game():
var next_player = TurnManager.next_turn(GameStateManager.players)
rpc("set_current_turn", next_player)
# Start the goals cycle timer
# Start the global match timer (this also starts the first cycle)
if goals_cycle_manager:
goals_cycle_manager.start_cycle()
var match_duration = LobbyManager.get_match_duration()
goals_cycle_manager.start_match(float(match_duration))
# Initialize leaderboard with all players
if ui_manager:
@@ -830,6 +882,155 @@ func _on_leaderboard_updated(sorted_scores: Array):
})
rpc("sync_leaderboard_data", player_data)
func _on_global_timer_updated(time_remaining: float):
"""Update the global match timer display."""
var global_timer_panel = get_node_or_null("GlobalMatchTimer")
if global_timer_panel:
var timer_label = global_timer_panel.get_node_or_null("VBox/TimerLabel")
if timer_label:
var minutes = int(time_remaining) / 60
var seconds = int(time_remaining) % 60
timer_label.text = "%d:%02d" % [minutes, seconds]
func _on_match_ended():
"""Called when the global match timer ends - show game over screen."""
print("[Main] Match ended! Showing game over screen...")
# Disable player controls
var local_player = GameStateManager.local_player_character
if local_player:
local_player.action_points = 0
# Show game over overlay
_show_game_over_panel()
func _show_game_over_panel():
"""Create and display the game over panel with final leaderboard."""
# Check if panel already exists
var existing_panel = get_node_or_null("GameOverPanel")
if existing_panel:
existing_panel.show()
return
# Create game over panel
var panel = PanelContainer.new()
panel.name = "GameOverPanel"
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
# Semi-transparent dark background
var style = StyleBoxFlat.new()
style.bg_color = Color(0.0, 0.0, 0.0, 0.85)
panel.add_theme_stylebox_override("panel", style)
# Content container
var vbox = VBoxContainer.new()
vbox.name = "VBox"
vbox.set_anchors_preset(Control.PRESET_CENTER)
vbox.add_theme_constant_override("separation", 20)
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
panel.add_child(vbox)
# Center the vbox
var margin = MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 200)
margin.add_theme_constant_override("margin_right", 200)
margin.add_theme_constant_override("margin_top", 100)
margin.add_theme_constant_override("margin_bottom", 100)
panel.add_child(margin)
var inner_vbox = VBoxContainer.new()
inner_vbox.add_theme_constant_override("separation", 30)
inner_vbox.alignment = BoxContainer.ALIGNMENT_CENTER
margin.add_child(inner_vbox)
# Title
var title = Label.new()
title.text = "⏱️ TIME'S UP!"
title.add_theme_font_size_override("font_size", 64)
title.add_theme_color_override("font_color", Color(0.992, 0.796, 0.047))
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
inner_vbox.add_child(title)
# Subtitle
var subtitle = Label.new()
subtitle.text = "FINAL STANDINGS"
subtitle.add_theme_font_size_override("font_size", 24)
subtitle.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
inner_vbox.add_child(subtitle)
# Leaderboard container
var leaderboard_container = VBoxContainer.new()
leaderboard_container.add_theme_constant_override("separation", 15)
inner_vbox.add_child(leaderboard_container)
# Get final scores
var player_scores = []
for p in get_tree().get_nodes_in_group("Players"):
player_scores.append({
"name": p.name,
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
})
player_scores.sort_custom(func(a, b): return a.score > b.score)
# Display each player
for i in range(min(player_scores.size(), 4)):
var entry = HBoxContainer.new()
entry.add_theme_constant_override("separation", 20)
var rank_colors = [Color(1.0, 0.84, 0.0), Color(0.75, 0.75, 0.75), Color(0.8, 0.5, 0.2), Color(0.5, 0.5, 0.5)]
var rank_emojis = ["🥇", "🥈", "🥉", "4th"]
var rank_label = Label.new()
rank_label.text = rank_emojis[i]
rank_label.add_theme_font_size_override("font_size", 32)
entry.add_child(rank_label)
var name_label = Label.new()
name_label.text = "Player %s" % player_scores[i].name
name_label.add_theme_font_size_override("font_size", 28)
name_label.add_theme_color_override("font_color", rank_colors[i])
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
entry.add_child(name_label)
var score_label = Label.new()
score_label.text = str(player_scores[i].score)
score_label.add_theme_font_size_override("font_size", 28)
score_label.add_theme_color_override("font_color", Color(0.4, 1.0, 0.4))
entry.add_child(score_label)
leaderboard_container.add_child(entry)
# Back to Menu button
var back_btn = Button.new()
back_btn.name = "BackToMenuBtn"
back_btn.text = "BACK TO MAIN MENU"
back_btn.custom_minimum_size = Vector2(300, 60)
back_btn.add_theme_font_size_override("font_size", 20)
back_btn.pressed.connect(_on_back_to_menu_pressed)
inner_vbox.add_child(back_btn)
# Center the button
var btn_container = HBoxContainer.new()
btn_container.alignment = BoxContainer.ALIGNMENT_CENTER
btn_container.add_child(back_btn)
inner_vbox.add_child(btn_container)
add_child(panel)
func _on_back_to_menu_pressed():
"""Return to lobby/main menu and clean up game state."""
print("[Main] Returning to lobby...")
# Clean up game state
GameStateManager.end_game()
LobbyManager.reset()
# Go back to lobby
if get_tree():
get_tree().change_scene_to_file("res://scenes/lobby.tscn")
func _deferred_init_leaderboard():
"""Initialize leaderboard after a delay to ensure all players are loaded."""
# Longer delay ensures players are synced