first commit

This commit is contained in:
Matteo Sonaglioni
2026-03-10 23:54:57 +01:00
commit f7b334b21c
298 changed files with 30899 additions and 0 deletions

330
Scripts/Binari.gd Normal file
View File

@@ -0,0 +1,330 @@
extends Path3D
@export_group("Impostazioni Treno")
@export var velocita_treno: float = 6.0
@export var in_movimento: bool = true
@export var modello_treno: PackedScene
@export var distanza_assi: float = 3.0
@export var larghezza_binari: float = 1.2
@export var gruppo_camere: Node3D
@export_group("Controlli Manuali")
@export var velocita_max: float = 15.0
@export var velocita_min: float = -5.0
@export var accelerazione_manuale: float = 5.0
@export_group("Fermate (Stazioni)")
@export var abilita_fermate: bool = true
@export var tempo_di_sosta: float = 4.0
@export var distanza_frenata: float = 15.0
@export var tempo_ripartenza: float = 3.0
@export var scena_fuochi: PackedScene
var colori_fuochi: Array[Color] = [
Color(1.0, 0.2, 0.2), # Rosso vivo
Color(0.2, 1.0, 0.2), # Verde lime
Color(0.3, 0.5, 1.0), # Blu cielo
Color(1.0, 0.8, 0.1), # Oro
Color(0.8, 0.2, 1.0), # Viola
Color(0.1, 1.0, 0.9) # Ciano
]
@export_group("Movimento e Oscillazione")
@export_range(0.0, 1.0) var intensita_oscillazione_base: float = 0.5
@export_group("Costruzione Rotaie")
@export var distanza_traversine: float = 1.0
@export var modello_traversina: PackedScene
var treno_istanziato: Node3D
var progresso_treno: float = 0.0
var tempo_oscillazione: float = 0.0
var offsets_fermate: Array[float] = []
var posizioni_fermate: Array[Vector3] = []
var fermata_in_corso: bool = false
var indice_prossima_fermata: int = 0
var moltiplicatore_stazione: float = 1.0
var in_ripartenza: bool = false
var tween_ripartenza: Tween
func _ready() -> void:
if curve != null and curve.get_baked_length() > 0:
_costruisci_rotaie_da_zero()
_costruisci_treno()
_calcola_fermate()
else:
print("ATTENZIONE: Devi prima disegnare i punti del Path3D!")
func _calcola_fermate() -> void:
var fermate_temp = []
for figlio in get_children():
if figlio is Marker3D:
var offset = curve.get_closest_offset(figlio.position)
fermate_temp.append({
"offset": offset,
"posizione": figlio.global_position
})
fermate_temp.sort_custom(func(a, b): return a["offset"] < b["offset"])
for dati in fermate_temp:
offsets_fermate.append(dati["offset"])
posizioni_fermate.append(dati["posizione"])
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and not event.echo:
if event.keycode == KEY_T:
_teletrasporta_prossima_fermata()
elif event.keycode == KEY_F:
_test_fuochi_manuale()
func _test_fuochi_manuale() -> void:
if scena_fuochi == null or treno_istanziato == null: return
print("Lancio fuochi d'artificio di test!")
var num_fuochi = randi_range(5, 8)
for i in range(num_fuochi):
var fuoco_root = scena_fuochi.instantiate()
get_tree().current_scene.add_child(fuoco_root)
var offset_x = randf_range(-6.0, 6.0)
var offset_z = randf_range(-6.0, 6.0)
# 1. POSIZIONA
fuoco_root.global_position = treno_istanziato.global_position + Vector3(offset_x, 0.5, offset_z)
# 2. COLORA E ACCENDI
if fuoco_root.has_method("imposta_colore"):
fuoco_root.imposta_colore(colori_fuochi.pick_random())
if fuoco_root.has_method("accendi"):
fuoco_root.accendi()
await get_tree().create_timer(randf_range(0.2, 0.6)).timeout
func _teletrasporta_prossima_fermata() -> void:
if offsets_fermate.size() == 0 or fermata_in_corso or in_ripartenza:
return
var target_offset = offsets_fermate[indice_prossima_fermata]
progresso_treno = target_offset
_esegui_fermata(velocita_treno >= 0)
func _physics_process(delta: float) -> void:
_gestione_input_movimento(delta)
_gestione_movimento_treno(delta)
func _gestione_input_movimento(delta: float) -> void:
if not in_movimento: return
if Input.is_action_pressed("ui_up"):
velocita_treno += accelerazione_manuale * delta
elif Input.is_action_pressed("ui_down"):
velocita_treno -= accelerazione_manuale * delta
velocita_treno = clamp(velocita_treno, velocita_min, velocita_max)
func _gestione_movimento_treno(delta: float) -> void:
if in_movimento and treno_istanziato and curve:
var lunghezza_totale = curve.get_baked_length()
if lunghezza_totale <= 0: return
if not fermata_in_corso:
var vecchio_progresso = progresso_treno
if abilita_fermate and offsets_fermate.size() > 0 and not in_ripartenza:
var target_offset = offsets_fermate[indice_prossima_fermata]
var dist = target_offset - progresso_treno
dist = wrapf(dist + lunghezza_totale / 2.0, 0.0, lunghezza_totale) - lunghezza_totale / 2.0
if abs(dist) < distanza_frenata and sign(dist) == sign(velocita_treno):
var t = abs(dist) / distanza_frenata
moltiplicatore_stazione = max(smoothstep(0.0, 1.0, t), 0.05)
else:
moltiplicatore_stazione = 1.0
var velocita_attuale = velocita_treno * moltiplicatore_stazione
progresso_treno += velocita_attuale * delta
if abilita_fermate and offsets_fermate.size() > 0:
var target_offset = offsets_fermate[indice_prossima_fermata]
var superato_avanti = (velocita_attuale > 0 and vecchio_progresso < target_offset and progresso_treno >= target_offset)
var superato_indietro = (velocita_attuale < 0 and vecchio_progresso > target_offset and progresso_treno <= target_offset)
if superato_avanti or superato_indietro:
progresso_treno = target_offset
_esegui_fermata(velocita_attuale > 0)
if progresso_treno > lunghezza_totale:
progresso_treno -= lunghezza_totale
if offsets_fermate.size() > 0: indice_prossima_fermata = 0
elif progresso_treno < 0:
progresso_treno += lunghezza_totale
if offsets_fermate.size() > 0: indice_prossima_fermata = offsets_fermate.size() - 1
# 1. Il centro matematico perfetto e incontrovertibile del treno
var pos_centro = to_global(curve.sample_baked(progresso_treno, true))
# 2. Il punto in cui deve guardare (abbastanza lontano per non vibrare)
var prog_davanti = wrapf(progresso_treno + 2.0, 0.0, lunghezza_totale)
var pos_davanti = to_global(curve.sample_baked(prog_davanti, true))
if pos_centro.distance_to(pos_davanti) > 0.01:
# Posizionamento esatto, zero vibrazioni laterali
treno_istanziato.global_position = pos_centro
treno_istanziato.look_at(pos_davanti, Vector3.UP)
treno_istanziato.rotate_y(PI)
if gruppo_camere:
# La telecamera segue il punto perfetto
gruppo_camere.global_position = gruppo_camere.global_position.lerp(pos_centro, delta * 12.0)
if not fermata_in_corso:
var vel_reale = abs(velocita_treno * moltiplicatore_stazione)
tempo_oscillazione += delta * vel_reale * 2.0
var ampiezza_z = 0.006 * intensita_oscillazione_base
var ampiezza_x = 0.004 * intensita_oscillazione_base
treno_istanziato.rotation.z += sin(tempo_oscillazione) * ampiezza_z
treno_istanziato.rotation.x += cos(tempo_oscillazione * 0.8) * ampiezza_x
func _esegui_fermata(andando_avanti: bool = true) -> void:
fermata_in_corso = true
moltiplicatore_stazione = 0.0
var indice_stazione_attuale = indice_prossima_fermata
if andando_avanti:
indice_prossima_fermata += 1
if indice_prossima_fermata >= offsets_fermate.size():
indice_prossima_fermata = 0
else:
indice_prossima_fermata -= 1
if indice_prossima_fermata < 0:
indice_prossima_fermata = offsets_fermate.size() - 1
if scena_fuochi != null:
var posizione_marker = posizioni_fermate[indice_stazione_attuale]
var num_fuochi = randi_range(5, 8)
for i in range(num_fuochi):
var fuoco_root = scena_fuochi.instantiate()
get_tree().current_scene.add_child(fuoco_root)
var offset_x = randf_range(-5.0, 5.0)
var offset_z = randf_range(-5.0, 5.0)
# 1. POSIZIONA
fuoco_root.global_position = posizione_marker + Vector3(offset_x, 0.5, offset_z)
# 2. COLORA E ACCENDI
if fuoco_root.has_method("imposta_colore"):
fuoco_root.imposta_colore(colori_fuochi.pick_random())
if fuoco_root.has_method("accendi"):
fuoco_root.accendi()
await get_tree().create_timer(randf_range(0.3, 0.8)).timeout
await get_tree().create_timer(tempo_di_sosta).timeout
fermata_in_corso = false
in_ripartenza = true
if tween_ripartenza and tween_ripartenza.is_valid():
tween_ripartenza.kill()
tween_ripartenza = create_tween()
tween_ripartenza.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tween_ripartenza.tween_property(self, "moltiplicatore_stazione", 1.0, tempo_ripartenza)
tween_ripartenza.tween_callback(func(): in_ripartenza = false)
func _costruisci_rotaie_da_zero() -> void:
var mat_legno = StandardMaterial3D.new()
mat_legno.albedo_color = Color(0.35, 0.2, 0.1)
var mat_ferro = StandardMaterial3D.new()
mat_ferro.albedo_color = Color(0.6, 0.6, 0.65)
mat_ferro.metallic = 0.8
var mesh_traversina_default = BoxMesh.new()
mesh_traversina_default.size = Vector3(larghezza_binari + 0.6, 0.1, 0.3)
var mesh_binario = BoxMesh.new()
mesh_binario.size = Vector3(0.1, 0.15, distanza_traversine + 0.05)
var lunghezza_totale = curve.get_baked_length()
var numero_pezzi = int(lunghezza_totale / distanza_traversine)
for i in range(numero_pezzi):
var distanza = i * distanza_traversine
var pezzo_binario = Node3D.new()
add_child(pezzo_binario)
var pos_attuale_locale = curve.sample_baked(distanza, true)
var distanza_avanti = distanza + 0.1
var pos_avanti_locale = Vector3.ZERO
if distanza_avanti > lunghezza_totale:
var pos_dietro = curve.sample_baked(distanza - 0.1, true)
pos_avanti_locale = pos_attuale_locale + (pos_attuale_locale - pos_dietro)
else:
pos_avanti_locale = curve.sample_baked(distanza_avanti, true)
pezzo_binario.position = pos_attuale_locale
var globale_attuale = to_global(pos_attuale_locale)
var globale_avanti = to_global(pos_avanti_locale)
if globale_attuale.distance_to(globale_avanti) > 0.001:
pezzo_binario.look_at(globale_avanti, Vector3.UP)
if modello_traversina != null:
var traversina_custom = modello_traversina.instantiate()
pezzo_binario.add_child(traversina_custom)
else:
var traversina = MeshInstance3D.new()
traversina.mesh = mesh_traversina_default
traversina.material_override = mat_legno
pezzo_binario.add_child(traversina)
var binario_sx = MeshInstance3D.new()
binario_sx.mesh = mesh_binario
binario_sx.material_override = mat_ferro
binario_sx.position = Vector3(-larghezza_binari / 2.0, 0.1, 0)
pezzo_binario.add_child(binario_sx)
var binario_dx = MeshInstance3D.new()
binario_dx.mesh = mesh_binario
binario_dx.material_override = mat_ferro
binario_dx.position = Vector3(larghezza_binari / 2.0, 0.1, 0)
pezzo_binario.add_child(binario_dx)
func _costruisci_treno() -> void:
if modello_treno != null:
treno_istanziato = modello_treno.instantiate()
add_child(treno_istanziato)
else:
_costruisci_trenino_default()
func _costruisci_trenino_default() -> void:
treno_istanziato = Node3D.new()
add_child(treno_istanziato)
var mat_carrozzeria = StandardMaterial3D.new()
mat_carrozzeria.albedo_color = Color(0.8, 0.15, 0.15)
var mat_vetro = StandardMaterial3D.new()
mat_vetro.albedo_color = Color(0.2, 0.8, 1.0)
var base_treno = MeshInstance3D.new()
var mesh_base = BoxMesh.new()
mesh_base.size = Vector3(1.4, 0.8, 3.0)
base_treno.mesh = mesh_base
base_treno.material_override = mat_carrozzeria
base_treno.position = Vector3(0, 0.6, 0)
treno_istanziato.add_child(base_treno)
var cabina = MeshInstance3D.new()
var mesh_cabina = BoxMesh.new()
mesh_cabina.size = Vector3(1.4, 1.0, 1.2)
cabina.mesh = mesh_cabina
cabina.material_override = mat_vetro
cabina.position = Vector3(0, 1.5, -0.8)
treno_istanziato.add_child(cabina)
var ciminiera = MeshInstance3D.new()
var mesh_ciminiera = CylinderMesh.new()
mesh_ciminiera.top_radius = 0.2
mesh_ciminiera.bottom_radius = 0.3
mesh_ciminiera.height = 0.8
ciminiera.mesh = mesh_ciminiera
ciminiera.material_override = mat_carrozzeria
ciminiera.position = Vector3(0, 1.2, 1.0)
treno_istanziato.add_child(ciminiera)

1
Scripts/Binari.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cagpbu4pg5s1y

127
Scripts/CameraMove.gd Normal file
View File

@@ -0,0 +1,127 @@
extends Node3D
@export_group("Riferimenti Camere")
@export var camera_iso : Camera3D
@export var camera_frontale : Camera3D
@export var camera_laterale : Camera3D
@export var camera_laterale_2 : Camera3D # Nuova Camera 4
@export var camera_alta : Camera3D # Nuova Camera 5
@export_group("Movimento Camera 1")
@export var move_speed : float = 20.0
@export var rotation_speed : float = 0.2
@export_group("Cinematic Mode")
@export var tempo_cambio_camera : float = 10.0 # Quanti secondi dura ogni inquadratura
var is_moving_enabled : bool = true
# --- Variabili Cinematic Mode ---
var is_cinematic_mode : bool = false
var cinematic_timer : float = 0.0
var cinematic_index : int = 0
var array_camere : Array[Camera3D] = []
func _ready():
# Creiamo un array con le telecamere valide per ciclarle facilmente
if camera_iso: array_camere.append(camera_iso)
if camera_frontale: array_camere.append(camera_frontale)
if camera_laterale: array_camere.append(camera_laterale)
if camera_laterale_2: array_camere.append(camera_laterale_2)
if camera_alta: array_camere.append(camera_alta)
# All'inizio, assicuriamoci che la CameraIso sia quella attiva
if camera_iso:
camera_iso.make_current()
func _input(event):
# Cambio Camera Manuale e Cinematic Toggle
if event is InputEventKey and event.pressed and not event.echo:
# --- TASTO C: TOGGLE CINEMATIC MODE ---
if event.keycode == KEY_C:
is_cinematic_mode = !is_cinematic_mode
if is_cinematic_mode:
cinematic_timer = 0.0 # Azzera il timer
# Cerca qual è la camera attualmente attiva per iniziare il ciclo da lì
for i in range(array_camere.size()):
if array_camere[i].current:
cinematic_index = i
break
print("Cinematic Mode: ATTIVATA")
else:
print("Cinematic Mode: DISATTIVATA")
return # Usciamo per non eseguire altri controlli in questo frame
# --- CONTROLLI MANUALI (1-5) ---
var intervento_manuale = false
if event.keycode == KEY_1 and camera_iso:
camera_iso.make_current()
is_moving_enabled = true
intervento_manuale = true
elif event.keycode == KEY_2 and camera_frontale:
camera_frontale.make_current()
is_moving_enabled = false
intervento_manuale = true
elif event.keycode == KEY_3 and camera_laterale:
camera_laterale.make_current()
is_moving_enabled = false
intervento_manuale = true
elif event.keycode == KEY_4 and camera_laterale_2:
camera_laterale_2.make_current()
is_moving_enabled = false
intervento_manuale = true
elif event.keycode == KEY_5 and camera_alta:
camera_alta.make_current()
is_moving_enabled = false
intervento_manuale = true
# Se l'utente preme un numero per cambiare camera manualmente, spegni la cinematic mode
if intervento_manuale and is_cinematic_mode:
is_cinematic_mode = false
print("Cinematic Mode: DISATTIVATA (Intervento manuale)")
# Rotazione: Solo se il movimento è abilitato (Camera 1)
if is_moving_enabled:
if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
rotate_y(deg_to_rad(-event.relative.x * rotation_speed))
func _process(delta):
# --- LOGICA CINEMATIC MODE ---
if is_cinematic_mode and array_camere.size() > 0:
cinematic_timer += delta
if cinematic_timer >= tempo_cambio_camera:
cinematic_timer = 0.0
# Passa alla camera successiva e se è l'ultima ricomincia da zero
cinematic_index = (cinematic_index + 1) % array_camere.size()
var next_cam = array_camere[cinematic_index]
next_cam.make_current()
# Abilita il movimento WASD solo se la camera inquadrata è quella isometrica (la 1)
is_moving_enabled = (next_cam == camera_iso)
# --- LOGICA DI MOVIMENTO WASD ---
# Se siamo in camera 2, 3, 4 o 5, non processare il movimento WASD
if not is_moving_enabled:
return
var input_dir = Vector3.ZERO
if Input.is_key_pressed(KEY_W): input_dir.z -= 1
if Input.is_key_pressed(KEY_S): input_dir.z += 1
if Input.is_key_pressed(KEY_A): input_dir.x -= 1
if Input.is_key_pressed(KEY_D): input_dir.x += 1
if input_dir != Vector3.ZERO:
input_dir = input_dir.normalized()
var forward = transform.basis.z
var right = transform.basis.x
forward.y = 0
right.y = 0
var motion = (forward * input_dir.z + right * input_dir.x).normalized()
position += motion * move_speed * delta

View File

@@ -0,0 +1 @@
uid://b8x6nf7u6iopv

253
Scripts/Day_night_Cycle.gd Normal file
View File

@@ -0,0 +1,253 @@
extends Node3D
@export_group("Luce Direzionale e Ambiente")
@export var color_mattina: Color = Color(1.0, 0.9, 0.8, 1.0)
@export var color_pomeriggio: Color = Color(1.0, 0.6, 0.2, 1.0)
@export var color_notte: Color = Color(0.1, 0.1, 0.3, 1.0)
@export_group("Colori Cielo - Alto (Top)")
@export var sky_top_mattina: Color = Color(0.35, 0.65, 0.9, 1.0)
@export var sky_top_pomeriggio: Color = Color(0.7, 0.45, 0.2, 1.0)
@export var sky_top_notte: Color = Color(0.05, 0.05, 0.15, 1.0)
@export_group("Colori Cielo - Orizzonte (Horizon)")
@export var sky_horizon_mattina: Color = Color(0.75, 0.85, 0.95, 1.0)
@export var sky_horizon_pomeriggio: Color = Color(0.95, 0.65, 0.35, 1.0)
@export var sky_horizon_notte: Color = Color(0.15, 0.1, 0.25, 1.0)
@export_group("Ciclo Temporale")
@export_range(1.0, 3.0) var tempo_giorno: float = 1.0
@export var durata_transizione: float = 3.0
@export_range(0.0, 1.0) var energia_luce_notte: float = 0.3
@export_group("Materiali Extra")
@export var materiale_nebbia: ShaderMaterial
@export var materiale_pioggia_gocce: StandardMaterial3D
@export_group("Meteo (Pioggia & Fulmini)")
@export var colore_mood_pioggia: Color = Color(0.5, 0.6, 0.7, 1.0)
@export var tempo_min_fulmine: float = 5.0
@export var tempo_max_fulmine: float = 15.0
@export var colore_fulmine: Color = Color(1.0, 1.0, 0.8, 1.0)
@export var fulmini_min: int = 1
@export var fulmini_max: int = 3
@export var scala_min_fulmine: float = 1.0
@export var scala_max_fulmine: float = 3.0
# --- NUOVI PARAMETRI CINEMATIC MODE ---
@export_group("Cinematic Mode Ambiente")
@export var intervallo_eventi_meteo: float = 8.0 # Ogni quanti secondi cambia qualcosa da solo
@onready var sole: DirectionalLight3D = $DirectionalLight3D
@onready var ambiente: WorldEnvironment = $WorldEnvironment
@onready var menu_giornata: OptionButton = $CanvasLayer/OptionButton
@onready var bottone_pioggia: CheckButton = $CanvasLayer/CheckButtonPioggia
@onready var particelle_pioggia: GPUParticles3D = $SubViewportContainer/SubViewport/World/Camere/PioggiaCadente
# --- NODI FULMINE ---
@onready var luce_lampo: DirectionalLight3D = $LuceLampo
@onready var sprite_fulmine: Sprite3D = $SpriteFulmine
var tween_giorno: Tween
var is_raining: bool = false
var intensita_pioggia: float = 0.0
var timer_prossimo_fulmine: float = 0.0
# Variabili Cinematic Mode
var is_cinematic_mode: bool = false
var timer_cinematic_env: float = 0.0
var sto_cambiando_in_cinematic: bool = false # Serve per non disattivare la modalità quando lo script clicca i bottoni da solo
func _ready() -> void:
if menu_giornata:
menu_giornata.clear()
menu_giornata.add_item("1 - Mattina")
menu_giornata.add_item("2 - Pomeriggio")
menu_giornata.add_item("3 - Notte")
menu_giornata.selected = int(tempo_giorno) - 1
menu_giornata.item_selected.connect(_on_giornata_selezionata)
if bottone_pioggia:
bottone_pioggia.toggled.connect(_on_pioggia_toggled)
if particelle_pioggia:
particelle_pioggia.emitting = false
_resetta_timer_fulmine()
# --- ASCOLTO TASTIERA PER CINEMATIC MODE ---
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and not event.echo:
# Attiva/Disattiva con C
if event.keycode == KEY_C:
is_cinematic_mode = !is_cinematic_mode
if is_cinematic_mode:
timer_cinematic_env = intervallo_eventi_meteo # Fa scattare un evento quasi subito
print("Ambiente Cinematic: ATTIVATA")
else:
print("Ambiente Cinematic: DISATTIVATA")
return
# Se premi le camere, si ferma la cinematic mode
if event.keycode in [KEY_1, KEY_2, KEY_3, KEY_4, KEY_5]:
if is_cinematic_mode:
is_cinematic_mode = false
print("Ambiente Cinematic: DISATTIVATA (Cambio camera manuale)")
func _on_giornata_selezionata(index: int) -> void:
# Se l'utente clicca la tendina a mano, spegni la cinematic mode
if not sto_cambiando_in_cinematic and is_cinematic_mode:
is_cinematic_mode = false
print("Ambiente Cinematic: DISATTIVATA (Orario cambiato manualmente)")
var target_tempo = float(index + 1)
if tween_giorno and tween_giorno.is_valid():
tween_giorno.kill()
tween_giorno = create_tween()
tween_giorno.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tween_giorno.tween_property(self, "tempo_giorno", target_tempo, durata_transizione)
func _on_pioggia_toggled(toggled_on: bool) -> void:
# Se l'utente clicca il bottone della pioggia a mano, spegni la cinematic mode
if not sto_cambiando_in_cinematic and is_cinematic_mode:
is_cinematic_mode = false
print("Ambiente Cinematic: DISATTIVATA (Pioggia cambiata manualmente)")
is_raining = toggled_on
if particelle_pioggia:
particelle_pioggia.emitting = is_raining
func _process(delta: float) -> void:
# --- LOGICA EVENTI CASUALI CINEMATIC MODE ---
if is_cinematic_mode:
timer_cinematic_env -= delta
if timer_cinematic_env <= 0.0:
_genera_evento_meteo_casuale()
# Reset timer con un po' di casualità per non renderlo troppo robotico
timer_cinematic_env = randf_range(intervallo_eventi_meteo * 0.8, intervallo_eventi_meteo * 1.5)
# --- LOGICA NORMALE AMBIENTE ---
var base_tint: Color
var base_sky_top: Color
var base_sky_horizon: Color
if is_raining:
intensita_pioggia = move_toward(intensita_pioggia, 1.0, delta * 0.5)
timer_prossimo_fulmine -= delta
if timer_prossimo_fulmine <= 0.0:
_scatena_fulmine()
_resetta_timer_fulmine()
else:
intensita_pioggia = move_toward(intensita_pioggia, 0.0, delta * 0.5)
if tempo_giorno <= 2.0:
var t = tempo_giorno - 1.0
base_tint = color_mattina.lerp(color_pomeriggio, t)
base_sky_top = sky_top_mattina.lerp(sky_top_pomeriggio, t)
base_sky_horizon = sky_horizon_mattina.lerp(sky_horizon_pomeriggio, t)
else:
var t = tempo_giorno - 2.0
base_tint = color_pomeriggio.lerp(color_notte, t)
base_sky_top = sky_top_pomeriggio.lerp(sky_top_notte, t)
base_sky_horizon = sky_horizon_pomeriggio.lerp(sky_horizon_notte, t)
var final_tint = base_tint.lerp(base_tint * colore_mood_pioggia, intensita_pioggia)
var final_sky_top = base_sky_top.lerp(base_sky_top * colore_mood_pioggia, intensita_pioggia)
var final_sky_horizon = base_sky_horizon.lerp(base_sky_horizon * colore_mood_pioggia, intensita_pioggia)
var night_val = clamp(tempo_giorno - 2.0, 0.0, 1.0)
var colore_riflesso = Color(0.4, 0.5, 0.6, 1.0)
var colore_gocce = final_sky_horizon.lerp(colore_riflesso, 0.15)
colore_gocce = colore_gocce.lerp(colore_gocce * 0.4, night_val)
if materiale_pioggia_gocce:
materiale_pioggia_gocce.albedo_color = colore_gocce
if sole:
sole.light_color = final_tint
var target_energy = lerp(1.0, energia_luce_notte, night_val)
sole.light_energy = lerp(target_energy, target_energy * 0.4, intensita_pioggia)
if ambiente and ambiente.environment:
ambiente.environment.ambient_light_color = final_tint
ambiente.environment.fog_light_color = final_sky_horizon
var sky_mat = ambiente.environment.sky.sky_material as ShaderMaterial
if sky_mat:
sky_mat.set_shader_parameter("sky_top_color", final_sky_top)
sky_mat.set_shader_parameter("sky_horizon_color", final_sky_horizon)
sky_mat.set_shader_parameter("sun_color", final_tint)
sky_mat.set_shader_parameter("night_intensity", night_val)
if materiale_nebbia:
materiale_nebbia.set_shader_parameter("night_intensity", night_val)
materiale_nebbia.set_shader_parameter("sun_color", final_tint)
# --- FUNZIONE GENERATORE CASUALE ---
func _genera_evento_meteo_casuale() -> void:
sto_cambiando_in_cinematic = true # Avvisiamo il codice che stiamo facendo modifiche "automatiche"
var scelta = randi() % 3
if scelta == 0:
# Cambia orario
var nuovo_orario = randi() % 3
if menu_giornata:
menu_giornata.selected = nuovo_orario
_on_giornata_selezionata(nuovo_orario)
elif scelta == 1:
# Cambia meteo (accende o spegne la pioggia)
var piove = randf() > 0.5
if bottone_pioggia:
bottone_pioggia.set_pressed_no_signal(piove)
_on_pioggia_toggled(piove)
elif scelta == 2:
# Forza un fulmine (se non piove, fa iniziare a piovere per dare senso al fulmine!)
if not is_raining:
if bottone_pioggia:
bottone_pioggia.set_pressed_no_signal(true)
_on_pioggia_toggled(true)
_scatena_fulmine()
sto_cambiando_in_cinematic = false # Fine modifiche automatiche
# --- FUNZIONI FULMINI ---
func _resetta_timer_fulmine() -> void:
timer_prossimo_fulmine = randf_range(tempo_min_fulmine, tempo_max_fulmine)
func _scatena_fulmine() -> void:
if luce_lampo:
var tween_luce = create_tween()
luce_lampo.light_energy = 5.0
tween_luce.tween_interval(0.05)
tween_luce.tween_callback(func(): luce_lampo.light_energy = 0.0)
tween_luce.tween_interval(0.05)
tween_luce.tween_callback(func(): luce_lampo.light_energy = 2.0)
tween_luce.tween_method(func(val): luce_lampo.light_energy = val, 2.0, 0.0, 0.2)
var numero_scariche = randi_range(fulmini_min, fulmini_max)
for i in numero_scariche:
if sprite_fulmine:
var angolo_casuale = randf() * PI * 2.0
var distanza = 20.0
sprite_fulmine.position = Vector3(cos(angolo_casuale) * distanza, 20.0, sin(angolo_casuale) * distanza)
sprite_fulmine.modulate = colore_fulmine
sprite_fulmine.modulate.a = 1.0
var s = randf_range(scala_min_fulmine, scala_max_fulmine)
sprite_fulmine.scale = Vector3(s, s, s)
var tween_sprite = create_tween()
tween_sprite.tween_interval(0.1)
tween_sprite.tween_property(sprite_fulmine, "modulate:a", 0.0, 0.2)
if numero_scariche > 1:
await get_tree().create_timer(randf_range(0.05, 0.15)).timeout

