From 918246fdcd9dd7168f52c7e47dacb645c8cd6a2d Mon Sep 17 00:00:00 2001 From: enne2 Date: Thu, 10 Sep 2026 23:11:38 +0200 Subject: [PATCH] feat: add sparse circus lights to circus sign on landing (max 4-5 at a time) --- .../shaders/circus_sign_lights.gdshader | 89 +++++++++++++++++++ menus/scripts/components/menu_ui.gd | 8 ++ menus/scripts/screens/p1_home_screen.gd | 10 ++- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 menus/assets/shaders/circus_sign_lights.gdshader diff --git a/menus/assets/shaders/circus_sign_lights.gdshader b/menus/assets/shaders/circus_sign_lights.gdshader new file mode 100644 index 0000000..55f3c6f --- /dev/null +++ b/menus/assets/shaders/circus_sign_lights.gdshader @@ -0,0 +1,89 @@ +shader_type canvas_item; +// Shader per l'insegna centrale UPENDI CIRCUS: +// Lucine colorate lungo la cornice del pannello che si accendono in modo rado e casuale +// solo dopo che l'insegna ha finito di atterrare e rimbalzare (sign_lights_active = 1.0). + +uniform float sign_lights_active : hint_range(0.0, 1.0) = 0.0; +uniform float blink_speed : hint_range(0.5, 8.0) = 2.2; + +// 37 posizioni lampadina distribuite lungo il contorno in legno dell'insegna (UV su canvas 880x561) +const vec2 sign_bulbs[37] = vec2[37]( + // Arco superiore + vec2(0.2107, 0.3839), vec2(0.2590, 0.3851), vec2(0.3223, 0.3828), vec2(0.3725, 0.3774), + vec2(0.4209, 0.3707), vec2(0.4946, 0.3551), vec2(0.5700, 0.3261), vec2(0.6281, 0.3031), + vec2(0.6769, 0.2784), vec2(0.7503, 0.2651), vec2(0.7999, 0.2504), vec2(0.8620, 0.2458), + // Bordo destro + vec2(0.8565, 0.3564), vec2(0.8585, 0.4328), vec2(0.8579, 0.5043), vec2(0.8628, 0.5807), + vec2(0.8640, 0.6594), vec2(0.8703, 0.7179), vec2(0.8680, 0.7879), + // Arco inferiore + vec2(0.8264, 0.8164), vec2(0.7829, 0.8452), vec2(0.7112, 0.8651), vec2(0.6513, 0.8823), + vec2(0.5675, 0.8988), vec2(0.5082, 0.9059), vec2(0.4341, 0.9007), vec2(0.3600, 0.8980), + vec2(0.3098, 0.8763), vec2(0.2447, 0.8536), vec2(0.1897, 0.8309), vec2(0.1363, 0.7941), + // Bordo sinistro + vec2(0.1328, 0.6995), vec2(0.1297, 0.6286), vec2(0.1256, 0.5398), vec2(0.1239, 0.4893), + vec2(0.1221, 0.4318), vec2(0.1208, 0.3685) +); + +// Palette di colori circensi vivaci +vec3 get_bulb_color(int idx) { + int c = idx % 6; + if (c == 0) return vec3(1.0, 0.25, 0.25); // Rosso rubino + if (c == 1) return vec3(1.0, 0.84, 0.15); // Giallo oro + if (c == 2) return vec3(0.25, 0.88, 0.40); // Verde smeraldo + if (c == 3) return vec3(0.25, 0.70, 1.00); // Blu ciano + if (c == 4) return vec3(0.95, 0.38, 0.90); // Magenta fucsia + return vec3(1.0, 0.55, 0.15); // Arancio caldo +} + +// Generatore pseudo-casuale deterministico per lampadina e tempo +float pseudo_rand(float n) { + return fract(sin(n) * 43758.5453123); +} + +void fragment() { + vec4 color = texture(TEXTURE, UV); + + if (sign_lights_active > 0.001) { + vec2 px = UV * vec2(880.0, 561.0); + + // Calcolo delle 5 lampadine attive nei 5 settori della cornice + // Durate leggermente sfalsate per evitare che si sincronizzino + const float durations[5] = float[5](1.3, 1.6, 1.9, 1.5, 2.1); + const int ranges_start[5] = int[5](0, 7, 15, 22, 30); + const int ranges_count[5] = int[5](7, 8, 7, 8, 7); + + for (int c = 0; c < 5; c++) { + float dur = durations[c] / (blink_speed / 2.2); + float step = floor(TIME / dur); + float sub_t = fract(TIME / dur); + // Campana morbida di accensione (fade-in -> picco -> fade-out) + float lit = sin(sub_t * 3.14159265); + + if (lit <= 0.15) { + continue; + } + + // Indice lampadina estratta pseudo-casualmente per questo settore e questo ciclo + float r_val = pseudo_rand(step * 17.13 + float(c) * 31.7); + int chosen_bulb = ranges_start[c] + int(floor(r_val * float(ranges_count[c]))); + chosen_bulb = clamp(chosen_bulb, 0, 36); + + vec2 delta = px - sign_bulbs[chosen_bulb] * vec2(880.0, 561.0); + float r = length(delta); + + // Nucleo piccolo e brillante ~5px + alone diffuso ~12px + float core = 1.0 - smoothstep(2.0, 6.0, r); + float halo = exp(-r / 7.5); + float intensity = lit * sign_lights_active; + vec3 bulb_col = get_bulb_color(chosen_bulb); + + // Colorazione del nucleo e alone additivo + color.rgb = mix(color.rgb, bulb_col, core * intensity); + color.rgb += bulb_col * (halo * 0.75 * intensity * (1.0 - core)); + color.a = max(color.a, halo * 0.5 * intensity); + } + } + + color.rgb = min(color.rgb, vec3(1.0)); + COLOR = color; +} diff --git a/menus/scripts/components/menu_ui.gd b/menus/scripts/components/menu_ui.gd index 3fbfe7f..0d92355 100644 --- a/menus/scripts/components/menu_ui.gd +++ b/menus/scripts/components/menu_ui.gd @@ -6,6 +6,7 @@ const DISPLAY_FONT := preload("res://shared/assets/fonts/NotoSerifDisplay-Conden const MENU_SHADER := preload("res://menus/assets/shaders/ambient_drift.gdshader") const TENT_STAR_SHADER := preload("res://menus/assets/shaders/tent_star_glow.gdshader") const TENT_LIGHTS_SHADER := preload("res://menus/assets/shaders/tent_lights.gdshader") +const SIGN_LIGHTS_SHADER := preload("res://menus/assets/shaders/circus_sign_lights.gdshader") # Posizione e ingombro condivisi della freccia indietro in tutte le schermate. const BACK_BUTTON_CENTER := Vector2(0.12, 0.14) @@ -52,6 +53,13 @@ static func tent_material(garland_strength := 0.0) -> ShaderMaterial: return material +static func sign_lights_material() -> ShaderMaterial: + var material := ShaderMaterial.new() + material.shader = SIGN_LIGHTS_SHADER + material.set_shader_parameter("sign_lights_active", 0.0) + return material + + static func add_image(parent: Control, texture: Texture2D, center: Vector2, normalized_size: Vector2, material: Material = null) -> TextureRect: var image := TextureRect.new() image.texture = texture diff --git a/menus/scripts/screens/p1_home_screen.gd b/menus/scripts/screens/p1_home_screen.gd index 0fb7e81..f163f9f 100644 --- a/menus/scripts/screens/p1_home_screen.gd +++ b/menus/scripts/screens/p1_home_screen.gd @@ -341,6 +341,8 @@ func _add_sign() -> void: _add_sign_layer(rig, CIRCUS_SIGN_ROPES) var panel := _add_sign_layer(rig, CIRCUS_SIGN_PANEL) + var sign_mat := MenuUI.sign_lights_material() + panel.material = sign_mat var final_y := panel.position.y panel.position.y = final_y - panel.size.y * 1.15 @@ -353,7 +355,13 @@ func _add_sign() -> void: # L'impatto รจ sincronizzato con la massima estensione, prima del ritorno elastico. tween.tween_callback(func(): sign_landed.emit()) tween.tween_property(panel, "position:y", final_y, 0.50).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) - tween.tween_callback(func(): sign_bounced.emit()) + tween.tween_callback(func(): + sign_bounced.emit() + # Attiva le lucine colorate dell'insegna con una dolce dissolvenza solo a fine animazione + var lights_tween := create_tween() + lights_tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) + lights_tween.tween_method(func(val: float): sign_mat.set_shader_parameter("sign_lights_active", val), 0.0, 1.0, 0.6).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) + ) func _add_sign_layer(parent: Control, texture: Texture2D) -> TextureRect: