feat: add battlepass slider and quest list tabs to lobby HUD

This commit is contained in:
2026-07-09 18:25:07 +08:00
parent 114748a54f
commit 933119ca56
99 changed files with 3861 additions and 417 deletions
+142 -1
View File
@@ -12,6 +12,8 @@ extends Control
@onready var avatar_display = %AvatarDisplay
@onready var lobby_settings_btn = %SettingsBtn
@onready var quit_btn = %QuitBtn
@onready var chat_toggle_btn = get_node_or_null("%BubbleBtn")
@onready var chat_panel_container = $MainMenuPanel/MainMargin/MainHBox/LeftCol/ChatPanel
# Main Menu 3D Preview
@onready var character_root = %CharacterRoot
@@ -158,6 +160,29 @@ var _friend_suggest_list: ItemList
# var server_option: OptionButton
# var server_ip_input: LineEdit
# =============================================================================
# Menu Button Animation (ROOM / PLAY hover + press juice)
# =============================================================================
@export_group("Menu Button Animation")
@export var menu_btn_hover_scale: float = 1.06
@export var menu_btn_press_scale: float = 0.94
@export var menu_btn_hover_duration: float = 0.15
@export var menu_btn_press_duration: float = 0.08
@export var menu_btn_release_duration: float = 0.2
@export var menu_btn_hover_trans: Tween.TransitionType = Tween.TRANS_BACK
@export var menu_btn_hover_ease: Tween.EaseType = Tween.EASE_OUT
@export var menu_btn_hover_brighten: float = 1.08
@export_group("Card Fan Idle Animation")
@export var card_fan_idle_enabled: bool = true
@export var card_fan_jump_height: float = 14.0
@export var card_fan_jump_duration: float = 0.35
@export var card_fan_jump_stagger: float = 0.15
@export var card_fan_cycle_pause: float = 14.0
var _button_tweens: Dictionary = {}
var _card_fan_tweens: Dictionary = {}
func _ready():
chat = LobbyChat.new(self)
@@ -182,7 +207,10 @@ func _ready():
# Initial UI update
_sync_room_profile_card()
# ROOM / PLAY button hover, press, and card-fan idle animation
_setup_menu_button_animations()
# Connect Social / Friend UI
var global_chat_tab_btn = get_node_or_null("%GlobalChatTabBtn")
if global_chat_tab_btn:
@@ -401,6 +429,119 @@ func _connect_slot_signals(slot: Control, i: int):
if right_btn:
right_btn.pressed.connect(func(): LobbyManager.cycle_character(1))
# =============================================================================
# Menu Button Animation (ROOM / PLAY)
# =============================================================================
func _setup_menu_button_animations() -> void:
for btn in [browse_rooms_btn, create_room_btn]:
if not btn:
continue
btn.pivot_offset = btn.size / 2.0
btn.mouse_entered.connect(_on_menu_btn_mouse_entered.bind(btn))
btn.mouse_exited.connect(_on_menu_btn_mouse_exited.bind(btn))
btn.button_down.connect(_on_menu_btn_down.bind(btn))
btn.button_up.connect(_on_menu_btn_up.bind(btn))
_start_card_fan_idle(get_node_or_null("MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel/RoomBtnRoot"))
_start_card_fan_idle(get_node_or_null("MainMenuPanel/MainMargin/MainHBox/RightCol/BottomRightPanel/PlayBtnRoot"))
if chat_toggle_btn:
chat_toggle_btn.pressed.connect(_on_chat_toggle_pressed)
func _on_chat_toggle_pressed() -> void:
if chat_panel_container:
chat_panel_container.visible = not chat_panel_container.visible
func _kill_button_tween(btn: Control) -> void:
if _button_tweens.has(btn) and is_instance_valid(_button_tweens[btn]):
_button_tweens[btn].kill()
func _tween_button_scale(btn: Control, target_scale: float, duration: float, use_overshoot: bool = false) -> void:
_kill_button_tween(btn)
var tween := create_tween()
if use_overshoot:
tween.set_trans(menu_btn_hover_trans).set_ease(menu_btn_hover_ease)
else:
tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
tween.tween_property(btn, "scale", Vector2(target_scale, target_scale), duration)
_button_tweens[btn] = tween
func _on_menu_btn_mouse_entered(btn: Control) -> void:
if btn.disabled:
return
_tween_button_scale(btn, menu_btn_hover_scale, menu_btn_hover_duration, true)
btn.self_modulate = Color(menu_btn_hover_brighten, menu_btn_hover_brighten, menu_btn_hover_brighten, 1.0)
func _on_menu_btn_mouse_exited(btn: Control) -> void:
_tween_button_scale(btn, 1.0, menu_btn_release_duration)
btn.self_modulate = Color.WHITE
func _on_menu_btn_down(btn: Control) -> void:
_tween_button_scale(btn, menu_btn_press_scale, menu_btn_press_duration)
func _on_menu_btn_up(btn: Control) -> void:
var target := menu_btn_hover_scale if btn.get_global_rect().has_point(get_global_mouse_position()) else 1.0
_tween_button_scale(btn, target, menu_btn_release_duration, true)
func _start_card_fan_idle(root: Control) -> void:
if not root or not card_fan_idle_enabled:
return
# find_child (recursive) instead of a direct get_node_or_null so this still
# finds the cards even if they end up nested under a layout wrapper (e.g. a
# PanelContainer) instead of being direct children of root.
var cards: Array[Control] = []
for card_name in ["CardLeft", "CardRight"]:
var card := root.find_child(card_name, true, false) as Control
if card:
cards.append(card)
if cards.is_empty():
return
if _card_fan_tweens.has(root):
for old_tween in _card_fan_tweens[root]:
if is_instance_valid(old_tween):
old_tween.kill()
# Sequential "leapfrog" jump: each card jumps in turn, staggered by
# card_fan_jump_stagger, then the whole row pauses before looping.
var cycle_total: float = cards.size() * card_fan_jump_stagger + card_fan_cycle_pause
var tweens: Array[Tween] = []
for i in cards.size():
var card := cards[i]
var start_y := card.position.y
var lead_in: float = i * card_fan_jump_stagger
var trailing: float = cycle_total - lead_in - card_fan_jump_duration
var tween := create_tween().set_loops()
if lead_in > 0.0:
tween.tween_interval(lead_in)
tween.tween_property(card, "position:y", start_y - card_fan_jump_height, card_fan_jump_duration * 0.5).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tween.tween_property(card, "position:y", start_y, card_fan_jump_duration * 0.5).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
if trailing > 0.0:
tween.tween_interval(trailing)
tweens.append(tween)
_card_fan_tweens[root] = tweens
func _exit_tree() -> void:
for tween in _button_tweens.values():
if is_instance_valid(tween):
tween.kill()
for tweens in _card_fan_tweens.values():
for tween in tweens:
if is_instance_valid(tween):
tween.kill()
# =============================================================================
# Panel Management
# =============================================================================