View File

@@ -0,0 +1 @@
uid://dcaxomfkulrig

47
Scripts/Leaf_Test1.gd Normal file
View File

@@ -0,0 +1,47 @@
@tool
extends MultiMeshInstance3D
@export var mesh_volume: MeshInstance3D : # Trascina qui il tuo volume low poly
set(val):
mesh_volume = val
generate_leaves()
@export var scale_multiplier: float = 1.5 # Per rendere i piani grandi come in foto
func generate_leaves():
if not mesh_volume or not mesh_volume.mesh:
return
var mdt = MeshDataTool.new()
mdt.create_from_surface(mesh_volume.mesh, 0)
var face_count = mdt.get_face_count()
# Inizializza il MultiMesh
multimesh.instance_count = face_count
for i in range(face_count):
# 1. Trova il centro della faccia
var v1 = mdt.get_vertex(mdt.get_face_vertex(i, 0))
var v2 = mdt.get_vertex(mdt.get_face_vertex(i, 1))
var v3 = mdt.get_vertex(mdt.get_face_vertex(i, 2))
var center = (v1 + v2 + v3) / 3.0
# 2. Ottieni la normale della faccia per orientare il piano
var normal = mdt.get_face_normal(i)
# 3. Crea la trasformazione
# Usiamo 'look_at' per far sì che il piano sia parallelo alla faccia
var pos = center
var target = pos + normal
var xform = Transform3D().looking_at(normal, Vector3.UP)
# Applichiamo posizione e scala
xform.origin = pos
xform = xform.scaled(Vector3.ONE * scale_multiplier)
# 4. Applica al MultiMesh
multimesh.set_instance_transform(i, xform)
# Opzionale: nascondi il volume originale se vuoi vedere solo i piani
# mesh_volume.visible = false

