From 7f7f9d6ae3cb18da10d82f66a9c3ea16f41993c3 Mon Sep 17 00:00:00 2001 From: Overside srl Date: Fri, 29 May 2026 18:16:16 +0200 Subject: [PATCH] add wagons --- core/biome_generator/rails.gd | 197 +++++++++++++++++- core/biome_generator/wagon_pool.gd | 6 + core/biome_generator/wagon_pool.tres | 9 + .../museum_biome_generator.tscn | 7 +- export_presets.cfg | 71 +++++++ tgcc/main_scene.tscn | 5 + 6 files changed, 286 insertions(+), 9 deletions(-) create mode 100644 core/biome_generator/wagon_pool.gd create mode 100644 core/biome_generator/wagon_pool.tres create mode 100644 export_presets.cfg diff --git a/core/biome_generator/rails.gd b/core/biome_generator/rails.gd index c90775b..924a4ae 100644 --- a/core/biome_generator/rails.gd +++ b/core/biome_generator/rails.gd @@ -1,31 +1,60 @@ extends Path3D @export_group("Train") -@export var train_speed: float = 6.0 -@export var is_inmotion: bool = true -@export var train_model: PackedScene +##train speed +@export var train_speed: float = 6.0 +##if true the train is in motion +@export var is_inmotion: bool = true +##the model for the locomotive +@export var train_model: PackedScene +##array of wagon scenes +@export var wagon_pool: Resource +##if true the number of wagons is random from 1 to wagon_count +@export var wagon_random_number: bool = true +##if wagon_random_number = true is used as max wagons +@export_range(0, 32, 1, "or_greater") var wagon_count: int = 3 +##the distance between two wagons +@export var wagon_gap: float = 0.4 +##the scale to calculate the distance of the bounds +@export_range(0.1, 2.0, 0.05, "or_greater") var wagon_spacing_scale: float = 0.5 +##override spacing using this value +@export var wagon_spacing_override: float = 0.0 +##distance of the rail axes @export var axes_distance: float = 3.0 -@export var rail_distance: float = 1.2 +##discance between rails +@export var rail_distance: float = 1.2 +##current camera @export var cameras: Node3D +##swing of the train during the ran @export_range(0.0, 1.0) var basic_swing: float = 0.1 @export_group("Rails Bulding") +##distance between sleepers of the rails @export var sleepers_distance: float = 1.0 +##nodel for the sleeper of the rail @export var sleepers_model: PackedScene @export_group("Manual Controls") +##max speed @export var speed_max: float = 15.0 -@export var speed_min: float = -5.0 +##min speed +@export var speed_min: float = -5.0 +##acceleration @export var manual_acceleration: float = 5.0 @export_group("Train Stops") +##if true the train stop the ran on stops @export var enable_stops: bool = true +##time of stop @export var stop_time: float = 4.0 -@export var brake_distance: float = 15.0 +##distance when the train start to brake +@export var brake_distance: float = 15.0 +##restart time @export var restart_time: float = 3.0 +##scene for fireworks @export var fireworks_scene: PackedScene - -var fireworks_colors: Array[Color] = [ +##array of colors for fireworks +@export var fireworks_colors: 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 @@ -48,9 +77,14 @@ var stop_multiply: float = 1.0 var is_restarting: bool = false var tween_restart: Tween var environment_config: EnvironmentConfig +var wagon_instances: Array[Node3D] = [] +var wagon_progress_offsets: Array[float] = [] func _ready() -> void: + randomize() + _cache_environment_config() + if curve != null and curve.get_baked_length() > 0: build_rails() build_train() @@ -153,6 +187,8 @@ func _snap_train_to_progress() -> void: cameras.global_position = center_position cameras.global_basis = train_instance.global_basis + _snap_wagons_to_progress(total_length) + func build_rails() -> void: var mat_wood = StandardMaterial3D.new() mat_wood.albedo_color = Color(0.35, 0.2, 0.1) @@ -323,6 +359,8 @@ func train_move(delta: float) -> void: if cameras: cameras.global_position = center_position cameras.global_basis = train_instance.global_basis + + _snap_wagons_to_progress(total_length) var real_speed = abs(train_speed * stop_multiply) var speed_factor = clamp(real_speed / max(speed_max, 0.001), 0.0, 1.0) @@ -401,6 +439,34 @@ func build_train() -> void: else: build_default_train() + var wagons = wagon_count + if wagon_random_number: + wagons = randi_range(1, wagon_count) + build_train_wagons(wagons) + +func build_train_wagons(wagon_number: int) -> void: + _clear_train_wagons() + if wagon_number <= 0 or wagon_pool == null or not "available_wagons" in wagon_pool: + return + if wagon_pool.available_wagons.is_empty(): + return + + for i in range(wagon_number): + var wagon_scene: PackedScene = wagon_pool.available_wagons.pick_random() + if wagon_scene == null: + continue + + var wagon: Node3D = wagon_scene.instantiate() as Node3D + if wagon == null: + push_warning("Wagon scene root must be a Node3D.") + continue + + add_child(wagon) + wagon_instances.append(wagon) + + _update_wagon_progress_offsets() + _snap_wagons_to_progress() + func build_default_train() -> void: train_instance = Node3D.new() add_child(train_instance) @@ -435,3 +501,118 @@ func build_default_train() -> void: stack.material_override = mat_body stack.position = Vector3(0, 1.2, 1.0) train_instance.add_child(stack) + +func _clear_train_wagons() -> void: + for wagon in wagon_instances: + if is_instance_valid(wagon): + wagon.queue_free() + wagon_instances.clear() + wagon_progress_offsets.clear() + +func _update_wagon_progress_offsets() -> void: + wagon_progress_offsets.clear() + if train_instance == null or wagon_instances.is_empty(): + return + + var train_length: float = _get_vehicle_length(train_instance) + var previous_length: float = train_length + var accumulated_distance: float = 0.0 + + for wagon in wagon_instances: + var wagon_length: float = _get_vehicle_length(wagon) + accumulated_distance += _get_wagon_spacing(previous_length, wagon_length) + wagon_progress_offsets.append(accumulated_distance) + previous_length = wagon_length + +func _get_wagon_spacing(previous_length: float, wagon_length: float) -> float: + if wagon_spacing_override > 0.0: + return wagon_spacing_override + wagon_gap + + var detected_spacing: float = previous_length * 0.5 + wagon_length * 0.5 + return detected_spacing * wagon_spacing_scale + wagon_gap + +func _snap_wagons_to_progress(total_length: float = 0.0) -> void: + if curve == null or wagon_instances.is_empty(): + return + + if total_length <= 0.0: + total_length = curve.get_baked_length() + if total_length <= 0.0: + return + + var direction: float = 1.0 + if train_speed < 0.0: + direction = -1.0 + + for i in range(wagon_instances.size()): + var wagon: Node3D = wagon_instances[i] + if not is_instance_valid(wagon): + continue + + var wagon_offset: float = float(i + 1) * 7.0 + if i < wagon_progress_offsets.size(): + wagon_offset = wagon_progress_offsets[i] + + var wagon_progress: float = train_progress - direction * wagon_offset + _snap_vehicle_to_progress(wagon, wagon_progress, total_length) + +func _snap_vehicle_to_progress(vehicle: Node3D, progress: float, total_length: float) -> void: + var vehicle_progress: float = wrapf(progress, 0.0, total_length) + var center_position: Vector3 = to_global(curve.sample_baked(vehicle_progress, true)) + var prog_forward: float = wrapf(vehicle_progress + 2.0, 0.0, total_length) + var forward_position: Vector3 = to_global(curve.sample_baked(prog_forward, true)) + + vehicle.global_position = center_position + if center_position.distance_to(forward_position) > 0.01: + vehicle.look_at(forward_position, Vector3.UP) + vehicle.rotate_y(PI) + +func _get_vehicle_length(vehicle: Node3D) -> float: + var bounds: AABB = _get_node_local_bounds(vehicle, vehicle) + if bounds.size == Vector3.ZERO: + return 7.0 + + return maxf(bounds.size.z, 0.1) + +func _get_node_local_bounds(root: Node3D, node: Node) -> AABB: + var result: AABB = AABB() + var has_bounds: bool = false + + var mesh_instance := node as MeshInstance3D + if mesh_instance != null and mesh_instance.mesh != null: + var mesh_bounds: AABB = mesh_instance.get_aabb() + var mesh_transform: Transform3D = root.global_transform.affine_inverse() * mesh_instance.global_transform + for corner in _get_aabb_corners(mesh_bounds): + var local_point: Vector3 = mesh_transform * corner + if has_bounds: + result = result.expand(local_point) + else: + result = AABB(local_point, Vector3.ZERO) + has_bounds = true + + for child in node.get_children(): + var child_bounds: AABB = _get_node_local_bounds(root, child) + if child_bounds.size == Vector3.ZERO: + continue + + if has_bounds: + result = result.merge(child_bounds) + else: + result = child_bounds + has_bounds = true + + return result + +func _get_aabb_corners(bounds: AABB) -> Array[Vector3]: + var start: Vector3 = bounds.position + var end: Vector3 = bounds.end + return [ + Vector3(start.x, start.y, start.z), + Vector3(end.x, start.y, start.z), + Vector3(start.x, end.y, start.z), + Vector3(end.x, end.y, start.z), + Vector3(start.x, start.y, end.z), + Vector3(end.x, start.y, end.z), + Vector3(start.x, end.y, end.z), + Vector3(end.x, end.y, end.z), + ] diff --git a/core/biome_generator/wagon_pool.gd b/core/biome_generator/wagon_pool.gd new file mode 100644 index 0000000..195284a --- /dev/null +++ b/core/biome_generator/wagon_pool.gd @@ -0,0 +1,6 @@ +## Resource that lists the wagon scenes available for train composition. +class_name WagonPool +extends Resource + +@export var name: String = "New Wagon Pool" +@export var available_wagons: Array[PackedScene] = [] diff --git a/core/biome_generator/wagon_pool.tres b/core/biome_generator/wagon_pool.tres new file mode 100644 index 0000000..d0b422b --- /dev/null +++ b/core/biome_generator/wagon_pool.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="WagonPool" format=3 uid="uid://bjnytgwt8tmf4"] + +[ext_resource type="PackedScene" uid="uid://dvk3bytqn3m5s" path="res://tgcc/train/main_train.tscn" id="1_t6q5r"] +[ext_resource type="Script" uid="uid://b6xk0gtn2pwa" path="res://core/biome_generator/wagon_pool.gd" id="2_x6bmc"] + +[resource] +script = ExtResource("2_x6bmc") +name = "Wagon Pool" +available_wagons = Array[PackedScene]([ExtResource("1_t6q5r")]) diff --git a/docs/museums/biome_generator/museum_biome_generator.tscn b/docs/museums/biome_generator/museum_biome_generator.tscn index 6e7eaba..45bc193 100644 --- a/docs/museums/biome_generator/museum_biome_generator.tscn +++ b/docs/museums/biome_generator/museum_biome_generator.tscn @@ -23,6 +23,7 @@ [ext_resource type="AudioStream" uid="uid://h5yhjn1x3f4j" path="res://core/daynight/sounds/rain_2.mp3" id="21_wnhyx"] [ext_resource type="AudioStream" uid="uid://elrjw0smm0cj" path="res://core/daynight/sounds/rain_3.mp3" id="22_53kbn"] [ext_resource type="Script" uid="uid://cx2tlvxhvatj5" path="res://docs/museums/daynight/control.gd" id="23_27ile"] +[ext_resource type="Script" uid="uid://ccdd52apoh4ir" path="res://docs/museums/daynight/camera_3d.gd" id="24_sw7bt"] [sub_resource type="FastNoiseLite" id="FastNoiseLite_wjpfq"] fractal_lacunarity = 1.915 @@ -148,7 +149,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) curve = SubResource("Curve3D_xtogr") script = ExtResource("6_pypsn") train_model = ExtResource("6_wjpfq") -cameras = NodePath("../cameras") +cameras = NodePath("../Camera3D") sleepers_model = ExtResource("8_mb5yv") fireworks_scene = ExtResource("8_xtogr") @@ -373,6 +374,10 @@ theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1) text = "Weather: Clear" +[node name="Camera3D" type="Camera3D" parent="." unique_id=1305162994] +transform = Transform3D(0.035881948, 0.7570504, -0.65237045, -0.1472942, 0.6496678, 0.7458125, 0.9884417, 0.06932917, 0.13482046, -18.173235, 14.658583, -10.483771) +script = ExtResource("24_sw7bt") + [connection signal="toggled" from="Control/Snow" to="Control" method="_on_snow_toggled"] [connection signal="toggled" from="Control/Rain" to="Control" method="_on_rain_toggled"] [connection signal="toggled" from="Control/Wind" to="Control" method="_on_wind_toggled"] diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..f730c68 --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,71 @@ +[preset.0] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="../tgcc.exe" +patches=PackedStringArray() +patch_delta_encoding=false +patch_delta_compression_level_zstd=19 +patch_delta_min_reduction=0.1 +patch_delta_include_filters="*" +patch_delta_exclude_filters="" +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +shader_baker/enabled=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="" +application/product_version="" +application/company_name="" +application/product_name="" +application/file_description="" +application/copyright="" +application/trademarks="" +application/export_angle=0 +application/export_d3d12=0 +application/d3d12_agility_sdk_multiarch=true +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' +$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' +$trigger = New-ScheduledTaskTrigger -Once -At 00:00 +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries +$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings +Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true +Start-ScheduledTask -TaskName godot_remote_debug +while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" +ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue +Remove-Item -Recurse -Force '{temp_dir}'" diff --git a/tgcc/main_scene.tscn b/tgcc/main_scene.tscn index 9bcb603..f577e49 100644 --- a/tgcc/main_scene.tscn +++ b/tgcc/main_scene.tscn @@ -45,6 +45,7 @@ [ext_resource type="PackedScene" uid="uid://1c1ion0qnho4" path="res://tgcc/map/map_1/map_1.tscn" id="43_ysd85"] [ext_resource type="Script" uid="uid://dboerd4a6dwj7" path="res://core/biome_generator/rails.gd" id="44_rcqo0"] [ext_resource type="PackedScene" uid="uid://dvk3bytqn3m5s" path="res://tgcc/train/main_train.tscn" id="45_c0jxs"] +[ext_resource type="Resource" uid="uid://bjnytgwt8tmf4" path="res://core/biome_generator/wagon_pool.tres" id="46_exdk1"] [ext_resource type="PackedScene" uid="uid://0kgjaqijaqku" path="res://docs/museums/biome_generator/rails.tscn" id="46_lbmv2"] [ext_resource type="PackedScene" uid="uid://dn1btv0e0ses7" path="res://core/fireworks.tscn" id="47_q7p65"] [ext_resource type="Script" uid="uid://cx2tlvxhvatj5" path="res://docs/museums/daynight/control.gd" id="48_r5xyi"] @@ -195,6 +196,10 @@ transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 270, curve = SubResource("Curve3D_ndco5") script = ExtResource("44_rcqo0") train_model = ExtResource("45_c0jxs") +wagon_pool = ExtResource("46_exdk1") +wagon_count = 4 +wagon_gap = 0.5 +wagon_spacing_scale = 0.7999999999999999 cameras = NodePath("../cameras") sleepers_model = ExtResource("46_lbmv2") fireworks_scene = ExtResource("47_q7p65")