Add target score setting + victory overlay (animation, victory audio, reset button)
This commit is contained in:
+51
-8
@@ -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
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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