View File

@@ -0,0 +1 @@
uid://dxgc5kjl1048

121
Scripts/binari_treno.gd Normal file
View File

@@ -0,0 +1,121 @@
extends Path3D
@export_group("Impostazioni Treno")
@export var velocita_treno: float = 6.0
@export var in_movimento: bool = true
@export_group("Costruzione Rotaie")
@export var distanza_traversine: float = 1.0 # Ogni quanti metri mettere un asse di legno
var nodo_treno: PathFollow3D
func _ready() -> void:
# Controlla se hai disegnato il percorso
if curve == null or curve.get_baked_length() == 0:
print("ATTENZIONE: Devi prima disegnare i punti del Path3D!")
return
_costruisci_rotaie_da_zero()
_costruisci_treno_da_zero()
func _costruisci_rotaie_da_zero() -> void:
# 1. Creiamo i materiali (Colori) per le rotaie
var mat_legno = StandardMaterial3D.new()
mat_legno.albedo_color = Color(0.35, 0.2, 0.1) # Marrone scuro
var mat_ferro = StandardMaterial3D.new()
mat_ferro.albedo_color = Color(0.6, 0.6, 0.65) # Grigio metallico
mat_ferro.metallic = 0.8
# 2. Creiamo le forme geometriche base (Box)
var mesh_traversina = BoxMesh.new()
mesh_traversina.size = Vector3(1.8, 0.1, 0.3)
var mesh_binario = BoxMesh.new()
# La lunghezza del binario è uguale alla distanza tra le traversine per connettersi perfettamente
mesh_binario.size = Vector3(0.1, 0.15, distanza_traversine + 0.05)
# 3. Calcoliamo quanti pezzi servono
var lunghezza_totale = curve.get_baked_length()
var numero_pezzi = int(lunghezza_totale / distanza_traversine)
# 4. Piazziamo i pezzi lungo la curva
for i in range(numero_pezzi):
var distanza = i * distanza_traversine
var transform_punto = curve.sample_baked_with_rotation(distanza, true, true)
# Nodo "Pezzo" che fa da raggruppamento
var pezzo_binario = Node3D.new()
pezzo_binario.transform = transform_punto
add_child(pezzo_binario)
# Creiamo la traversina in legno
var traversina = MeshInstance3D.new()
traversina.mesh = mesh_traversina
traversina.material_override = mat_legno
pezzo_binario.add_child(traversina)
# Creiamo il binario sinistro
var binario_sx = MeshInstance3D.new()
binario_sx.mesh = mesh_binario
binario_sx.material_override = mat_ferro
binario_sx.position = Vector3(-0.6, 0.1, 0) # Spostato a sinistra e leggermente in alto
pezzo_binario.add_child(binario_sx)
# Creiamo il binario destro
var binario_dx = MeshInstance3D.new()
binario_dx.mesh = mesh_binario
binario_dx.material_override = mat_ferro
binario_dx.position = Vector3(0.6, 0.1, 0) # Spostato a destra e leggermente in alto
pezzo_binario.add_child(binario_dx)
func _costruisci_treno_da_zero() -> void:
# 1. Creiamo il PathFollow che gestisce il movimento
nodo_treno = PathFollow3D.new()
nodo_treno.rotation_mode = PathFollow3D.ROTATION_Y # IMPORTANTE: Evita che il treno si inclini di lato
nodo_treno.cubic_interp = false # Rende la velocità costante ed elimina i tremolii
nodo_treno.loop = true
add_child(nodo_treno)
# 2. Materiali del treno
var mat_carrozzeria = StandardMaterial3D.new()
mat_carrozzeria.albedo_color = Color(0.8, 0.15, 0.15) # Rosso acceso
var mat_vetro = StandardMaterial3D.new()
mat_vetro.albedo_color = Color(0.2, 0.8, 1.0) # Azzurro
# 3. Costruiamo la Base del treno
var base_treno = MeshInstance3D.new()
var mesh_base = BoxMesh.new()
mesh_base.size = Vector3(1.4, 0.8, 3.0)
base_treno.mesh = mesh_base
base_treno.material_override = mat_carrozzeria
base_treno.position = Vector3(0, 0.6, 0) # Alzato per stare sopra le rotaie
nodo_treno.add_child(base_treno)
# 4. Costruiamo la Cabina del macchinista
var cabina = MeshInstance3D.new()
var mesh_cabina = BoxMesh.new()
mesh_cabina.size = Vector3(1.4, 1.0, 1.2)
cabina.mesh = mesh_cabina
cabina.material_override = mat_vetro
cabina.position = Vector3(0, 1.5, -0.8) # Messa dietro e in alto
nodo_treno.add_child(cabina)
# 5. Costruiamo la Ciminiera (davanti)
var ciminiera = MeshInstance3D.new()
var mesh_ciminiera = CylinderMesh.new()
mesh_ciminiera.top_radius = 0.2
mesh_ciminiera.bottom_radius = 0.3
mesh_ciminiera.height = 0.8
ciminiera.mesh = mesh_ciminiera
ciminiera.material_override = mat_carrozzeria
ciminiera.position = Vector3(0, 1.2, 1.0)
nodo_treno.add_child(ciminiera)
func _process(delta: float) -> void:
# Se in movimento, fa avanzare automaticamente il nodo_treno lungo la curva
if in_movimento and nodo_treno:
nodo_treno.progress += velocita_treno * delta

