Add target score setting + victory overlay (animation, victory audio, reset button)
This commit is contained in:
+49
-6
@@ -14,8 +14,8 @@ const VIEW := Vector2(1280, 720)
|
|||||||
const LANE_TOP := 210.0
|
const LANE_TOP := 210.0
|
||||||
const LANE_GAP := 98.0
|
const LANE_GAP := 98.0
|
||||||
const BALL_RADIUS := 40.0
|
const BALL_RADIUS := 40.0
|
||||||
const HOME_X := 1210.0 # alloggiamento destro (a tutta larghezza)
|
const HOME_X := 1180.0 # alloggiamento destro (dentro i bordi)
|
||||||
const LEFT_X := 70.0 # alloggiamento sinistro (a tutta larghezza)
|
const LEFT_X := 100.0 # alloggiamento sinistro (dentro i bordi)
|
||||||
const HIT_RADIUS := BALL_RADIUS * 1.4 # area di tocco/clic (maggiorata)
|
const HIT_RADIUS := BALL_RADIUS * 1.4 # area di tocco/clic (maggiorata)
|
||||||
|
|
||||||
# I valori di calibrazione (velocità, moltiplicatori, decay) sono gestiti dal
|
# I valori di calibrazione (velocità, moltiplicatori, decay) sono gestiti dal
|
||||||
@@ -61,9 +61,13 @@ var sound_launch: AudioStreamWAV
|
|||||||
var sound_error: AudioStreamWAV
|
var sound_error: AudioStreamWAV
|
||||||
var sound_timeout: AudioStreamWAV
|
var sound_timeout: AudioStreamWAV
|
||||||
var sound_milestone: 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 ball_sounds := {} # color_name -> AudioStreamWAV (tono pentatonico)
|
||||||
var bgm: AudioStreamPlayer
|
var bgm: AudioStreamPlayer
|
||||||
var options_menu: CanvasLayer
|
var options_menu: CanvasLayer
|
||||||
|
var lock_menu: CanvasLayer
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -74,6 +78,12 @@ func _ready() -> void:
|
|||||||
_setup_bgm()
|
_setup_bgm()
|
||||||
options_menu = load("res://scripts/options_menu.gd").new()
|
options_menu = load("res://scripts/options_menu.gd").new()
|
||||||
add_child(options_menu)
|
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()
|
_new_game()
|
||||||
|
|
||||||
|
|
||||||
@@ -106,17 +116,22 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
_handle_click(pos)
|
_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:
|
func _open_options() -> void:
|
||||||
if options_menu:
|
if options_menu:
|
||||||
options_menu.show_menu()
|
options_menu.show_menu()
|
||||||
|
|
||||||
|
|
||||||
func _toggle_options() -> void:
|
func _toggle_options() -> void:
|
||||||
if options_menu:
|
if options_menu and options_menu.visible:
|
||||||
if options_menu.visible:
|
|
||||||
options_menu.hide_menu()
|
options_menu.hide_menu()
|
||||||
else:
|
else:
|
||||||
options_menu.show_menu()
|
_request_options()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- costruzione
|
# ---------------------------------------------------------------- costruzione
|
||||||
@@ -206,7 +221,7 @@ func _build_ui() -> void:
|
|||||||
opt_hover.bg_color = Color("#ebcb96")
|
opt_hover.bg_color = Color("#ebcb96")
|
||||||
opts_btn.add_theme_stylebox_override("hover", opt_hover)
|
opts_btn.add_theme_stylebox_override("hover", opt_hover)
|
||||||
opts_btn.add_theme_stylebox_override("pressed", opt_style)
|
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)
|
layer.add_child(opts_btn)
|
||||||
|
|
||||||
# feedback celebrativo alle milestone
|
# 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_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_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)
|
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
|
# tono pentatonico per ogni pallina colorata
|
||||||
for cn in BALL_NOTES:
|
for cn in BALL_NOTES:
|
||||||
ball_sounds[cn] = _create_tone(BALL_NOTES[cn], 0.2, 0.3)
|
ball_sounds[cn] = _create_tone(BALL_NOTES[cn], 0.2, 0.3)
|
||||||
@@ -363,6 +380,7 @@ func _handle_click(pos: Vector2) -> void:
|
|||||||
_pick_target()
|
_pick_target()
|
||||||
_update_hud()
|
_update_hud()
|
||||||
_check_milestone()
|
_check_milestone()
|
||||||
|
_check_victory()
|
||||||
else:
|
else:
|
||||||
# tasto sbagliato: suono morbido, penalità gentile (combo -1) o reset
|
# tasto sbagliato: suono morbido, penalità gentile (combo -1) o reset
|
||||||
_play_sound(sound_error)
|
_play_sound(sound_error)
|
||||||
@@ -449,6 +467,31 @@ func _update_hud() -> void:
|
|||||||
|
|
||||||
# ---------------------------------------------- feedback alle milestone
|
# ---------------------------------------------- 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:
|
func _check_milestone() -> void:
|
||||||
if score < next_milestone:
|
if score < next_milestone:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ func _build() -> void:
|
|||||||
["min_time", "Tempo minimo (s)", 0.3, 3.0, 0.1],
|
["min_time", "Tempo minimo (s)", 0.3, 3.0, 0.1],
|
||||||
["score_ramp", "Punteggio per velocità max", 100.0, 20000.0, 100.0],
|
["score_ramp", "Punteggio per velocità max", 100.0, 20000.0, 100.0],
|
||||||
["points_per_combo", "Punti per combo", 1.0, 50.0, 1.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],
|
["max_combo", "Combo massimo", 1.0, 20.0, 1.0],
|
||||||
["combo_decay_threshold", "Soglia decay combo", 0.0, 30.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],
|
["combo_decay_rate", "Rate decay combo", 0.0, 0.5, 0.01],
|
||||||
|
|||||||
@@ -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 min_time := 0.8 # tempo minimo (velocità massima)
|
||||||
var score_ramp := 8000.0 # punteggio per difficoltà piena (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 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 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_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
|
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)
|
min_time = cfg.get_value("gameplay", "min_time", min_time)
|
||||||
score_ramp = cfg.get_value("gameplay", "score_ramp", score_ramp)
|
score_ramp = cfg.get_value("gameplay", "score_ramp", score_ramp)
|
||||||
points_per_combo = cfg.get_value("gameplay", "points_per_combo", points_per_combo)
|
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)
|
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_threshold = cfg.get_value("gameplay", "combo_decay_threshold", combo_decay_threshold)
|
||||||
combo_decay_rate = cfg.get_value("gameplay", "combo_decay_rate", combo_decay_rate)
|
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", "min_time", min_time)
|
||||||
cfg.set_value("gameplay", "score_ramp", score_ramp)
|
cfg.set_value("gameplay", "score_ramp", score_ramp)
|
||||||
cfg.set_value("gameplay", "points_per_combo", points_per_combo)
|
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", "max_combo", max_combo)
|
||||||
cfg.set_value("gameplay", "combo_decay_threshold", combo_decay_threshold)
|
cfg.set_value("gameplay", "combo_decay_threshold", combo_decay_threshold)
|
||||||
cfg.set_value("gameplay", "combo_decay_rate", combo_decay_rate)
|
cfg.set_value("gameplay", "combo_decay_rate", combo_decay_rate)
|
||||||
@@ -67,6 +70,7 @@ func reset_defaults() -> void:
|
|||||||
min_time = 0.8
|
min_time = 0.8
|
||||||
score_ramp = 8000.0
|
score_ramp = 8000.0
|
||||||
points_per_combo = 1
|
points_per_combo = 1
|
||||||
|
target_score = 1000
|
||||||
max_combo = 5.0
|
max_combo = 5.0
|
||||||
combo_decay_threshold = 0.0
|
combo_decay_threshold = 0.0
|
||||||
combo_decay_rate = 0.15
|
combo_decay_rate = 0.15
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user