Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54619a2385 | |||
| f9837153a0 | |||
| e20482676f | |||
| c7918d9f55 |
@@ -1,5 +1,13 @@
|
||||

|
||||
|
||||
# JuggleBoard Game
|
||||
|
||||
Un gioco arcade **reaction & chaining** per dispositivi mobili (orizzontale), realizzato in **Godot 4.7** (GDScript).
|
||||
|
||||
**Prodotto per la [Cooperativa Sociale UPENDI](https://www.upendi.it/)** (Gravina in Puglia) — Pedagogia del Circo e Circo Sociale.
|
||||
|
||||
> Il logo UPENDI è stato generato con l'API Gemini.
|
||||
|
||||
Un gioco arcade **reaction & chaining** per dispositivi mobili (orizzontale), realizzato in **Godot 4.7** (GDScript), ispirato al prodotto riabilitativo "Juggle Board".
|
||||
|
||||
## Gameplay
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 510 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cew543durakby"
|
||||
path="res://.godot/imported/boot_splash.png-8f633205b8d0e7391635b9ec17b0cd20.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://branding/boot_splash.png"
|
||||
dest_files=["res://.godot/imported/boot_splash.png-8f633205b8d0e7391635b9ec17b0cd20.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dhlb2eosjbair"
|
||||
path="res://.godot/imported/upendi_avatar.png-48ff18b41ca3a4a18bc21bb70b9da5b2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://branding/upendi_avatar.png"
|
||||
dest_files=["res://.godot/imported/upendi_avatar.png-48ff18b41ca3a4a18bc21bb70b9da5b2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 798 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://4shhqux1km4c"
|
||||
path="res://.godot/imported/upendi_logo.png-4e597672e26c4524fe84d6f00a5d9c10.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://branding/upendi_logo.png"
|
||||
dest_files=["res://.godot/imported/upendi_logo.png-4e597672e26c4524fe84d6f00a5d9c10.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cf6ae642ot6du"
|
||||
path="res://.godot/imported/upendi_logo_transparent.png-a8de36b73118f430d663844359491d71.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://branding/upendi_logo_transparent.png"
|
||||
dest_files=["res://.godot/imported/upendi_logo_transparent.png-a8de36b73118f430d663844359491d71.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
+6
-1
@@ -4,9 +4,14 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="JuggleBoard"
|
||||
config/description="Gioco arcade JuggleBoard per la Cooperativa Sociale UPENDI (Pedagogia del Circo)"
|
||||
run/main_scene="res://scenes/main.tscn"
|
||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
config/icon="res://branding/upendi_logo.png"
|
||||
boot_splash/bg_color=Color(0.0980392, 0.34902, 0.427451, 1)
|
||||
boot_splash/image="res://branding/boot_splash.png"
|
||||
boot_splash/show_image=true
|
||||
boot_splash/show_logo=false
|
||||
|
||||
[autoload]
|
||||
|
||||
|
||||
+55
-37
@@ -29,6 +29,16 @@ const COLORS := {
|
||||
"viola": Color("9b59b6"),
|
||||
}
|
||||
|
||||
# Note pentatoniche (Do4 Re4 Mi4 Sol4 La4): qualsiasi pallina toccata
|
||||
# produce sempre un suono armonico e piacevole (sensory-friendly).
|
||||
const BALL_NOTES := {
|
||||
"rosso": 261.63,
|
||||
"blu": 293.66,
|
||||
"verde": 329.63,
|
||||
"giallo": 392.00,
|
||||
"viola": 440.00,
|
||||
}
|
||||
|
||||
var balls := {} # color_name -> Ball
|
||||
var target_color := "" # bersaglio corrente (vuoto = nessun bersaglio)
|
||||
var score := 0
|
||||
@@ -51,6 +61,7 @@ var sound_launch: AudioStreamWAV
|
||||
var sound_error: AudioStreamWAV
|
||||
var sound_timeout: AudioStreamWAV
|
||||
var sound_milestone: AudioStreamWAV
|
||||
var ball_sounds := {} # color_name -> AudioStreamWAV (tono pentatonico)
|
||||
var bgm: AudioStreamPlayer
|
||||
var options_menu: CanvasLayer
|
||||
|
||||
@@ -70,6 +81,8 @@ func _process(delta: float) -> void:
|
||||
elapsed += delta
|
||||
if target_color == "":
|
||||
return # in attesa che una pallina torni a casa: nessun countdown
|
||||
if Settings.zen_mode:
|
||||
return # modalità zen: nessun countdown né decay (niente pressione)
|
||||
# il combo decade in proporzione al tempo che aspetto prima di cliccare
|
||||
if combo > 0.0:
|
||||
combo = maxf(0.0, combo - _combo_decay() * delta)
|
||||
@@ -214,10 +227,15 @@ func _build_ui() -> void:
|
||||
# ------------------------------------------------------------------- audio
|
||||
|
||||
func _init_audio() -> void:
|
||||
sound_launch = _create_tone(587.33, 0.12, "sine") # Re5
|
||||
sound_error = _create_tone(164.81, 0.18, "square") # Mi3 basso
|
||||
sound_timeout = _create_tone(220.0, 0.22, "saw") # La3
|
||||
sound_milestone = _create_arpeggio([523.25, 659.25, 783.99, 1046.5], 0.12) # Do5 Mi5 Sol5 Do6
|
||||
# Suoni MORBIDI (sine, inviluppo dolce) per non sovrastimolare i bambini
|
||||
# neurodivergenti: niente onde quadre/sega, volumi attenuati.
|
||||
sound_launch = _create_tone(660.0, 0.18, 0.3) # marimba morbido
|
||||
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)
|
||||
# tono pentatonico per ogni pallina colorata
|
||||
for cn in BALL_NOTES:
|
||||
ball_sounds[cn] = _create_tone(BALL_NOTES[cn], 0.2, 0.3)
|
||||
|
||||
|
||||
func _setup_bgm() -> void:
|
||||
@@ -236,7 +254,7 @@ func _setup_bgm() -> void:
|
||||
bgm.play()
|
||||
|
||||
|
||||
func _create_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
|
||||
func _create_soft_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
|
||||
var sample_rate := 22050
|
||||
var total := 0
|
||||
for f in freqs:
|
||||
@@ -244,13 +262,17 @@ func _create_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
|
||||
var data := PackedByteArray()
|
||||
data.resize(total)
|
||||
var idx := 0
|
||||
var attack := int(sample_rate * 0.01)
|
||||
for f in freqs:
|
||||
var n := int(sample_rate * note_dur)
|
||||
for i in n:
|
||||
var t := float(i) / sample_rate
|
||||
var env := 1.0 - (float(i) / n)
|
||||
var env := 1.0
|
||||
if i < attack:
|
||||
env = float(i) / attack
|
||||
env *= exp(-3.0 * t / note_dur)
|
||||
var s := sin(t * f * TAU) * env
|
||||
var val := int(clampf(s * 0.35 * 127.0, -128.0, 127.0))
|
||||
var val := int(clampf(s * 0.3 * 127.0, -128.0, 127.0))
|
||||
data[idx] = val & 0xFF
|
||||
idx += 1
|
||||
var stream := AudioStreamWAV.new()
|
||||
@@ -260,22 +282,22 @@ func _create_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
|
||||
return stream
|
||||
|
||||
|
||||
func _create_tone(freq: float, duration: float, wave_type: String = "sine") -> AudioStreamWAV:
|
||||
func _create_tone(freq: float, duration: float, amp: float = 0.3) -> AudioStreamWAV:
|
||||
# Onda sinusoidale pura con attacco morbido e decadimento esponenziale:
|
||||
# timbro dolce, senza clic né armoniche spigolose.
|
||||
var sample_rate := 22050
|
||||
var num_samples := int(sample_rate * duration)
|
||||
var data := PackedByteArray()
|
||||
data.resize(num_samples)
|
||||
var attack := int(sample_rate * 0.01)
|
||||
for i in num_samples:
|
||||
var t := float(i) / sample_rate
|
||||
var env := 1.0 - (float(i) / num_samples)
|
||||
var s := 0.0
|
||||
if wave_type == "sine":
|
||||
s = sin(t * freq * TAU)
|
||||
elif wave_type == "square":
|
||||
s = 1.0 if sin(t * freq * TAU) > 0.0 else -1.0
|
||||
elif wave_type == "saw":
|
||||
s = fmod(t * freq, 1.0) * 2.0 - 1.0
|
||||
var val := int(clampf(s * env * 0.35 * 127.0, -128.0, 127.0))
|
||||
var env := 1.0
|
||||
if i < attack:
|
||||
env = float(i) / attack
|
||||
env *= exp(-3.0 * t / duration)
|
||||
var s := sin(t * freq * TAU)
|
||||
var val := int(clampf(s * env * amp * 127.0, -128.0, 127.0))
|
||||
data[i] = val & 0xFF
|
||||
var stream := AudioStreamWAV.new()
|
||||
stream.format = AudioStreamWAV.FORMAT_8_BITS
|
||||
@@ -324,10 +346,10 @@ func _handle_click(pos: Vector2) -> void:
|
||||
return # click su spazio vuoto
|
||||
|
||||
if clicked.color_name == target_color:
|
||||
# corretto: suono lancio, punti, nuovo bersaglio, reset timer
|
||||
combo += 1.0
|
||||
# corretto: suono pentatonico della pallina, punti, nuovo bersaglio
|
||||
combo = minf(float(Settings.max_combo), combo + 1.0)
|
||||
score += _points_for_combo()
|
||||
_play_sound(sound_launch)
|
||||
_play_sound(ball_sounds.get(clicked.color_name, sound_launch))
|
||||
var mt := _current_time()
|
||||
clicked.launch(mt)
|
||||
window_time = mt
|
||||
@@ -336,15 +358,21 @@ func _handle_click(pos: Vector2) -> void:
|
||||
_update_hud()
|
||||
_check_milestone()
|
||||
else:
|
||||
# tasto sbagliato: suono errore, reset combo, nessun game over
|
||||
# tasto sbagliato: suono morbido, penalità gentile (combo -1) o reset
|
||||
_play_sound(sound_error)
|
||||
if Settings.gentle_errors:
|
||||
combo = maxf(0.0, combo - 1.0)
|
||||
else:
|
||||
combo = 0.0
|
||||
_update_hud()
|
||||
|
||||
|
||||
func _on_timeout() -> void:
|
||||
# tempo scaduto: suono timeout, reset combo, nuovo bersaglio (se possibile), nessun game over
|
||||
# tempo scaduto: suono dolce, penalità gentile, nuovo bersaglio, nessun game over
|
||||
_play_sound(sound_timeout)
|
||||
if Settings.gentle_errors:
|
||||
combo = maxf(0.0, combo - 1.0)
|
||||
else:
|
||||
combo = 0.0
|
||||
window_timer = 0.0
|
||||
_pick_target()
|
||||
@@ -363,11 +391,9 @@ func _current_time() -> float:
|
||||
|
||||
|
||||
func _difficulty() -> float:
|
||||
# fattori lineari di tempo e punteggio
|
||||
var tf := clampf(elapsed / Settings.time_ramp, 0.0, 1.0)
|
||||
# La difficoltà (e quindi la velocità massima) è legata unicamente al punteggio
|
||||
var sf := clampf(score / Settings.score_ramp, 0.0, 1.0)
|
||||
# curva dolce: smoothstep rende l'accelerazione quasi impercettibile all'inizio
|
||||
return _smoothstep(maxf(tf, sf))
|
||||
return _smoothstep(sf)
|
||||
|
||||
|
||||
func _combo_decay() -> float:
|
||||
@@ -379,17 +405,9 @@ func _combo_decay() -> float:
|
||||
|
||||
|
||||
func _points_for_combo() -> int:
|
||||
# Punti per un tap corretto, in base alla curva selezionata nelle Opzioni.
|
||||
match Settings.score_curve:
|
||||
1: # scaglioni: +0.5x ogni 5 tap di combo
|
||||
var mult := 1.0 + floori(combo / 5.0) * 0.5
|
||||
return int(Settings.points_per_combo * mult)
|
||||
2: # radice quadrata
|
||||
return int(Settings.points_per_combo * sqrt(combo))
|
||||
3: # additiva: base + combo
|
||||
return Settings.points_per_combo + int(combo)
|
||||
_: # 0 = quadratica (attuale): base × combo
|
||||
return Settings.points_per_combo * int(combo)
|
||||
# Punti per lancio = combo corrente × valore punto configurato.
|
||||
# Es. combo 5 × 1 = 5 pt; combo 1 × 1 = 1 pt. Il valore punto è configurabile.
|
||||
return int(combo) * Settings.points_per_combo
|
||||
|
||||
|
||||
func _smoothstep(x: float) -> float:
|
||||
|
||||
+24
-24
@@ -4,7 +4,8 @@ extends CanvasLayer
|
||||
## scena applicando i nuovi valori.
|
||||
|
||||
var rows := {} # key -> {slider, spin}
|
||||
var curve_option: OptionButton
|
||||
var zen_cb: CheckButton
|
||||
var gentle_cb: CheckButton
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -50,9 +51,9 @@ func _build() -> void:
|
||||
var entries := [
|
||||
["start_time", "Tempo iniziale (s)", 1.0, 10.0, 0.1],
|
||||
["min_time", "Tempo minimo (s)", 0.3, 3.0, 0.1],
|
||||
["time_ramp", "Rampa tempo (s)", 30.0, 600.0, 10.0],
|
||||
["score_ramp", "Rampa punteggio", 100.0, 10000.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],
|
||||
["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],
|
||||
["difficulty_decay_rate", "Rate decay difficoltà", 0.0, 2.0, 0.05],
|
||||
@@ -60,23 +61,18 @@ func _build() -> void:
|
||||
for e in entries:
|
||||
rows[e[0]] = _build_row(vb, e[0], e[1], e[2], e[3], e[4])
|
||||
|
||||
# selettore curva punteggio
|
||||
var ccurve := HBoxContainer.new()
|
||||
ccurve.add_theme_constant_override("separation", 6)
|
||||
vb.add_child(ccurve)
|
||||
var clbl := Label.new()
|
||||
clbl.text = "Curva punteggio"
|
||||
clbl.custom_minimum_size.x = 230
|
||||
ccurve.add_child(clbl)
|
||||
curve_option = OptionButton.new()
|
||||
curve_option.custom_minimum_size.x = 360
|
||||
curve_option.add_item("Quadratica (attuale)")
|
||||
curve_option.add_item("A scaglioni (ogni 5 tap)")
|
||||
curve_option.add_item("Radice quadrata")
|
||||
curve_option.add_item("Additiva (base + combo)")
|
||||
curve_option.select(Settings.score_curve)
|
||||
curve_option.item_selected.connect(func(idx: int): Settings.score_curve = idx)
|
||||
ccurve.add_child(curve_option)
|
||||
# opzioni accessibilità / neurodivergenza
|
||||
zen_cb = CheckButton.new()
|
||||
zen_cb.text = "Modalità Zen (senza tempo)"
|
||||
zen_cb.button_pressed = Settings.zen_mode
|
||||
zen_cb.toggled.connect(func(on: bool): Settings.zen_mode = on)
|
||||
vb.add_child(zen_cb)
|
||||
|
||||
gentle_cb = CheckButton.new()
|
||||
gentle_cb.text = "Errori gentili (combo -1)"
|
||||
gentle_cb.button_pressed = Settings.gentle_errors
|
||||
gentle_cb.toggled.connect(func(on: bool): Settings.gentle_errors = on)
|
||||
vb.add_child(gentle_cb)
|
||||
|
||||
# pulsanti
|
||||
var hb := HBoxContainer.new()
|
||||
@@ -140,8 +136,10 @@ func show_menu() -> void:
|
||||
var val: float = Settings.get(key)
|
||||
rows[key].slider.value = val
|
||||
rows[key].spin.value = val
|
||||
if curve_option:
|
||||
curve_option.select(Settings.score_curve)
|
||||
if zen_cb:
|
||||
zen_cb.button_pressed = Settings.zen_mode
|
||||
if gentle_cb:
|
||||
gentle_cb.button_pressed = Settings.gentle_errors
|
||||
visible = true
|
||||
get_tree().paused = true
|
||||
|
||||
@@ -163,8 +161,10 @@ func _on_reset() -> void:
|
||||
var val: float = Settings.get(key)
|
||||
rows[key].slider.value = val
|
||||
rows[key].spin.value = val
|
||||
if curve_option:
|
||||
curve_option.select(Settings.score_curve)
|
||||
if zen_cb:
|
||||
zen_cb.button_pressed = Settings.zen_mode
|
||||
if gentle_cb:
|
||||
gentle_cb.button_pressed = Settings.gentle_errors
|
||||
|
||||
|
||||
func _on_cancel() -> void:
|
||||
|
||||
+30
-17
@@ -4,17 +4,20 @@ extends Node
|
||||
## al riavvio della scena.
|
||||
|
||||
const PATH := "user://settings.cfg"
|
||||
const VERSION := 5 # incrementa quando cambiano i default, per resettare i file vecchi
|
||||
var version := VERSION
|
||||
|
||||
var start_time := 4.5 # tempo iniziale (s) tra un ordine e l'altro
|
||||
var min_time := 0.8 # tempo minimo (velocità massima)
|
||||
var time_ramp := 180.0 # secondi di gioco per difficoltà piena
|
||||
var score_ramp := 2000.0 # punteggio per difficoltà piena
|
||||
var points_per_combo := 10 # punti base per ogni livello di combo
|
||||
var combo_decay_threshold := 5.0 # combo sopra cui inizia il decay
|
||||
var combo_decay_rate := 0.06 # combo persi per unità sopra la soglia
|
||||
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 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
|
||||
var difficulty_decay_rate := 0.3 # decay aggiuntivo legato alla difficoltà
|
||||
# Curva del punteggio: 0=quadratica, 1=scaglioni, 2=radice, 3=additiva
|
||||
var score_curve := 0
|
||||
# Accessibilità / neurodivergenza
|
||||
var gentle_errors := true # errore: combo -1 invece di azzerare
|
||||
var zen_mode := false # modalità senza tempo (niente countdown né decay)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -25,38 +28,48 @@ func load_settings() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
if cfg.load(PATH) != OK:
|
||||
return # usa i default
|
||||
version = cfg.get_value("meta", "version", 0)
|
||||
if version < VERSION:
|
||||
# file di impostazioni vecchio: applica i nuovi default e risalva
|
||||
reset_defaults()
|
||||
save_settings()
|
||||
return
|
||||
start_time = cfg.get_value("gameplay", "start_time", start_time)
|
||||
min_time = cfg.get_value("gameplay", "min_time", min_time)
|
||||
time_ramp = cfg.get_value("gameplay", "time_ramp", time_ramp)
|
||||
score_ramp = cfg.get_value("gameplay", "score_ramp", score_ramp)
|
||||
points_per_combo = cfg.get_value("gameplay", "points_per_combo", points_per_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_rate = cfg.get_value("gameplay", "combo_decay_rate", combo_decay_rate)
|
||||
difficulty_decay_rate = cfg.get_value("gameplay", "difficulty_decay_rate", difficulty_decay_rate)
|
||||
score_curve = cfg.get_value("gameplay", "score_curve", score_curve)
|
||||
gentle_errors = cfg.get_value("accessibility", "gentle_errors", gentle_errors)
|
||||
zen_mode = cfg.get_value("accessibility", "zen_mode", zen_mode)
|
||||
|
||||
|
||||
func save_settings() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.set_value("meta", "version", VERSION)
|
||||
cfg.set_value("gameplay", "start_time", start_time)
|
||||
cfg.set_value("gameplay", "min_time", min_time)
|
||||
cfg.set_value("gameplay", "time_ramp", time_ramp)
|
||||
cfg.set_value("gameplay", "score_ramp", score_ramp)
|
||||
cfg.set_value("gameplay", "points_per_combo", points_per_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_rate", combo_decay_rate)
|
||||
cfg.set_value("gameplay", "difficulty_decay_rate", difficulty_decay_rate)
|
||||
cfg.set_value("gameplay", "score_curve", score_curve)
|
||||
cfg.set_value("accessibility", "gentle_errors", gentle_errors)
|
||||
cfg.set_value("accessibility", "zen_mode", zen_mode)
|
||||
cfg.save(PATH)
|
||||
|
||||
|
||||
func reset_defaults() -> void:
|
||||
start_time = 4.5
|
||||
min_time = 0.8
|
||||
time_ramp = 180.0
|
||||
score_ramp = 2000.0
|
||||
points_per_combo = 10
|
||||
combo_decay_threshold = 5.0
|
||||
combo_decay_rate = 0.06
|
||||
score_ramp = 8000.0
|
||||
points_per_combo = 1
|
||||
max_combo = 5.0
|
||||
combo_decay_threshold = 0.0
|
||||
combo_decay_rate = 0.15
|
||||
difficulty_decay_rate = 0.3
|
||||
score_curve = 0
|
||||
gentle_errors = true
|
||||
zen_mode = false
|
||||
|
||||
Reference in New Issue
Block a user