View File

@@ -0,0 +1 @@
uid://c2ec8k6lbx5cb

View File

@@ -0,0 +1,123 @@
@tool
class_name EdgeDectionCompositor
extends CompositorEffect
var rd: RenderingDevice
var shader: RID
var pipeline: RID
var parameter_storage_buffer := RID()
func _init() -> void:
effect_callback_type = CompositorEffect.EFFECT_CALLBACK_TYPE_POST_OPAQUE
rd = RenderingServer.get_rendering_device()
RenderingServer.call_on_render_thread(_initialize_compute)
var data := PackedFloat32Array()
data.resize(20)
data.fill(0)
var parameter_data := data.to_byte_array()
parameter_storage_buffer = rd.storage_buffer_create(parameter_data.size(), parameter_data)
# System notifications, we want to react on the notification that
# alerts us we are about to be destroyed.
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
if shader.is_valid():
# Freeing our shader will also free any dependents such as the pipeline!
rd.free_rid(shader)
#region Code in this region runs on the rendering thread.
# Compile our shader at initialization.
func _initialize_compute() -> void:
rd = RenderingServer.get_rendering_device()
if not rd:
return
# Compile our shader.
var shader_file := load("res://Shaders/PostProcessing/edge_detection_shader.glsl")
var shader_spirv: RDShaderSPIRV = shader_file.get_spirv()
shader = rd.shader_create_from_spirv(shader_spirv)
if shader.is_valid():
pipeline = rd.compute_pipeline_create(shader)
# Called by the rendering thread every frame.
func _render_callback(p_effect_callback_type: EffectCallbackType, p_render_data: RenderData) -> void:
if rd and p_effect_callback_type == EFFECT_CALLBACK_TYPE_POST_OPAQUE and pipeline.is_valid():
# Get our render scene buffers object, this gives us access to our render buffers.
# Note that implementation differs per renderer hence the need for the cast.
var render_scene_buffers := p_render_data.get_render_scene_buffers()
if render_scene_buffers:
# Get our render size, this is the 3D render resolution!
var size = render_scene_buffers.get_internal_size()
if size.x == 0 and size.y == 0:
return
@warning_ignore("integer_division")
var x_groups : int = (size.x - 1.0) / 8.0 + 1.0
@warning_ignore("integer_division")
var y_groups : int = (size.y - 1.0) / 8.0 + 1.0
var z_groups := 1.0
# Loop through views just in case we're doing stereo rendering. No extra cost if this is mono.
var view_count: int = render_scene_buffers.get_view_count()
for view in view_count:
# Get the RID for our color image, we will be reading from and writing to it.
var input_image: RID = render_scene_buffers.get_color_layer(view)
var input_depth: RID = render_scene_buffers.get_depth_layer(view)
var input_normal: RID = render_scene_buffers.get_texture("forward_clustered","normal_roughness")
var texture_sampler = RDSamplerState.new()
texture_sampler = rd.sampler_create(texture_sampler)
var parameters := PackedFloat32Array([size.x, size.y, 0.0, 0.0])
var inv_proj_mat := p_render_data.get_render_scene_data().get_cam_projection().inverse()
var inv_proj_mat_array := PackedVector4Array([inv_proj_mat.x, inv_proj_mat.y, inv_proj_mat.z, inv_proj_mat.w])
var parameter_data := parameters.to_byte_array()
parameter_data.append_array(inv_proj_mat_array.to_byte_array())
rd.buffer_update(parameter_storage_buffer, 0, parameter_data.size(), parameter_data)
var uniform_parameter := RDUniform.new()
uniform_parameter.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
uniform_parameter.binding = 0
uniform_parameter.add_id(parameter_storage_buffer)
# Create a uniform set, this will be cached, the cache will be cleared if our viewports configuration is changed.
var uniform_color := RDUniform.new()
uniform_color.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
uniform_color.binding = 1
uniform_color.add_id(input_image)
var uniform_depth := RDUniform.new()
uniform_depth.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
uniform_depth.binding = 2
uniform_depth.add_id(texture_sampler)
uniform_depth.add_id(input_depth)
var uniform_normal := RDUniform.new()
uniform_normal.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
uniform_normal.binding = 3
uniform_normal.add_id(texture_sampler)
uniform_normal.add_id(input_normal)
var uniform_set := UniformSetCacheRD.get_cache(shader, 0, [uniform_parameter, uniform_color, uniform_depth, uniform_normal])
# Run our compute shader.
var compute_list := rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
#rd.compute_list_set_push_constant(compute_list, push_constant.to_byte_array(), push_constant.size() * 4)
rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups)
rd.compute_list_end()
rd.free_rid(texture_sampler)
#endregion

