class_name P1HomeScreen extends Control ## P1: home con i due tendoni/percorsi. signal juggle_requested(focal: Control) signal gym_requested(focal: Control) signal exit_requested signal sign_drop_started signal sign_landed signal sign_bounced const HOME_BG := preload("res://menus/assets/common/1_sfondo.png") const CLOUDS := preload("res://menus/assets/common/1_nuvole.png") const BLUE_TENT := preload("res://menus/assets/p1/1_tendone_1.png") const RED_TENT := preload("res://menus/assets/p1/1_tendone_2.png") const BACK_BUTTON := preload("res://menus/assets/common/pulsante_indietro.png") const SETTINGS_BUTTON := preload("res://menus/assets/common/pulsante_rotellina.png") const CIRCUS_SIGN_PANEL := preload("res://menus/assets/p1/upendi_circus_sign_panel.png") const CIRCUS_SIGN_ROPES := preload("res://menus/assets/p1/upendi_circus_sign_ropes.png") const PLAY_BUTTON := preload("res://menus/assets/common/pulsante_gioca.png") var piedone: Control var piedone_title: Label var piedone_desc: Label var piedone_play: TextureButton var selected_tent: String = "" # "juggle" o "gym" var piedone_tween: Tween var blue_tent_control: Control var red_tent_control: Control # Riferimenti alle animazioni e shader per ciascun tendone var blue_mat: ShaderMaterial var red_mat: ShaderMaterial var blue_curtain_dir: Array = [0] var blue_curtain_timer: Timer var blue_lights: TextureRect var red_curtain_dir: Array = [0] var red_curtain_timer: Timer var red_lights: TextureRect const CURTAIN_FRAMES := [ preload("res://menus/assets/p1/curtain_anim/curtain_00.png"), preload("res://menus/assets/p1/curtain_anim/curtain_01.png"), preload("res://menus/assets/p1/curtain_anim/curtain_02.png"), preload("res://menus/assets/p1/curtain_anim/curtain_03.png"), preload("res://menus/assets/p1/curtain_anim/curtain_04.png"), preload("res://menus/assets/p1/curtain_anim/curtain_05.png"), preload("res://menus/assets/p1/curtain_anim/curtain_06.png"), ] const GARLAND_FRAMES := [ preload("res://menus/assets/p1/garland_anim/lights_00.png"), preload("res://menus/assets/p1/garland_anim/lights_01.png"), preload("res://menus/assets/p1/garland_anim/lights_02.png"), preload("res://menus/assets/p1/garland_anim/lights_03.png"), preload("res://menus/assets/p1/garland_anim/lights_04.png"), preload("res://menus/assets/p1/garland_anim/lights_05.png"), ] func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _build() func _build() -> void: MenuUI.add_full_texture(self, HOME_BG) MenuUI.add_full_texture(self, CLOUDS, MenuUI.ambient_material(1.0, 0.16)) _add_sign() # Costruzione dei due tendoni selezionabili al click (entrambi con luci e sipario) var blue := _add_tent_button(BLUE_TENT, Vector2(0.29, 0.46), "juggle") blue_tent_control = blue blue.pressed.connect(func(): _select_tent("juggle")) var red := _add_tent_button(RED_TENT, Vector2(0.71, 0.46), "gym") red_tent_control = red red.pressed.connect(func(): _select_tent("gym")) _add_bottom_bar() MenuUI.add_image(self, SETTINGS_BUTTON, Vector2(0.91, 0.13), Vector2(0.09, 0.14)) if not OS.has_feature("web"): var exit_button := MenuUI.add_image_button(self, BACK_BUTTON, MenuUI.BACK_BUTTON_CENTER, MenuUI.BACK_BUTTON_SIZE) exit_button.pressed.connect(func(): exit_requested.emit()) func _add_tent_button(texture: Texture2D, center: Vector2, tent_id: String) -> TextureButton: var button := MenuUI.add_image_button(self, texture, center, Vector2(0.36, 0.59)) var mat := MenuUI.tent_material(0.0) button.material = mat button.tooltip_text = "Seleziona questo percorso" if tent_id == "juggle": blue_mat = mat _setup_tent_interactive(button, center, "blue") elif tent_id == "gym": red_mat = mat _setup_tent_interactive(button, center, "red") return button func _setup_tent_interactive(parent_button: TextureButton, center: Vector2, color_id: String) -> void: # Sipario: parte chiuso (frame 0). Alla selezione si apre (0 -> 6). # Alla deselezione si richiude al contrario (6 -> 0). var curtain := TextureRect.new() curtain.name = color_id.capitalize() + "Curtain" curtain.expand_mode = TextureRect.EXPAND_IGNORE_SIZE curtain.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED curtain.mouse_filter = Control.MOUSE_FILTER_IGNORE curtain.texture = CURTAIN_FRAMES[0] add_child(curtain) MenuUI.set_normalized_rect(curtain, center, Vector2(0.36, 0.59)) var curtain_dir := [0] # 0 = fermo, 1 = apre, -1 = chiude var curtain_timer := Timer.new() curtain_timer.name = color_id.capitalize() + "CurtainTimer" curtain_timer.wait_time = 1.0 / 12.5 curtain_timer.process_mode = Node.PROCESS_MODE_ALWAYS var curtain_idx := [0] curtain_timer.timeout.connect(func(): if curtain_dir[0] == 1: if curtain_idx[0] < CURTAIN_FRAMES.size() - 1: curtain_idx[0] += 1 curtain.texture = CURTAIN_FRAMES[curtain_idx[0]] curtain.visible = true else: # Completamente aperto curtain.texture = CURTAIN_FRAMES[curtain_idx[0]] curtain_dir[0] = 0 curtain_timer.stop() elif curtain_dir[0] == -1: if curtain_idx[0] > 0: curtain_idx[0] -= 1 curtain.texture = CURTAIN_FRAMES[curtain_idx[0]] curtain.visible = true else: # Completamente chiuso curtain.texture = CURTAIN_FRAMES[0] curtain_dir[0] = 0 curtain_timer.stop() ) add_child(curtain_timer) # Lucine a inseguimento var lights := TextureRect.new() lights.name = color_id.capitalize() + "Garland" lights.expand_mode = TextureRect.EXPAND_IGNORE_SIZE lights.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED lights.mouse_filter = Control.MOUSE_FILTER_IGNORE lights.texture = GARLAND_FRAMES[0] lights.visible = false add_child(lights) MenuUI.set_normalized_rect(lights, center, Vector2(0.36, 0.59)) var lights_idx := [0] var lights_timer := Timer.new() lights_timer.name = color_id.capitalize() + "GarlandTimer" lights_timer.wait_time = 1.0 / 12.5 lights_timer.process_mode = Node.PROCESS_MODE_ALWAYS lights_timer.timeout.connect(func(): lights_idx[0] = (lights_idx[0] + 1) % GARLAND_FRAMES.size() if lights.visible: lights.texture = GARLAND_FRAMES[lights_idx[0]] ) add_child(lights_timer) lights_timer.start() if color_id == "blue": blue_curtain_dir = curtain_dir blue_curtain_timer = curtain_timer blue_lights = lights elif color_id == "red": red_curtain_dir = curtain_dir red_curtain_timer = curtain_timer red_lights = lights func _select_tent(which: String) -> void: if selected_tent == which: return selected_tent = which # Aggiorna animazioni sui tendoni if which == "juggle": # Attiva tendone blu if blue_mat: blue_mat.set_shader_parameter("hover_strength", 1.0) blue_mat.set_shader_parameter("garland_strength", 1.0) if blue_curtain_dir and blue_curtain_timer: blue_curtain_dir[0] = 1 blue_curtain_timer.start() if blue_lights: blue_lights.visible = true # Spegne e richiude tendone rosso if red_mat: red_mat.set_shader_parameter("hover_strength", 0.0) red_mat.set_shader_parameter("garland_strength", 0.0) if red_curtain_dir and red_curtain_timer: red_curtain_dir[0] = -1 red_curtain_timer.start() if red_lights: red_lights.visible = false elif which == "gym": # Attiva tendone rosso if red_mat: red_mat.set_shader_parameter("hover_strength", 1.0) red_mat.set_shader_parameter("garland_strength", 1.0) if red_curtain_dir and red_curtain_timer: red_curtain_dir[0] = 1 red_curtain_timer.start() if red_lights: red_lights.visible = true # Spegne e richiude tendone blu if blue_mat: blue_mat.set_shader_parameter("hover_strength", 0.0) blue_mat.set_shader_parameter("garland_strength", 0.0) if blue_curtain_dir and blue_curtain_timer: blue_curtain_dir[0] = -1 blue_curtain_timer.start() if blue_lights: blue_lights.visible = false # Transizione del piedone: scende e risale con le nuove info _transition_bottom_bar(which) func _transition_bottom_bar(which: String) -> void: if not is_instance_valid(piedone): return var v_height := get_viewport_rect().size.y var hidden_y := v_height * 0.29 var target_y := 0.0 if is_instance_valid(piedone_tween) and piedone_tween.is_running(): piedone_tween.kill() piedone_tween = create_tween() piedone_tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) # Quando l'utente tocca un tendone, Piedone scende in modo fluido e risale con molleggio morbido e lento piedone_tween.tween_property(piedone, "position:y", hidden_y, 0.40).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) piedone_tween.tween_callback(func(): _update_piedone_content(which)) piedone_tween.tween_property(piedone, "position:y", -v_height * 0.015, 0.70).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) piedone_tween.tween_property(piedone, "position:y", v_height * 0.006, 0.40).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) piedone_tween.tween_property(piedone, "position:y", target_y, 0.35).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) func _update_piedone_content(which: String) -> void: piedone_play.visible = true # Riposiziona il titolo sopra alla descrizione MenuUI.set_normalized_rect(piedone_title, Vector2(0.38, 0.845), Vector2(0.52, 0.065)) piedone_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT if which == "juggle": piedone_title.text = "JUGGLEBOARD" piedone_desc.text = "Allena ritmo e coordinazione con le biglie e i lanci a bersaglio." elif which == "gym": piedone_title.text = "PALESTRA DI GIOCOLERIA" piedone_desc.text = "Scopri gli attrezzi del circo: clave, palline, piatti e rola bola." piedone_desc.visible = true func _on_play_pressed() -> void: if selected_tent == "juggle": juggle_requested.emit(blue_tent_control) elif selected_tent == "gym": gym_requested.emit(red_tent_control) func _add_bottom_bar() -> void: # Piedone parte nascosto e sale all'avvio con testo centrale "Tocca uno dei tendoni" piedone = Control.new() piedone.name = "Piedone" piedone.mouse_filter = Control.MOUSE_FILTER_IGNORE piedone.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(piedone) var bar := ColorRect.new() bar.color = Color("#f1dfbd") bar.mouse_filter = Control.MOUSE_FILTER_IGNORE piedone.add_child(bar) MenuUI.set_normalized_rect(bar, Vector2(0.50, 0.885), Vector2(1.0, 0.23)) for item in [[0.774, 3.0], [0.787, 2.0]]: var rule := ColorRect.new() rule.color = Color("#28201c") rule.mouse_filter = Control.MOUSE_FILTER_IGNORE piedone.add_child(rule) MenuUI.set_normalized_rect(rule, Vector2(0.50, item[0]), Vector2(1.0, item[1] / 720.0)) # Titolo / invito iniziale: allineato armoniosamente nella colonna sinistra piedone_title = MenuUI.add_label(piedone, "Tocca uno dei tendoni", Vector2(0.38, 0.885), Vector2(0.52, 0.09), 26) piedone_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER # Descrizione del gioco: posizionata direttamente sotto al titolo piedone_desc = Label.new() piedone_desc.text = "" piedone_desc.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT piedone_desc.vertical_alignment = VERTICAL_ALIGNMENT_TOP piedone_desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART piedone_desc.add_theme_font_override("font", MenuUI.DISPLAY_FONT) piedone_desc.add_theme_font_size_override("font_size", 17) piedone_desc.add_theme_color_override("font_color", Color("#423832")) piedone_desc.mouse_filter = Control.MOUSE_FILTER_IGNORE piedone_desc.visible = false piedone.add_child(piedone_desc) MenuUI.set_normalized_rect(piedone_desc, Vector2(0.38, 0.925), Vector2(0.52, 0.08)) # Pulsante GIOCA sul lato destro del piedone (coerente con i mockup P1 e P2a) piedone_play = TextureButton.new() piedone_play.ignore_texture_size = true piedone_play.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED piedone_play.texture_normal = PLAY_BUTTON piedone_play.texture_hover = PLAY_BUTTON piedone_play.texture_pressed = PLAY_BUTTON piedone_play.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND piedone_play.mouse_entered.connect(func(): piedone_play.modulate = Color(1.08, 1.08, 1.08, 1.0)) piedone_play.mouse_exited.connect(func(): piedone_play.modulate = Color.WHITE) piedone_play.pressed.connect(_on_play_pressed) piedone_play.visible = false piedone.add_child(piedone_play) MenuUI.set_normalized_rect(piedone_play, Vector2(0.79, 0.885), Vector2(0.26, 0.14)) # Parte nascosto sotto lo schermo e sale con molleggio morbido, lento e disteso var viewport_height := get_viewport_rect().size.y piedone.position.y = viewport_height * 0.29 var intro_tween := create_tween() intro_tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) intro_tween.tween_property(piedone, "position:y", -viewport_height * 0.018, 1.10).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) intro_tween.tween_property(piedone, "position:y", viewport_height * 0.007, 0.45).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) intro_tween.tween_property(piedone, "position:y", 0.0, 0.40).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) func _add_sign() -> void: # Il pannello si muove, mentre le funi restano ancorate al bordo superiore. # Separare i due livelli conserva la texture disegnata originale senza lasciare # un vuoto quando il pannello supera verso il basso il punto di riposo. var rig := Control.new() rig.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(rig) MenuUI.set_normalized_rect(rig, Vector2(0.50, 0.17), Vector2(0.43, 0.34)) _add_sign_layer(rig, CIRCUS_SIGN_ROPES) var panel := _add_sign_layer(rig, CIRCUS_SIGN_PANEL) var sign_mat := MenuUI.sign_lights_material() panel.material = sign_mat var final_y := panel.position.y panel.position.y = final_y - panel.size.y * 1.15 var tween := create_tween() # Il menu viene messo in pausa dalla shell: l'ingresso deve continuare comunque. tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) # Discesa rapida, atterraggio evidente e rimbalzo elastico. call_deferred("_emit_sign_drop_started") tween.tween_property(panel, "position:y", final_y + panel.size.y * 0.11, 1.10).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) # L'impatto รจ sincronizzato con la massima estensione, prima del ritorno elastico. tween.tween_callback(func(): sign_landed.emit()) tween.tween_property(panel, "position:y", final_y, 0.50).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) tween.tween_callback(func(): sign_bounced.emit() # Attiva le lucine colorate dell'insegna con una dolce dissolvenza solo a fine animazione var lights_tween := create_tween() lights_tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) lights_tween.tween_method(func(val: float): sign_mat.set_shader_parameter("sign_lights_active", val), 0.0, 1.0, 0.6).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) ) func _add_sign_layer(parent: Control, texture: Texture2D) -> TextureRect: var layer := TextureRect.new() layer.texture = texture layer.expand_mode = TextureRect.EXPAND_IGNORE_SIZE layer.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED layer.mouse_filter = Control.MOUSE_FILTER_IGNORE parent.add_child(layer) layer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) return layer func _emit_sign_drop_started() -> void: if is_inside_tree(): sign_drop_started.emit()