Refactor Godot architecture into AppShell, modular menus and game domains

This commit is contained in:
enne2
2026-09-06 17:07:05 +02:00
parent a73fb4b55f
commit f4a9da6bc3
209 changed files with 2726 additions and 2072 deletions
@@ -0,0 +1,159 @@
extends Area2D
## Una singola pallina nella sua corsia. Partendo da destra, se lanciata
## scivola verso il bordo sinistro e poi torna alla posizione di partenza.
## Il rilevamento del tocco/click è gestito centralmente da main.gd tramite
## distanza: qui NON ci sono handler di input dedicati.
signal returned # emesso quando la pallina torna a casa
var color_name := ""
var ball_color := Color.WHITE
var radius := 40.0
var home_pos := Vector2.ZERO
var left_pos := Vector2.ZERO
var in_motion := false
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
func _draw() -> void:
# ombra a terra
draw_circle(Vector2(0, 6), radius, Color(0, 0, 0, 0.25))
# corpo base
draw_circle(Vector2.ZERO, radius, ball_color)
# ombreggiatura inferiore-destra (profondità 3D)
draw_circle(Vector2(radius * 0.18, radius * 0.22), radius * 0.92, Color(0, 0, 0, 0.16))
# luce superiore-sinistra
draw_circle(Vector2(-radius * 0.18, -radius * 0.22), radius * 0.92, Color(1, 1, 1, 0.2))
# marmorizzazione a vortici che ruota col rotolamento (design realistico)
_draw_marbling()
# bordo nero spesso (stile china)
draw_arc(Vector2.ZERO, radius, 0.0, TAU, 48, Color(0.1, 0.1, 0.1, 0.9), 4.0)
# riflesso speculare (fisso: la luce non ruota con la superficie)
draw_circle(Vector2(-radius * 0.3, -radius * 0.34), radius * 0.16, Color(1, 1, 1, 0.85))
func _draw_marbling() -> void:
# 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
for i: int in range(veins.size()):
var rotated_points := PackedVector2Array()
var vis_sum := 0.0
for point: Vector2 in veins[i]:
# 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()
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)) * radius)
return points
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
var shape := CollisionShape2D.new()
var cs := CircleShape2D.new()
cs.radius = r * 1.35 # area di tocco ampliata (ma l'input è via distanza in main)
shape.shape = cs
add_child(shape)
queue_redraw()
func is_home() -> bool:
return not in_motion
func reset() -> void:
if _tween and _tween.is_valid():
_tween.kill()
in_motion = false
position = home_pos
roll_angle = 0.0
## Lancia la pallina verso sinistra e la fa tornare a casa. `mt` è il tempo
## totale (andata + ritorno) in secondi.
func launch(mt: float) -> void:
in_motion = true
if _tween and _tween.is_valid():
_tween.kill()
var half: float = maxf(mt * 0.5, 0.01)
# rotazione proporzionale alla distanza percorsa (rotolamento)
var dist: float = home_pos.x - left_pos.x
var total_angle: float = dist / radius
_tween = create_tween()
_tween.tween_property(self, "position", left_pos, half) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
_tween.parallel().tween_property(self, "roll_angle", total_angle, half) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
_tween.tween_property(self, "position", home_pos, half) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
_tween.parallel().tween_property(self, "roll_angle", 0.0, half) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
_tween.finished.connect(_on_motion_done)
func _on_motion_done() -> void:
in_motion = false
returned.emit()
@@ -0,0 +1 @@
uid://d3kyc2ecf0aco
@@ -0,0 +1,88 @@
extends Node2D
## Plancia Juggle Board in legno: binari a TUTTA larghezza con larghezza
## variabile (svasati alle estremità, stretti al centro), alloggiamenti
## circolari incassati e indicatori numerici 1-5, come nel mockup.
const VIEW := Vector2(1280, 720)
const LANE_TOP := 210.0
const LANE_GAP := 98.0
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
const HALF_SPAN := (LANE_RIGHT - LANE_LEFT) / 2.0
const LANE_FILL := Color("#c9ac7c")
const LANE_EDGE := Color(0.22, 0.15, 0.08, 0.9)
var wood_tex: Texture2D
var circus_tex: Texture2D
func _ready() -> void:
z_index = -10
wood_tex = load("res://shared/assets/textures/wood_board_rounded.png") # angoli arrotondati coerenti con la cornice
circus_tex = load("res://games/juggleboard/assets/textures/circus_arena_bg.png")
queue_redraw()
func _draw() -> void:
# Arena circense quieta ai margini; la plancia copre il centro più ricco.
if circus_tex:
draw_texture_rect(circus_tex, Rect2(Vector2.ZERO, VIEW), false)
# Ombra e corpo smussato del tabellone.
var shadow := StyleBoxFlat.new()
shadow.bg_color = Color(0.12, 0.055, 0.02, 0.5)
shadow.corner_radius_top_left = 28
shadow.corner_radius_top_right = 28
shadow.corner_radius_bottom_left = 28
shadow.corner_radius_bottom_right = 28
draw_style_box(shadow, Rect2(32, 151, 1216, 560))
if wood_tex:
draw_texture_rect(wood_tex, Rect2(36, 142, 1208, 558), false)
var frame := StyleBoxFlat.new()
frame.bg_color = Color(0, 0, 0, 0)
frame.border_color = Color("#81542f")
frame.set_border_width_all(8)
frame.corner_radius_top_left = 27
frame.corner_radius_top_right = 27
frame.corner_radius_bottom_left = 27
frame.corner_radius_bottom_right = 27
draw_style_box(frame, Rect2(32, 138, 1216, 566))
for i in range(5):
_draw_lane(LANE_TOP + i * LANE_GAP)
func _lane_half_width(x: float) -> float:
# larghezza corsia: ampia alle estremità (d=1), stretta al centro (d=0)
var d := absf(x - CENTER_X) / HALF_SPAN
d = clampf(d, 0.0, 1.0)
return H_NARROW + (SOCKET_R - H_NARROW) * d * d
func _draw_lane(y: float) -> void:
var pts := PackedVector2Array()
var steps := 48
for k in range(steps + 1):
var x := lerpf(LANE_LEFT, LANE_RIGHT, float(k) / steps)
var hw := _lane_half_width(x)
pts.append(Vector2(x, y - hw))
for k in range(steps, -1, -1):
var x := lerpf(LANE_LEFT, LANE_RIGHT, float(k) / steps)
var hw := _lane_half_width(x)
pts.append(Vector2(x, y + hw))
draw_polygon(pts, [LANE_FILL])
var closed := pts.duplicate()
closed.append(pts[0])
draw_polyline(closed, LANE_EDGE, 3.0, true)
# Alloggiamenti a calotta: anelli concentrici e luce/ombra danno profondità.
for cx in [LANE_LEFT, LANE_RIGHT]:
var c := Vector2(cx, y)
draw_circle(c + Vector2(0, 4), SOCKET_R + 3.0, Color(0.12, 0.06, 0.02, 0.38))
draw_circle(c, SOCKET_R, Color("#8b6841"))
draw_circle(c + Vector2(0, 3), SOCKET_R - 6.0, Color("#6d4b2d"))
draw_circle(c + Vector2(-2, -4), SOCKET_R - 9.0, Color("#c5a476"))
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)
@@ -0,0 +1 @@
uid://l7l3t71lt50f
@@ -0,0 +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:
# 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()
@@ -0,0 +1 @@
uid://b1xr5s67yiov