View File

@@ -0,0 +1 @@
uid://brcimd12tx3dm

View File

@@ -0,0 +1,8 @@
extends GPUParticles3D
func _ready() -> void:
# Quando spawna, emette le particelle
emitting = true
# Si autodistrugge dopo che le particelle sono svanite
await get_tree().create_timer(lifetime + 0.5).timeout
queue_free()

View File

@@ -0,0 +1 @@
uid://c1wwiocqnv0qs

View File

@@ -0,0 +1,47 @@
extends Node3D
# Funzione chiamata dal treno per iniettare il colore scelto
func imposta_colore(colore_target: Color) -> void:
# Cerchiamo i 3 nodi specifici per nome
# Assicurati che i nomi nella tua scena siano ESATTAMENTE questi!
var razzo = $"1_RazzoIniziale"
var esplosione = $"2_EsplosionePrincipale"
var scie = $"3_ScieCadenti"
# 1. Il Razzo iniziale lo vogliamo BIANCO BRILLANTE (la "testa")
# Usiamo un bianco "sovraccarico" (valori > 1) per farlo brillare di più
_colora_nodo_particellare(razzo, Color(1.5, 1.5, 1.5))
# 2. L'esplosione principale la vogliamo BIANCA (i punti centrali dello scoppio)
_colora_nodo_particellare(esplosione, Color(1.2, 1.2, 1.2))
# 3. Le scie che cadono le vogliamo del COLORE SCELTO dal treno
_colora_nodo_particellare(scie, colore_target)
# Funzione helper per duplicare e colorare il materiale in modo sicuro
func _colora_nodo_particellare(nodo: GPUParticles3D, colore_da_applicare: Color) -> void:
if nodo and nodo.draw_pass_1:
var mesh = nodo.draw_pass_1 as QuadMesh
if mesh and mesh.material:
# --- IL SEGRETO E' QUI! ---
# Duplichiamo il materiale. Ora questo nodo ha una sua copia privata
# e non influenzerà gli altri fuochi d'artificio.
var mat_unico = mesh.material.duplicate() as StandardMaterial3D
# Impostiamo il colore e ci assicuriamo che brilli
mat_unico.albedo_color = colore_da_applicare
mat_unico.blend_mode = BaseMaterial3D.BLEND_MODE_ADD # Fondamentale per l'effetto luce
mat_unico.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
# Assegniamo il nuovo materiale unico alla mesh
mesh.material = mat_unico
# Funzione per far partire l'effetto
func accendi() -> void:
var razzo = $"1_RazzoIniziale"
if razzo:
razzo.emitting = true # Accende solo la miccia iniziale
# Tempo di vita totale aumentato per dare tempo alle scie di cadere
await get_tree().create_timer(6.0).timeout
queue_free()

