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("Costruzione Rotaie") @export var distanza_traversine: float = 1.0 @export var modello_traversina: PackedScene @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 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 _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 _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) fuoco_root.global_position = treno_istanziato.global_position + Vector3(offset_x, 0.5, offset_z) 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 var pos_centro = to_global(curve.sample_baked(progresso_treno, true)) 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: treno_istanziato.global_position = pos_centro treno_istanziato.look_at(pos_davanti, Vector3.UP) treno_istanziato.rotate_y(PI) if gruppo_camere: # Nessun ritardo, nessuna molla: posizione e rotazione sono incollate a quelle del treno! gruppo_camere.global_position = pos_centro gruppo_camere.global_basis = treno_istanziato.global_basis 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) fuoco_root.global_position = posizione_marker + Vector3(offset_x, 0.5, offset_z) 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_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)