UI polish: victory overlay & lock dialog in wood/brass circus theme (codex); rounded board corners; per-ball veins

This commit is contained in:
enne2
2026-08-18 20:18:05 +02:00
parent 44160adc1a
commit 7c5850eb1d
9 changed files with 670 additions and 68 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+40
View File
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://btc1ubsxb6puh"
path="res://.godot/imported/wood_board_rounded.png-f23c55e0bb735a20beb2de2e8bbb0023.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/wood_board_rounded.png"
dest_files=["res://.godot/imported/wood_board_rounded.png-f23c55e0bb735a20beb2de2e8bbb0023.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
+41 -9
View File
@@ -18,6 +18,8 @@ var roll_angle := 0.0:
set(v):
roll_angle = v
queue_redraw() # aggiorna il disegno a ogni step di rotazione
var veins: Array[PackedVector2Array] = []
var _rng := RandomNumberGenerator.new()
var _tween: Tween
@@ -39,26 +41,54 @@ func _draw() -> void:
func _draw_marbling() -> void:
# Tre venature curve, irregolari e non simmetriche. Tutta la geometria
# superficiale ruota; illuminazione e riflesso vengono invece disegnati dopo.
# Venature curve, irregolari e casuali per ogni biglia. Tutta la geometria
# superficiale ruota; illuminazione e riflesso vengono disegnati dopo.
var vein_color: Color = ball_color.darkened(0.42)
vein_color.a = 0.20
var soft_color: Color = ball_color.darkened(0.28)
soft_color.a = 0.12
var veins: Array[PackedVector2Array] = [
_make_vein(-0.72, -0.22, 0.68, -0.06, 0.15, 0.09),
_make_vein(-0.55, 0.34, 0.34, 0.60, -0.13, 0.07),
_make_vein(-0.18, -0.64, 0.58, -0.40, -0.10, 0.06),
]
for i: int in range(veins.size()):
var rotated_points := PackedVector2Array()
var vis_sum := 0.0
for point: Vector2 in veins[i]:
rotated_points.append(point.rotated(roll_angle))
var width: float = radius * (0.075 if i == 0 else 0.045)
# Rotazione attorno all'asse Y (perpendicolare al moto orizzontale):
# la vena scorre in orizzontale (sin(θ+roll)) e si schiaccia ai bordi
# (cos = foreshortening). I punti sul retro (cos<=0) non sono visibili.
var theta: float = asin(clampf(point.x / radius, -1.0, 1.0))
var new_theta: float = theta + roll_angle
var c := cos(new_theta)
if c <= 0.0:
continue # retro della sfera: nascosto
var vis := clampf(c, 0.0, 1.0)
vis_sum += vis
rotated_points.append(Vector2(
sin(new_theta) * radius,
point.y * (0.3 + 0.7 * vis)))
if rotated_points.size() < 2:
continue
var vis_avg := vis_sum / veins[i].size()
var width: float = radius * (0.075 if i == 0 else 0.045) * (0.3 + 0.7 * vis_avg)
draw_polyline(rotated_points, soft_color, width * 1.9, true)
draw_polyline(rotated_points, vein_color, width, true)
func _generate_veins() -> Array[PackedVector2Array]:
# 7 venature distribuite uniformemente su TUTTA la longitudine (360°):
# così la superficie non resta mai liscia durante la rotazione.
var result: Array[PackedVector2Array] = []
var count := 7
for i in count:
var center: float = -PI + TAU * (float(i) + _rng.randf_range(0.0, 0.9)) / count
var spread := _rng.randf_range(0.25, 0.7)
var yc := _rng.randf_range(-0.5, 0.5)
var y0 := clampf(yc - _rng.randf_range(0.0, 0.3), -0.6, 0.6)
var y1 := clampf(yc + _rng.randf_range(0.0, 0.3), -0.6, 0.6)
var bend := _rng.randf_range(0.05, 0.22)
var ripple := _rng.randf_range(0.03, 0.11)
result.append(_make_vein(sin(center - spread), y0, sin(center + spread), y1, bend, ripple))
return result
func _make_vein(x0: float, y0: float, x1: float, y1: float,
bend: float, ripple: float) -> PackedVector2Array:
var points := PackedVector2Array()
@@ -77,6 +107,8 @@ func setup(cn: String, c: Color, home: Vector2, left: Vector2, r: float) -> void
color_name = cn
ball_color = c
radius = r
_rng.seed = hash(cn) # venature uniche ma stabili per ogni colore
veins = _generate_veins()
home_pos = home
left_pos = left
position = home
+3 -22
View File
@@ -6,8 +6,8 @@ extends Node2D
const VIEW := Vector2(1280, 720)
const LANE_TOP := 210.0
const LANE_GAP := 98.0
const LANE_LEFT := 70.0 # centro alloggiamento sinistro
const LANE_RIGHT := 1210.0 # centro alloggiamento destro
const LANE_LEFT := 100.0 # centro alloggiamento sinistro (dentro i bordi)
const LANE_RIGHT := 1180.0 # centro alloggiamento destro (dentro i bordi)
const SOCKET_R := 46.0 # raggio alloggiamento (ampio alle estremità)
const H_NARROW := 24.0 # semi-altezza canale al centro (stretto)
const CENTER_X := (LANE_LEFT + LANE_RIGHT) / 2.0
@@ -22,7 +22,7 @@ var circus_tex: Texture2D
func _ready() -> void:
z_index = -10
wood_tex = load("res://assets/wood_board.png")
wood_tex = load("res://assets/wood_board_rounded.png") # angoli arrotondati coerenti con la cornice
circus_tex = load("res://assets/circus_arena_bg.png")
queue_redraw()
@@ -52,7 +52,6 @@ func _draw() -> void:
draw_style_box(frame, Rect2(32, 138, 1216, 566))
for i in range(5):
_draw_lane(LANE_TOP + i * LANE_GAP)
_draw_number(i + 1, LANE_TOP + i * LANE_GAP)
func _lane_half_width(x: float) -> float:
@@ -87,21 +86,3 @@ func _draw_lane(y: float) -> void:
draw_circle(c + Vector2(3, 5), SOCKET_R - 13.0, Color(0.2, 0.1, 0.04, 0.16))
draw_arc(c, SOCKET_R, 0.0, TAU, 48, Color("#4a301d"), 3.0)
draw_arc(c - Vector2(1, 2), SOCKET_R - 8.0, PI, TAU, 24, Color(1, 0.92, 0.72, 0.28), 2.0)
# ombra del canale in alto (profondità)
draw_rect(Rect2(LANE_LEFT + 10, y - _lane_half_width((LANE_LEFT + CENTER_X) / 2.0) + 3.0, HALF_SPAN * 2 - 20, 3.0),
Color(0.2, 0.12, 0.05, 0.18))
func _draw_number(n: int, y: float) -> void:
var c := Vector2(38, y)
var col: Color
match n:
1: col = Color("#de6b7e")
2: col = Color("#1852de")
3: col = Color("#88ce96")
4: col = Color("#e8b84b")
_: col = Color("#9b7ede")
draw_circle(c, 22.0, col)
draw_circle(c + Vector2(0, 2), 22.0, Color(0, 0, 0, 0.18))
draw_arc(c, 22.0, 0.0, TAU, 32, Color(0.1, 0.1, 0.1, 0.85), 2.0)
draw_string(ThemeDB.fallback_font, c + Vector2(-14, -10), str(n), HORIZONTAL_ALIGNMENT_CENTER, 28, 26, Color(1, 1, 1))
+363
View File
@@ -0,0 +1,363 @@
extends CanvasLayer
## Area riservata: per aprire le Opzioni bisogna premere la sequenza
## giallo, verde, blu, rosso scegliendo fra le cinque biglie del gioco.
signal unlocked
const COLORS := {
"giallo": Color("#e8b84b"),
"verde": Color("#88ce96"),
"blu": Color("#1852de"),
"rosso": Color("#de6b7e"),
"viola": Color("#9b7ede"),
}
const BALL_ORDER := ["giallo", "verde", "blu", "rosso", "viola"]
const SEQUENCE := ["giallo", "verde", "blu", "rosso"]
const DISPLAY_FONT := preload("res://assets/fonts/NotoSerifDisplay-CondensedExtraBold.ttf")
const WOOD_TEXTURE := preload("res://assets/wood_board_rounded.png")
var step := 0
var sequence_attempt: Array[String] = []
var _busy := false # blocca l'input durante il feedback di errore
var progress_dots: Array[Control] = []
var _click: AudioStreamWAV
var _wrong: AudioStreamWAV
var _unlock: AudioStreamWAV
class MarbleButton extends Control:
signal chosen(color_name: String)
var color_name := ""
var ball_color := Color.WHITE
var radius := 47.0
var hovered := false
var held := false
var veins: Array[PackedVector2Array] = []
func setup(name: String, color: Color) -> void:
color_name = name
ball_color = color
custom_minimum_size = Vector2(108, 108)
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
mouse_entered.connect(func(): hovered = true; queue_redraw())
mouse_exited.connect(func(): hovered = false; held = false; queue_redraw())
_generate_veins()
queue_redraw()
func _gui_input(event: InputEvent) -> void:
# Gestiamo SOLO InputEventMouseButton: con emulate_touch_from_mouse=true
# ogni tocco genera anche un evento mouse, quindi processare entrambi
# causerebbe il DOPPIO incremento dei pallini.
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
held = event.pressed
queue_redraw()
if not event.pressed:
chosen.emit(color_name)
accept_event()
func _draw() -> void:
var center := size * 0.5 + Vector2(0, 2 if held else 0)
var r := radius * (0.96 if held else (1.03 if hovered else 1.0))
draw_circle(center + Vector2(0, 6), r, Color(0, 0, 0, 0.28))
draw_circle(center, r, ball_color)
draw_circle(center + Vector2(r * 0.18, r * 0.22), r * 0.92, Color(0, 0, 0, 0.16))
draw_circle(center - Vector2(r * 0.18, r * 0.22), r * 0.92, Color(1, 1, 1, 0.20))
var vein_color := ball_color.darkened(0.42)
vein_color.a = 0.22
var soft_color := ball_color.darkened(0.28)
soft_color.a = 0.13
for vein in veins:
var points := PackedVector2Array()
for point in vein:
points.append(center + point * (r / radius))
draw_polyline(points, soft_color, r * 0.095, true)
draw_polyline(points, vein_color, r * 0.045, true)
draw_arc(center, r, 0.0, TAU, 56, Color(0.1, 0.1, 0.1, 0.92), 4.0, true)
draw_circle(center - Vector2(r * 0.30, r * 0.34), r * 0.16, Color(1, 1, 1, 0.86))
if hovered:
draw_arc(center, r + 4.0, 0.0, TAU, 56, Color(1, 1, 1, 0.75), 2.0, true)
func _generate_veins() -> void:
var rng := RandomNumberGenerator.new()
rng.seed = hash(color_name)
for i in 5:
var points := PackedVector2Array()
var y_base := rng.randf_range(-0.55, 0.55) * radius
var phase := rng.randf_range(0.0, TAU)
var x_start := rng.randf_range(-0.78, -0.35) * radius
var x_end := rng.randf_range(0.35, 0.78) * radius
for j in 13:
var t := float(j) / 12.0
var x := lerpf(x_start, x_end, t)
var y := y_base + sin(t * TAU * 1.2 + phase) * radius * 0.13
points.append(Vector2(x, y))
veins.append(points)
class ProgressDot extends Control:
var dot_color := Color.TRANSPARENT # trasparente = non ancora premuto
func _init() -> void:
custom_minimum_size = Vector2(38, 38)
func set_color(c: Color) -> void:
dot_color = c
queue_redraw()
func _draw() -> void:
var center := size * 0.5
var r := 12.0
draw_circle(center + Vector2(1.0, 2.0), r + 2.0, Color(0.18, 0.07, 0.03, 0.32))
if dot_color.a > 0.01:
# pallino pieno del colore della biglia premuta
draw_circle(center, r, dot_color)
draw_circle(center - Vector2(3.0, 3.0), 3.2, Color(1, 1, 1, 0.5))
draw_arc(center, r, 0.0, TAU, 32, Color("#4b2818"), 3.0, true)
else:
# vuoto: cerchio neutro
draw_circle(center, r, Color("#eee2cf"))
draw_arc(center, r, 0.0, TAU, 32, Color("#69401f"), 3.0, true)
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
visible = false
_init_audio()
_build()
func _init_audio() -> void:
# Suoni morbidi (sine, decadimento dolce): piacevoli, non fastidiosi
_click = _make_tone(660.0, 0.09, 0.28) # click corretto, tono allegro
_wrong = _make_tone(330.0, 0.11, 0.18) # errore: tono più basso e attenuato
_unlock = _make_arpeggio([523.25, 659.25, 783.99], 0.09) # sblocco
func _make_tone(freq: float, dur: float, amp: float) -> AudioStreamWAV:
var sr := 22050
var n := int(sr * dur)
var data := PackedByteArray()
data.resize(n)
var attack := int(sr * 0.008)
for i in n:
var t := float(i) / sr
var env := 1.0
if i < attack:
env = float(i) / attack
env *= exp(-4.0 * t / dur)
data[i] = int(clampf(sin(t * freq * TAU) * env * amp * 127.0, -128.0, 127.0)) & 0xFF
var s := AudioStreamWAV.new()
s.format = AudioStreamWAV.FORMAT_8_BITS
s.mix_rate = sr
s.data = data
return s
func _make_arpeggio(freqs: Array, note_dur: float) -> AudioStreamWAV:
var sr := 22050
var total := 0
for f in freqs:
total += int(sr * note_dur)
var data := PackedByteArray()
data.resize(total)
var idx := 0
for f in freqs:
var n := int(sr * note_dur)
var attack := int(sr * 0.008)
for i in n:
var t := float(i) / sr
var env := 1.0
if i < attack:
env = float(i) / attack
env *= exp(-3.0 * t / note_dur)
data[idx] = int(clampf(sin(t * float(f) * TAU) * env * 0.24 * 127.0, -128.0, 127.0)) & 0xFF
idx += 1
var s := AudioStreamWAV.new()
s.format = AudioStreamWAV.FORMAT_8_BITS
s.mix_rate = sr
s.data = data
return s
func _play(stream: AudioStreamWAV) -> void:
if stream == null:
return
var p := AudioStreamPlayer.new()
add_child(p)
p.stream = stream
p.finished.connect(p.queue_free)
p.play()
func _build() -> void:
var dim := ColorRect.new()
dim.color = Color(0.06, 0.035, 0.025, 0.78)
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 frame := PanelContainer.new()
frame.custom_minimum_size = Vector2(720, 420)
var frame_style := StyleBoxFlat.new()
frame_style.bg_color = Color("#70411f")
frame_style.border_color = Color("#e1ba59")
frame_style.set_border_width_all(6)
frame_style.set_corner_radius_all(30)
frame_style.shadow_color = Color(0, 0, 0, 0.52)
frame_style.shadow_size = 20
frame_style.shadow_offset = Vector2(0, 8)
for side in [SIDE_LEFT, SIDE_TOP, SIDE_RIGHT, SIDE_BOTTOM]:
frame_style.set_content_margin(side, 10.0)
frame.add_theme_stylebox_override("panel", frame_style)
center.add_child(frame)
var panel := PanelContainer.new()
var wood_style := StyleBoxTexture.new()
wood_style.texture = WOOD_TEXTURE
wood_style.texture_margin_left = 35.0
wood_style.texture_margin_right = 35.0
wood_style.texture_margin_top = 35.0
wood_style.texture_margin_bottom = 35.0
panel.add_theme_stylebox_override("panel", wood_style)
frame.add_child(panel)
var margin := MarginContainer.new()
for side in ["left", "right"]:
margin.add_theme_constant_override("margin_" + side, 42)
margin.add_theme_constant_override("margin_top", 27)
margin.add_theme_constant_override("margin_bottom", 25)
panel.add_child(margin)
var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 12)
margin.add_child(vb)
var title := Label.new()
title.text = "AREA RISERVATA"
title.add_theme_font_override("font", DISPLAY_FONT)
title.add_theme_font_size_override("font_size", 36)
title.add_theme_color_override("font_color", Color("#ffe08a"))
title.add_theme_color_override("font_outline_color", Color("#673018"))
title.add_theme_constant_override("outline_size", 8)
title.add_theme_color_override("font_shadow_color", Color(0.2, 0.07, 0.02, 0.35))
title.add_theme_constant_override("shadow_offset_y", 3)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vb.add_child(title)
var hint := Label.new()
hint.text = "Chiedi a un adulto di premere la sequenza di colori"
hint.add_theme_font_override("font", DISPLAY_FONT)
hint.add_theme_font_size_override("font_size", 19)
hint.add_theme_color_override("font_color", Color("#56321e"))
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vb.add_child(hint)
var balls := HBoxContainer.new()
balls.add_theme_constant_override("separation", 14)
balls.alignment = BoxContainer.ALIGNMENT_CENTER
vb.add_child(balls)
for color_name in BALL_ORDER:
var marble := MarbleButton.new()
marble.setup(color_name, COLORS[color_name])
marble.chosen.connect(_on_press)
balls.add_child(marble)
var progress := HBoxContainer.new()
progress.alignment = BoxContainer.ALIGNMENT_CENTER
progress.add_theme_constant_override("separation", 12)
vb.add_child(progress)
for i in SEQUENCE.size():
var dot := ProgressDot.new()
progress_dots.append(dot)
progress.add_child(dot)
var cancel := Button.new()
cancel.text = "ANNULLA"
cancel.custom_minimum_size = Vector2(210, 52)
cancel.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
cancel.add_theme_font_override("font", DISPLAY_FONT)
cancel.add_theme_font_size_override("font_size", 21)
cancel.add_theme_color_override("font_color", Color("#fff0c4"))
cancel.add_theme_color_override("font_hover_color", Color.WHITE)
cancel.add_theme_stylebox_override("normal", _make_button_style(Color("#744020"), Color("#ce9b3d")))
cancel.add_theme_stylebox_override("hover", _make_button_style(Color("#8b5129"), Color("#f0ca68")))
cancel.add_theme_stylebox_override("pressed", _make_button_style(Color("#5c3019"), Color("#ae7927")))
cancel.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
cancel.pressed.connect(_on_cancel)
vb.add_child(cancel)
func _make_button_style(bg: Color, border: Color) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = bg
style.border_color = border
style.set_border_width_all(4)
style.set_corner_radius_all(13)
style.shadow_color = Color(0.15, 0.05, 0.02, 0.42)
style.shadow_size = 5
style.shadow_offset = Vector2(0, 3)
return style
func show_menu() -> void:
sequence_attempt.clear()
_update_progress()
visible = true
get_tree().paused = true
func hide_menu() -> void:
visible = false
get_tree().paused = false
func _on_press(color_name: String) -> void:
if _busy:
return
_play(_click)
sequence_attempt.append(color_name)
_update_progress()
if sequence_attempt.size() >= SEQUENCE.size():
if sequence_attempt == SEQUENCE:
# sequenza completa e CORRETTA: sblocca
call_deferred("_finish_unlock")
else:
# sequenza completa ma ERRATA: feedback visivo + reset dopo 2s
_handle_wrong()
func _handle_wrong() -> void:
_busy = true
_play(_wrong)
# feedback di errore: tutti i pallini diventano rossi
for dot in progress_dots:
dot.set_color(Color("#d9534f"))
# attende ~2 secondi, poi resetta la sequenza
await get_tree().create_timer(2.0).timeout
sequence_attempt.clear()
_update_progress()
_busy = false
func _finish_unlock() -> void:
_play(_unlock)
hide_menu()
unlocked.emit()
func _update_progress() -> void:
# ogni pallino mostra il colore della biglia premuta in sequenza
for i in progress_dots.size():
if i < sequence_attempt.size():
progress_dots[i].set_color(COLORS[sequence_attempt[i]])
else:
progress_dots[i].set_color(Color.TRANSPARENT)
func _on_cancel() -> void:
hide_menu()
+1
View File
@@ -0,0 +1 @@
uid://bsxirosmw4y0r
+68 -5
View File
@@ -1,19 +1,82 @@
extends Node2D
## Pallina di colore che mostra il bersaglio attuale nella barra in alto.
## Stile coerente con le biglie della plancia: glossy con venature marmorizzate.
var dot_color := Color.WHITE
const R := 36.0
var veins: Array[PackedVector2Array] = []
var _rng := RandomNumberGenerator.new()
func _draw() -> void:
draw_circle(Vector2(0, 4), 36.0, Color(0, 0, 0, 0.25))
draw_circle(Vector2.ZERO, 36.0, dot_color)
draw_circle(Vector2(6.5, 8.0), 33.0, Color(0, 0, 0, 0.16))
draw_circle(Vector2(-6.5, -8.0), 33.0, Color(1, 1, 1, 0.2))
draw_arc(Vector2.ZERO, 36.0, 0.0, TAU, 40, Color(0.1, 0.1, 0.1, 0.9), 4.0)
# ombra
draw_circle(Vector2(0, 4), R, Color(0, 0, 0, 0.25))
# corpo
draw_circle(Vector2.ZERO, R, dot_color)
# ombreggiatura inferiore-destra e luce superiore-sinistra
draw_circle(Vector2(6.5, 8.0), R * 0.92, Color(0, 0, 0, 0.16))
draw_circle(Vector2(-6.5, -8.0), R * 0.92, Color(1, 1, 1, 0.2))
# venature marmorizzate (come le biglie della plancia, statiche in HUD)
_draw_marbling()
# bordo nero stile china
draw_arc(Vector2.ZERO, R, 0.0, TAU, 40, Color(0.1, 0.1, 0.1, 0.9), 4.0)
# riflesso speculare fisso
draw_circle(Vector2(-11.0, -12.0), 6.0, Color(1, 1, 1, 0.85))
func _draw_marbling() -> void:
var vein_color: Color = dot_color.darkened(0.42)
vein_color.a = 0.2
var soft_color: Color = dot_color.darkened(0.28)
soft_color.a = 0.12
for i: int in range(veins.size()):
var pts := PackedVector2Array()
for point: Vector2 in veins[i]:
# visibile solo la metà frontale (longitudine cos>0): copertura 360°
var theta: float = asin(clampf(point.x / R, -1.0, 1.0))
if cos(theta) <= 0.0:
continue
pts.append(point)
if pts.size() < 2:
continue
var width: float = R * (0.075 if i == 0 else 0.045)
draw_polyline(pts, soft_color, width * 1.9, true)
draw_polyline(pts, vein_color, width, true)
func _generate_veins() -> Array[PackedVector2Array]:
# 7 venature distribuite uniformemente su tutta la longitudine (360°)
var result: Array[PackedVector2Array] = []
var count := 7
for i in count:
var center: float = -PI + TAU * (float(i) + _rng.randf_range(0.0, 0.9)) / count
var spread := _rng.randf_range(0.25, 0.7)
var yc := _rng.randf_range(-0.5, 0.5)
var y0 := clampf(yc - _rng.randf_range(0.0, 0.3), -0.6, 0.6)
var y1 := clampf(yc + _rng.randf_range(0.0, 0.3), -0.6, 0.6)
var bend := _rng.randf_range(0.05, 0.22)
var ripple := _rng.randf_range(0.03, 0.11)
result.append(_make_vein(sin(center - spread), y0, sin(center + spread), y1, bend, ripple))
return result
func _make_vein(x0: float, y0: float, x1: float, y1: float,
bend: float, ripple: float) -> PackedVector2Array:
var points := PackedVector2Array()
var direction := Vector2(x1 - x0, y1 - y0)
var normal := direction.normalized().orthogonal()
for step: int in range(17):
var t: float = float(step) / 16.0
var base := Vector2(lerpf(x0, x1, t), lerpf(y0, y1, t))
var curve: float = sin(t * PI) * bend
var wave: float = sin(t * TAU * 1.35 + x0 * 4.0) * ripple
points.append((base + normal * (curve + wave)) * R)
return points
func set_color(c: Color) -> void:
dot_color = c
_rng.seed = hash(c) # pattern unico ma stabile per ogni colore
veins = _generate_veins()
queue_redraw()
+153 -32
View File
@@ -1,22 +1,78 @@
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"
signal reset_requested
const DISPLAY_FONT := preload("res://assets/fonts/NotoSerifDisplay-CondensedExtraBold.ttf")
const WOOD_TEXTURE := preload("res://assets/wood_board_rounded.png")
const INK := Color("#482615")
const BRASS := Color("#d5a83f")
const BRASS_LIGHT := Color("#f6d77a")
var title_label: Label
var score_label: Label
var _card: Control
var _reset_button: Button
class CircusTrim extends Control:
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
queue_redraw()
func _draw() -> void:
var flag_colors := [Color("#a93334"), Color("#d8a83e"), Color("#397b76")]
var count := 9
var start_x := (size.x - 360.0) * 0.5
draw_line(Vector2(start_x - 8, 11), Vector2(start_x + 368, 11), Color("#59301c"), 3.0, true)
for i in count:
var x := start_x + i * 45.0
var points := PackedVector2Array([Vector2(x, 10), Vector2(x + 34, 10), Vector2(x + 17, 35)])
draw_colored_polygon(points, flag_colors[i % flag_colors.size()])
draw_polyline(PackedVector2Array([points[0], points[1], points[2], points[0]]), Color("#6b341c"), 2.0, true)
class BrassStuds extends Control:
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
queue_redraw()
func _draw() -> void:
for p in [Vector2(22, 22), Vector2(size.x - 22, 22), Vector2(22, size.y - 22), Vector2(size.x - 22, size.y - 22)]:
draw_circle(p + Vector2(1.5, 2.5), 8.0, Color(0.12, 0.05, 0.02, 0.45))
draw_circle(p, 8.0, Color("#b17b24"))
draw_circle(p - Vector2(2, 2), 4.5, Color("#f6d77a"))
draw_line(p - Vector2(3, 0), p + Vector2(3, 0), Color("#765018"), 1.5, true)
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS # funziona anche a gioco in pausa
process_mode = Node.PROCESS_MODE_ALWAYS
visible = false
_build()
func _wood_style() -> StyleBoxTexture:
var style := StyleBoxTexture.new()
style.texture = WOOD_TEXTURE
style.texture_margin_left = 35.0
style.texture_margin_right = 35.0
style.texture_margin_top = 35.0
style.texture_margin_bottom = 35.0
return style
func _flat_style(color: Color, border: Color, width: int, radius: int) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = color
style.border_color = border
style.set_border_width_all(width)
style.set_corner_radius_all(radius)
return style
func _build() -> void:
var dim := ColorRect.new()
dim.color = Color(0.05, 0.02, 0.02, 0.72)
dim.color = Color(0.045, 0.018, 0.012, 0.76)
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(dim)
@@ -25,63 +81,128 @@ func _build() -> void:
center.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(center)
var panel := PanelContainer.new()
center.add_child(panel)
_card = PanelContainer.new()
_card.custom_minimum_size = Vector2(680, 510)
var frame := _flat_style(Color("#70411f"), Color("#edca69"), 6, 31)
frame.shadow_color = Color(0, 0, 0, 0.55)
frame.shadow_size = 22
frame.shadow_offset = Vector2(0, 9)
frame.content_margin_left = 10
frame.content_margin_right = 10
frame.content_margin_top = 10
frame.content_margin_bottom = 10
_card.add_theme_stylebox_override("panel", frame)
center.add_child(_card)
var wood := PanelContainer.new()
wood.add_theme_stylebox_override("panel", _wood_style())
_card.add_child(wood)
var studs := BrassStuds.new()
studs.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
wood.add_child(studs)
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)
margin.add_theme_constant_override("margin_left", 58)
margin.add_theme_constant_override("margin_right", 58)
margin.add_theme_constant_override("margin_top", 36)
margin.add_theme_constant_override("margin_bottom", 38)
wood.add_child(margin)
var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 18)
vb.add_theme_constant_override("separation", 15)
vb.alignment = BoxContainer.ALIGNMENT_CENTER
margin.add_child(vb)
var bunting := CircusTrim.new()
bunting.custom_minimum_size = Vector2(0, 40)
vb.add_child(bunting)
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.add_theme_font_override("font", DISPLAY_FONT)
title_label.add_theme_font_size_override("font_size", 68)
title_label.add_theme_color_override("font_color", Color("#ffe08a"))
title_label.add_theme_color_override("font_outline_color", Color("#713019"))
title_label.add_theme_constant_override("outline_size", 10)
title_label.add_theme_color_override("font_shadow_color", Color(0.9, 0.58, 0.12, 0.42))
title_label.add_theme_constant_override("shadow_offset_x", 0)
title_label.add_theme_constant_override("shadow_offset_y", 4)
title_label.add_theme_constant_override("shadow_outline_size", 14)
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.add_theme_font_override("font", DISPLAY_FONT)
subtitle.add_theme_font_size_override("font_size", 23)
subtitle.add_theme_color_override("font_color", INK)
subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vb.add_child(subtitle)
var score_plaque := PanelContainer.new()
score_plaque.add_theme_stylebox_override("panel", _flat_style(Color("#f7e6bc"), Color("#9d6b29"), 3, 14))
var score_margin := MarginContainer.new()
score_margin.add_theme_constant_override("margin_left", 30)
score_margin.add_theme_constant_override("margin_right", 30)
score_margin.add_theme_constant_override("margin_top", 8)
score_margin.add_theme_constant_override("margin_bottom", 8)
score_plaque.add_child(score_margin)
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.add_theme_font_override("font", DISPLAY_FONT)
score_label.add_theme_font_size_override("font_size", 34)
score_label.add_theme_color_override("font_color", INK)
score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vb.add_child(score_label)
score_margin.add_child(score_label)
vb.add_child(score_plaque)
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)
_reset_button = Button.new()
_reset_button.text = "↻ RIGIOCA"
_reset_button.custom_minimum_size = Vector2(250, 66)
_reset_button.add_theme_font_override("font", DISPLAY_FONT)
_reset_button.add_theme_font_size_override("font_size", 27)
_reset_button.add_theme_color_override("font_color", Color("#fff1c7"))
_reset_button.add_theme_color_override("font_hover_color", Color.WHITE)
_reset_button.add_theme_stylebox_override("normal", _button_style(Color("#784121"), Color("#d9a947"), 5))
_reset_button.add_theme_stylebox_override("hover", _button_style(Color("#8e5129"), BRASS_LIGHT, 5))
_reset_button.add_theme_stylebox_override("pressed", _button_style(Color("#5f311b"), Color("#b7832e"), 4))
_reset_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_reset_button.pressed.connect(func(): reset_requested.emit())
vb.add_child(_reset_button)
func _button_style(color: Color, border: Color, width: int) -> StyleBoxFlat:
var style := _flat_style(color, border, width, 15)
style.shadow_color = Color(0.14, 0.05, 0.02, 0.48)
style.shadow_size = 6
style.shadow_offset = Vector2(0, 4)
return style
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
_card.modulate.a = 0.0
_card.scale = Vector2(0.94, 0.94)
_card.pivot_offset = _card.size * 0.5
title_label.pivot_offset = title_label.size * 0.5
title_label.scale = Vector2(0.4, 0.4)
title_label.scale = Vector2(0.45, 0.45)
title_label.rotation = deg_to_rad(-3.0)
title_label.modulate.a = 0.0
score_label.modulate.a = 0.0
_reset_button.modulate.a = 0.0
var tw := create_tween()
tw.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS)
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)
tw.tween_property(_card, "modulate:a", 1.0, 0.22)
tw.tween_property(_card, "scale", Vector2.ONE, 0.34).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tw.tween_property(title_label, "modulate:a", 1.0, 0.22).set_delay(0.12)
tw.tween_property(title_label, "scale", Vector2(1.08, 1.08), 0.42).set_delay(0.10).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tw.tween_property(title_label, "rotation", deg_to_rad(1.0), 0.42).set_delay(0.10).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tw.tween_property(score_label, "modulate:a", 1.0, 0.35).set_delay(0.38)
tw.tween_property(_reset_button, "modulate:a", 1.0, 0.35).set_delay(0.52)
tw.chain().tween_property(title_label, "scale", Vector2.ONE, 0.18).set_trans(Tween.TRANS_SINE)
tw.parallel().tween_property(title_label, "rotation", 0.0, 0.18).set_trans(Tween.TRANS_SINE)
func hide_victory() -> void:
+1
View File
@@ -0,0 +1 @@
uid://5reli744dkgr