View File

@@ -0,0 +1 @@
uid://buwbary2rs60d

View File

@@ -0,0 +1,10 @@
extends PathFollow3D
@export var velocita_treno: float = 5.0
@export var in_movimento: bool = true
func _process(delta: float) -> void:
if in_movimento:
# 'progress' è una variabile nativa di PathFollow3D
# Aumentandola, il nodo si sposta lungo la curva in automatico!
progress += velocita_treno * delta

View File

@@ -0,0 +1 @@
uid://bvn3yskdmvcuj

View File

@@ -0,0 +1,62 @@
@tool
extends MultiMeshInstance3D
# Trascina qui la MeshInstance3D che rappresenta il volume della tua chioma
@export var mesh_volume: MeshInstance3D
# Clicca su questa casella nell'Inspector per generare le foglie
@export var genera_foglie: bool = false:
set(val):
if val:
popola_chioma()
genera_foglie = false # Resetta il pulsante
# Scala delle singole foglie
@export var scala_foglie: Vector3 = Vector3(1, 1, 1)
func popola_chioma():
if not mesh_volume or not mesh_volume.mesh:
print("Errore: Assegna una MeshInstance3D valida a 'Mesh Volume'.")
return
if not multimesh:
print("Errore: Il nodo MultiMeshInstance3D non ha una risorsa MultiMesh assegnata.")
return
# Assicurati che il formato sia 3D per posizioni, rotazioni e scale
multimesh.transform_format = MultiMesh.TRANSFORM_3D
var mdt = MeshDataTool.new()
# Crea i dati dalla prima superficie della mesh (indice 0)
mdt.create_from_surface(mesh_volume.mesh, 0)
var numero_facce = mdt.get_face_count()
multimesh.instance_count = numero_facce
print("Generazione di ", numero_facce, " foglie...")
for i in range(numero_facce):
# Ottieni i tre vertici che formano la faccia
var v1 = mdt.get_vertex(mdt.get_face_vertex(i, 0))
var v2 = mdt.get_vertex(mdt.get_face_vertex(i, 1))
var v3 = mdt.get_vertex(mdt.get_face_vertex(i, 2))
# Calcola il centro della faccia
var centro_faccia = (v1 + v2 + v3) / 3.0
# Crea una trasformazione con posizione, rotazione casuale e scala
var transform = Transform3D()
transform.origin = centro_faccia
# Rotazione casuale per un look naturale
transform = transform.rotated(Vector3.UP, randf() * TAU) # Rotazione attorno all'asse Y
transform = transform.rotated(Vector3.RIGHT, randf() * TAU) # Rotazione attorno all'asse X
transform = transform.rotated(Vector3.FORWARD, randf() * TAU) # Rotazione attorno all'asse Z
# Applica la scala
transform = transform.scaled(scala_foglie)
# Imposta la trasformazione per questa istanza nel MultiMesh
multimesh.set_instance_transform(i, transform)
print("Foglie generate con successo!")

View File

@@ -0,0 +1 @@
uid://bj181l5u1b81x

View File

@@ -0,0 +1,49 @@
extends Node
@export_group("Gestione Palette")
@export var materiale_da_cambiare: ShaderMaterial # Trascina qui il tuo materiale dall'Inspector
@export var tempo_transizione: float = 2.0 # Quanto dura la sfumatura (in secondi)
@export var valore_riga_2: float = 0.2 # Il valore target della seconda riga
var palette_spostata: bool = false
var tween_palette: Tween
func _input(event: InputEvent) -> void:
# Usiamo il tasto P (Palette) per innescare l'effetto
if event is InputEventKey and event.pressed and not event.echo:
if event.keycode == KEY_P:
_cambia_palette_fluida()
func _cambia_palette_fluida() -> void:
if materiale_da_cambiare == null:
print("ERRORE: Non hai assegnato il materiale nell'Inspector!")
return
# Invertiamo lo stato
palette_spostata = !palette_spostata
# Decidiamo dove deve arrivare lo slider
var target_shift = valore_riga_2 if palette_spostata else 0.0
# Se c'è già un'animazione in corso (es. hai premuto due volte velocemente), la fermiamo
if tween_palette and tween_palette.is_valid():
tween_palette.kill()
# Creiamo la nuova animazione fluida
tween_palette = create_tween()
# Usiamo SINE e IN_OUT per renderla morbidissima in partenza e in arrivo
tween_palette.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
# Anima il parametro dello shader
tween_palette.tween_property(
materiale_da_cambiare,
"shader_parameter/palette_shift_y", # Sintassi magica per parlare con lo shader
target_shift,
tempo_transizione
)
if palette_spostata:
print("Transizione verso la Palette 2 iniziata...")
else:
print("Ritorno alla Palette 1 originale...")

View File

@@ -0,0 +1 @@
uid://bhdoxc2oak88u