From 44160adc1a600bdcf0a3b91f1ea713a16b141c1b Mon Sep 17 00:00:00 2001 From: enne2 Date: Tue, 18 Aug 2026 18:45:21 +0200 Subject: [PATCH] Add target score setting + victory overlay (animation, victory audio, reset button) --- scripts/main.gd | 59 +++++++++++++++++++++---- scripts/options_menu.gd | 1 + scripts/settings.gd | 4 ++ scripts/victory_overlay.gd | 88 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 scripts/victory_overlay.gd diff --git a/scripts/main.gd b/scripts/main.gd index cc053e8..900f708 100644 --- a/scripts/main.gd +++ b/scripts/main.gd @@ -14,8 +14,8 @@ const VIEW := Vector2(1280, 720) const LANE_TOP := 210.0 const LANE_GAP := 98.0 const BALL_RADIUS := 40.0 -const HOME_X := 1210.0 # alloggiamento destro (a tutta larghezza) -const LEFT_X := 70.0 # alloggiamento sinistro (a tutta larghezza) +const HOME_X := 1180.0 # alloggiamento destro (dentro i bordi) +const LEFT_X := 100.0 # alloggiamento sinistro (dentro i bordi) const HIT_RADIUS := BALL_RADIUS * 1.4 # area di tocco/clic (maggiorata) # I valori di calibrazione (velocità, moltiplicatori, decay) sono gestiti dal @@ -61,9 +61,13 @@ var sound_launch: AudioStreamWAV var sound_error: AudioStreamWAV var sound_timeout: AudioStreamWAV var sound_milestone: AudioStreamWAV +var sound_victory: AudioStreamWAV +var victory_layer: CanvasLayer +var victory_shown := false var ball_sounds := {} # color_name -> AudioStreamWAV (tono pentatonico) var bgm: AudioStreamPlayer var options_menu: CanvasLayer +var lock_menu: CanvasLayer func _ready() -> void: @@ -74,6 +78,12 @@ func _ready() -> void: _setup_bgm() options_menu = load("res://scripts/options_menu.gd").new() add_child(options_menu) + lock_menu = load("res://scripts/lock_menu.gd").new() + lock_menu.unlocked.connect(_open_options) + add_child(lock_menu) + victory_layer = load("res://scripts/victory_overlay.gd").new() + victory_layer.reset_requested.connect(_reset_after_victory) + add_child(victory_layer) _new_game() @@ -106,17 +116,22 @@ func _unhandled_input(event: InputEvent) -> void: _handle_click(pos) +func _request_options() -> void: + # Le Opzioni sono protette: prima la sequenza di sblocco (per adulti) + if lock_menu: + lock_menu.show_menu() + + func _open_options() -> void: if options_menu: options_menu.show_menu() func _toggle_options() -> void: - if options_menu: - if options_menu.visible: - options_menu.hide_menu() - else: - options_menu.show_menu() + if options_menu and options_menu.visible: + options_menu.hide_menu() + else: + _request_options() # ---------------------------------------------------------------- costruzione @@ -206,7 +221,7 @@ func _build_ui() -> void: opt_hover.bg_color = Color("#ebcb96") opts_btn.add_theme_stylebox_override("hover", opt_hover) opts_btn.add_theme_stylebox_override("pressed", opt_style) - opts_btn.pressed.connect(_open_options) + opts_btn.pressed.connect(_request_options) layer.add_child(opts_btn) # feedback celebrativo alle milestone @@ -239,6 +254,8 @@ func _init_audio() -> void: sound_error = _create_tone(220.0, 0.15, 0.16) # "pop" basso e attenuato sound_timeout = _create_tone(330.0, 0.2, 0.16) # tono dolce sound_milestone = _create_soft_arpeggio([523.25, 659.25, 783.99, 1046.5], 0.12) + # audio di vittoria: arpeggio ascendente luminoso ma morbido (sensory-friendly) + sound_victory = _create_soft_arpeggio([523.25, 659.25, 783.99, 1046.5, 1318.51, 1567.98], 0.16) # tono pentatonico per ogni pallina colorata for cn in BALL_NOTES: ball_sounds[cn] = _create_tone(BALL_NOTES[cn], 0.2, 0.3) @@ -363,6 +380,7 @@ func _handle_click(pos: Vector2) -> void: _pick_target() _update_hud() _check_milestone() + _check_victory() else: # tasto sbagliato: suono morbido, penalità gentile (combo -1) o reset _play_sound(sound_error) @@ -449,6 +467,31 @@ func _update_hud() -> void: # ---------------------------------------------- feedback alle milestone +func _check_victory() -> void: + if not victory_shown and score >= Settings.target_score: + _on_victory() + + +func _on_victory() -> void: + if victory_shown: + return + victory_shown = true + # ferma la musica di sottofondo e riproduce l'audio di vittoria + bgm.stop() + _play_sound(sound_victory) + # congela il gioco e mostra l'overlay di vittoria (animazione inclusa) + get_tree().paused = true + victory_layer.show_victory(score) + + +func _reset_after_victory() -> void: + get_tree().paused = false + victory_shown = false + victory_layer.hide_victory() + bgm.play() + _new_game() + + func _check_milestone() -> void: if score < next_milestone: return diff --git a/scripts/options_menu.gd b/scripts/options_menu.gd index bd8204a..93de120 100644 --- a/scripts/options_menu.gd +++ b/scripts/options_menu.gd @@ -53,6 +53,7 @@ func _build() -> void: ["min_time", "Tempo minimo (s)", 0.3, 3.0, 0.1], ["score_ramp", "Punteggio per velocità max", 100.0, 20000.0, 100.0], ["points_per_combo", "Punti per combo", 1.0, 50.0, 1.0], + ["target_score", "Punteggio vittoria", 100.0, 50000.0, 100.0], ["max_combo", "Combo massimo", 1.0, 20.0, 1.0], ["combo_decay_threshold", "Soglia decay combo", 0.0, 30.0, 1.0], ["combo_decay_rate", "Rate decay combo", 0.0, 0.5, 0.01], diff --git a/scripts/settings.gd b/scripts/settings.gd index f5acd2c..ff1afd3 100644 --- a/scripts/settings.gd +++ b/scripts/settings.gd @@ -11,6 +11,7 @@ var start_time := 4.5 # tempo iniziale (s) tra un ordine e l'altro var min_time := 0.8 # tempo minimo (velocità massima) var score_ramp := 8000.0 # punteggio per difficoltà piena (velocità massima) var points_per_combo := 1 # punti per ogni lancio corretto (lineare, default 1) +var target_score := 1000 # punteggio da raggiungere per la vittoria var max_combo := 5.0 # combo massimo di default (configurabile) var combo_decay_threshold := 0.0 # combo sopra cui inizia il decay (0 = da subito) var combo_decay_rate := 0.15 # combo persi al secondo per unità sopra la soglia @@ -38,6 +39,7 @@ func load_settings() -> void: min_time = cfg.get_value("gameplay", "min_time", min_time) score_ramp = cfg.get_value("gameplay", "score_ramp", score_ramp) points_per_combo = cfg.get_value("gameplay", "points_per_combo", points_per_combo) + target_score = cfg.get_value("gameplay", "target_score", target_score) max_combo = cfg.get_value("gameplay", "max_combo", max_combo) combo_decay_threshold = cfg.get_value("gameplay", "combo_decay_threshold", combo_decay_threshold) combo_decay_rate = cfg.get_value("gameplay", "combo_decay_rate", combo_decay_rate) @@ -53,6 +55,7 @@ func save_settings() -> void: cfg.set_value("gameplay", "min_time", min_time) cfg.set_value("gameplay", "score_ramp", score_ramp) cfg.set_value("gameplay", "points_per_combo", points_per_combo) + cfg.set_value("gameplay", "target_score", target_score) cfg.set_value("gameplay", "max_combo", max_combo) cfg.set_value("gameplay", "combo_decay_threshold", combo_decay_threshold) cfg.set_value("gameplay", "combo_decay_rate", combo_decay_rate) @@ -67,6 +70,7 @@ func reset_defaults() -> void: min_time = 0.8 score_ramp = 8000.0 points_per_combo = 1 + target_score = 1000 max_combo = 5.0 combo_decay_threshold = 0.0 combo_decay_rate = 0.15 diff --git a/scripts/victory_overlay.gd b/scripts/victory_overlay.gd new file mode 100644 index 0000000..98cfc54 --- /dev/null +++ b/scripts/victory_overlay.gd @@ -0,0 +1,88 @@ +extends CanvasLayer +## Overlay di vittoria: appare quando si raggiunge il punteggio target. +## Animazione di comparsa, mostra il punteggio e un pulsante per rigiocare. + +signal reset_requested # emesso quando l'utente preme "Rigioca" + +var title_label: Label +var score_label: Label + + +func _ready() -> void: + process_mode = Node.PROCESS_MODE_ALWAYS # funziona anche a gioco in pausa + visible = false + _build() + + +func _build() -> void: + var dim := ColorRect.new() + dim.color = Color(0.05, 0.02, 0.02, 0.72) + dim.set_anchors_preset(Control.PRESET_FULL_RECT) + dim.mouse_filter = Control.MOUSE_FILTER_STOP + add_child(dim) + + var center := CenterContainer.new() + center.set_anchors_preset(Control.PRESET_FULL_RECT) + add_child(center) + + var panel := PanelContainer.new() + center.add_child(panel) + var margin := MarginContainer.new() + margin.add_theme_constant_override("margin_left", 48) + margin.add_theme_constant_override("margin_right", 48) + margin.add_theme_constant_override("margin_top", 32) + margin.add_theme_constant_override("margin_bottom", 32) + panel.add_child(margin) + + var vb := VBoxContainer.new() + vb.add_theme_constant_override("separation", 18) + vb.alignment = BoxContainer.ALIGNMENT_CENTER + margin.add_child(vb) + + title_label = Label.new() + title_label.text = "VITTORIA!" + title_label.add_theme_font_size_override("font_size", 64) + title_label.add_theme_color_override("font_color", Color("#f6c445")) + title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + vb.add_child(title_label) + + var subtitle := Label.new() + subtitle.text = "Hai raggiunto il punteggio target!" + subtitle.add_theme_font_size_override("font_size", 22) + subtitle.add_theme_color_override("font_color", Color(0.15, 0.1, 0.05)) + subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + vb.add_child(subtitle) + + score_label = Label.new() + score_label.add_theme_font_size_override("font_size", 36) + score_label.add_theme_color_override("font_color", Color(0.1, 0.07, 0.04)) + score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + vb.add_child(score_label) + + var reset := Button.new() + reset.text = "🔄 Rigioca" + reset.custom_minimum_size = Vector2(220, 64) + reset.add_theme_font_size_override("font_size", 26) + reset.pressed.connect(func(): reset_requested.emit()) + vb.add_child(reset) + + +func show_victory(score: int) -> void: + score_label.text = "Punteggio: %d" % score + visible = true + # animazione di comparsa chiara: il titolo "salta" e le label si fanno strada + title_label.pivot_offset = title_label.size * 0.5 + title_label.scale = Vector2(0.4, 0.4) + title_label.modulate.a = 0.0 + score_label.modulate.a = 0.0 + var tw := create_tween() + tw.set_parallel(true) + tw.tween_property(title_label, "scale", Vector2(1.15, 1.15), 0.35) \ + .set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) + tw.tween_property(title_label, "modulate:a", 1.0, 0.25) + tw.tween_property(score_label, "modulate:a", 1.0, 0.5).set_delay(0.25) + tw.chain().tween_property(title_label, "scale", Vector2(1.0, 1.0), 0.15) + + +func hide_victory() -> void: + visible = false