51 Commits

Author SHA1 Message Date
2aa7d1f7d4 fix snow 2026-05-10 11:01:22 +02:00
040ded5f0b fix rain 2026-05-09 14:57:07 +02:00
ddec91d515 update cloud density when is rainy or snowy 2026-05-06 23:14:51 +02:00
d8efa735dd Merge pull request 'polish2' (#12) from polish2 into main
Reviewed-on: #12
2026-05-06 20:29:12 +00:00
Matteo Sonaglioni
9bc7f38d07 add river curve 2026-05-06 22:27:54 +02:00
Matteo Sonaglioni
f4c8775a45 add river chunk 2026-05-06 21:43:34 +02:00
70d3dfe15c add ai, photo_mode and radio 2026-05-05 20:33:00 +00:00
Matteo Sonaglioni
9827b98b3e river water shader 2026-05-05 18:45:16 +02:00
Matteo Sonaglioni
977e8012aa river chunk 2026-05-05 17:03:24 +02:00
72d06560b1 add groups to scenes 2026-05-05 00:04:02 +02:00
9d62d97422 Merge pull request 'biome_gen_river' (#10) from biome_gen_river into main
Reviewed-on: #10
2026-05-04 22:00:08 +00:00
c1e99a3b8e fix merge scenes 2026-05-04 23:59:27 +02:00
8e1ba915a2 add scenes 2026-05-04 23:43:22 +02:00
Matteo Sonaglioni
577b4570da add river chunk 2026-05-04 23:35:29 +02:00
36dff9c070 remove train radar 2026-05-04 10:09:20 +02:00
56591bb907 fix snow cap 2026-05-02 18:10:51 +02:00
9f254d9725 Merge pull request 'polish1' (#6) from polish1 into main
Reviewed-on: #6
2026-05-02 16:09:44 +00:00
abae8434fc add river bioma gen + label weather random 2026-04-30 23:23:49 +02:00
Matteo Sonaglioni
e211f89fca revamp pylon 2026-04-30 17:48:08 +02:00
3c7cbdc662 Merge pull request 'biome_gen_overload' (#5) from biome_gen_overload into main
Reviewed-on: #5
2026-04-30 14:42:30 +00:00
8c7f1c3c82 fix stutter 2026-04-30 16:21:08 +02:00
b7308c5d66 fix fog 2026-04-30 12:20:12 +02:00
Matteo Sonaglioni
d709d9236c Merge branch 'main' into polish1 2026-04-30 11:24:50 +02:00
Matteo Sonaglioni
9ee6d08918 chunk fix 2026-04-30 11:05:18 +02:00
ff93a7ab2c add fog 2026-04-30 07:13:34 +02:00
122ec189eb fix water colors 2026-04-30 06:55:28 +02:00
889334e7a3 fix snow and water ripples + add water colors 2026-04-30 06:53:54 +02:00
Matteo Sonaglioni
3521839ed9 add bambu 2026-04-29 17:54:13 +02:00
Matteo Sonaglioni
6a62f92ed5 fog planes 2026-04-29 14:14:58 +02:00
Matteo Sonaglioni
ead4e557c4 Squashed commit of the following:
commit d8496f4cc7
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Wed Apr 29 11:01:07 2026 +0200

    tweak enviromentconfig

commit 84538679f3
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Wed Apr 29 09:29:48 2026 +0200

    museum_map
2026-04-29 11:02:24 +02:00
Matteo Sonaglioni
b868584b78 Squashed commit of the following:
commit 53646e36f0
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Thu Apr 23 17:24:36 2026 +0200

    map - zoos

commit 88dd7c3ad8
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Thu Apr 23 16:48:14 2026 +0200

    fix railway chunk
2026-04-28 10:06:03 +02:00
d668804038 add label to see current weather 2026-04-24 17:51:49 +02:00
e8f2c4db84 add random weather function 2026-04-24 17:44:43 +02:00
72c933ba73 add random start for train o start from a stop 2026-04-24 17:08:24 +02:00
8f02afa796 update snow parameters 2026-04-24 16:18:36 +02:00
211455f5da fix materials for wind and add new snow system 2026-04-24 15:36:10 +02:00
ab87c7f478 fix set shader on nodes 2026-04-24 12:22:46 +02:00
b95e8598b2 some fix 2026-04-24 10:58:07 +02:00
Matteo Sonaglioni
5ddf363368 Squashed commit of the following:
commit 75286521d1
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Thu Apr 23 15:41:28 2026 +0200

    railway chunk

commit b975e13c8d
Merge: ae9b75c f3120c6
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Tue Apr 21 23:37:06 2026 +0200

    Merge branch 'Chunk' of https://gitea.jmpgames.it/jmp-games/tgcc into Chunk

commit ae9b75cc53
Author: Matteo Sonaglioni <sonaglioni.matteo7@gmail.com>
Date:   Tue Apr 21 23:34:41 2026 +0200

    nomenclatura chunk rotaie
2026-04-23 16:03:35 +02:00
fda8188102 Merge pull request 'rev_ffabbrizi_newbiome' (#3) from rev_ffabbrizi_newbiome into main
Reviewed-on: #3
2026-04-23 13:38:16 +00:00
7470937b49 add controls to daytime scene + change main scene on run 2026-04-23 15:37:25 +02:00
35618b5656 fix snow and rain for grass 2026-04-23 15:29:03 +02:00
8b2cbab23b fix shaders 2026-04-23 15:07:04 +02:00
174ae98242 turnoff ripples when is not rainy 2026-04-22 00:03:01 +02:00
f3120c6972 remove old chunks 2026-04-21 23:32:31 +02:00
8505fcbe40 Merge pull request 'main' (#2) from main into Chunk
Reviewed-on: #2
2026-04-21 16:36:48 +00:00
464f405091 add train swing when rail is curve 2026-04-21 16:13:15 +02:00
edb96d6a89 fix labels 2026-04-21 15:55:10 +02:00
5fe96068a4 Merge pull request 'add wind_slider and change wind_amount particles base on wind_stranght' (#1) from rev_ffabbrizi_biomegen into main
Reviewed-on: #1
2026-04-21 13:28:06 +00:00
77aaf92104 add wind_slider and change wind_amount particles base on wind_stranght 2026-04-21 15:26:13 +02:00
f5a38bd8a0 add wind fade in/out + add wire movement when is windy 2026-04-21 15:16:30 +02:00
290 changed files with 17551 additions and 4925 deletions

13
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/modules.xml
/projectSettingsUpdater.xml
/.idea.tgcc.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

4
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

8
.idea/indexLayout.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,51 @@
extends CharacterBody3D
class_name AIBase
@export var speed: float = 4.0
var _enable_state_machine: bool = true
@export var enable_state_machine: bool:
set(value):
_enable_state_machine = value
toggle_enable_state_machine()
get:
return _enable_state_machine
@onready var patrol_radius_shape: CollisionShape3D = $%PatrolRadiusShape
@onready var nav_agent: NavigationAgent3D = $%NavigationAgent3D
@onready var state_machine: StateMachine = $%StateMachine
func _ready() -> void:
randomize()
toggle_enable_state_machine()
func toggle_enable_state_machine() -> void:
state_machine.enable = _enable_state_machine
func _physics_process(delta: float) -> void:
if !_enable_state_machine:
return
state_machine.physics_process(delta)
if nav_agent.is_navigation_finished():
velocity = Vector3.ZERO
move_and_slide()
return
var next_point = nav_agent.get_next_path_position()
var direction = global_position.direction_to(next_point)
velocity.x = direction.x * speed
velocity.z = direction.z * speed
move_and_slide()
func navigate_to_random_point() -> void:
var nav_map_rid = get_world_3d().get_navigation_map()
var patrol_radius = patrol_radius_shape.shape.radius
var target_point_in_space = global_position + Vector3(randf_range(-1, 1), 0, randf_range(-1, 1)).normalized() * randf_range(0, patrol_radius)
var closest_valid_point = NavigationServer3D.map_get_closest_point(nav_map_rid, target_point_in_space)
nav_agent.target_position = closest_valid_point

View File

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

View File

@@ -0,0 +1,44 @@
[gd_scene format=3 uid="uid://clx701xdwelgx"]
[ext_resource type="Script" uid="uid://b30p1yqojbbbk" path="res://core/ai/agents/base/ai_base.gd" id="1_4d1nn"]
[ext_resource type="Script" uid="uid://ps3vlu2qvmop" path="res://core/ai/framework/state_machine.gd" id="2_q1hg3"]
[ext_resource type="Script" uid="uid://nga8qx56iwgu" path="res://core/ai/agents/base/idle_state.gd" id="3_26faq"]
[ext_resource type="Script" uid="uid://bngfthvt04ivv" path="res://core/ai/agents/base/patrol_state.gd" id="4_lim44"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_mh3lg"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_mh3lg"]
[sub_resource type="SphereShape3D" id="SphereShape3D_lim44"]
radius = 20.0
[node name="AiBase" type="CharacterBody3D" unique_id=1228675528]
script = ExtResource("1_4d1nn")
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=214482161]
mesh = SubResource("CapsuleMesh_mh3lg")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=92811823]
shape = SubResource("CapsuleShape3D_mh3lg")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=1370024449]
unique_name_in_owner = true
[node name="StateMachine" type="Node" parent="." unique_id=1286404264 node_paths=PackedStringArray("initial_state")]
unique_name_in_owner = true
script = ExtResource("2_q1hg3")
initial_state = NodePath("IdleState")
[node name="IdleState" type="Node" parent="StateMachine" unique_id=763668255]
script = ExtResource("3_26faq")
state_id = &"idle"
[node name="PatrolState" type="Node" parent="StateMachine" unique_id=2054606629]
script = ExtResource("4_lim44")
state_id = &"patrol"
[node name="PatrolRadiusShape" type="CollisionShape3D" parent="." unique_id=1379515938]
unique_name_in_owner = true
shape = SubResource("SphereShape3D_lim44")
disabled = true
debug_color = Color(1, 1, 0, 1)

View File

@@ -0,0 +1,26 @@
extends State
const PATROL_STATE_ID: StringName = &"patrol"
@export var wait_time: float = 3.0
var runtime_timer: Timer
func enter() -> void:
runtime_timer = Timer.new()
runtime_timer.name = "IdleTimer"
add_child(runtime_timer)
runtime_timer.one_shot = true
runtime_timer.timeout.connect(_on_timer_timeout)
runtime_timer.start(wait_time)
func exit() -> void:
if runtime_timer:
if runtime_timer.is_connected("timeout", _on_timer_timeout):
runtime_timer.timeout.disconnect(_on_timer_timeout)
runtime_timer.queue_free()
runtime_timer = null
func _on_timer_timeout() -> void:
transitioned.emit(self, PATROL_STATE_ID)

View File

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

View File

@@ -0,0 +1,12 @@
extends State
const IDLE_STATE_ID: StringName = &"idle"
@onready var agent: AIBase = owner
func enter() -> void:
agent.navigate_to_random_point()
func physics_update(_delta) -> void:
if agent.nav_agent.is_navigation_finished():
transitioned.emit(self, IDLE_STATE_ID)

View File

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

View File

@@ -0,0 +1,20 @@
extends Node
class_name State
@export var state_id: StringName = &""
@warning_ignore("unused_signal")
signal transitioned(state: State, new_state_id: StringName)
func enter() -> void:
pass
func exit() -> void:
pass
func update(_delta) -> void:
pass
func physics_update(_delta) -> void:
pass

View File

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

View File

@@ -0,0 +1,61 @@
extends Node
class_name StateMachine
@export var initial_state: State
var current_state: State
var states: Dictionary[StringName, State] = {}
var is_initialized: bool = false
var _enabled: bool = true
var enable: bool:
set(value):
_enabled = value
_set_enabled(value)
get:
return _enabled
func _ready() -> void:
_initialize_states()
_set_enabled(enable)
func _initialize_states() -> void:
if is_initialized:
return
for state in get_children():
if state is State:
states[state.state_id] = state
state.transitioned.connect(_on_state_transition)
is_initialized = true
func _set_enabled(value: bool) -> void:
_enabled = value
if not _enabled:
return
_initialize_states()
if initial_state and current_state == null:
current_state = initial_state
current_state.enter()
func physics_process(delta) -> void:
if current_state and _enabled:
current_state.physics_update(delta)
func _on_state_transition(state: State, new_state_id: StringName) -> void:
if state != current_state:
return
var new_state = states.get(new_state_id)
if not new_state:
return
if current_state:
current_state.exit()
current_state = new_state
current_state.enter()

View File

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

View File

@@ -0,0 +1,31 @@
extends Node
const MIN_VOLUME: float = 0.0
const MAX_VOLUME: float = 1.0
func _ready() -> void:
load_save_data()
func load_save_data() -> void:
for bus_name in GameState.save_data.audio_bus_volumes:
_apply_audio_bus_volume(bus_name, GameState.save_data.audio_bus_volumes[bus_name])
func set_audio_bus_volume(bus_name: StringName, linear_value: float) -> void:
var normalized_bus_name := str(bus_name)
var clamped_value := clampf(linear_value, MIN_VOLUME, MAX_VOLUME)
GameState.save_data.audio_bus_volumes[normalized_bus_name] = clamped_value
_apply_audio_bus_volume(normalized_bus_name, clamped_value)
GameState.save_game()
func get_audio_bus_volume(bus_name: StringName, default_value: float = 1.0) -> float:
return float(GameState.save_data.audio_bus_volumes.get(str(bus_name), default_value))
func _apply_audio_bus_volume(bus_name: String, linear_value: float) -> void:
var bus_index := AudioServer.get_bus_index(bus_name)
if bus_index == -1:
return
var clamped_value := clampf(linear_value, MIN_VOLUME, MAX_VOLUME)
AudioServer.set_bus_volume_db(bus_index, linear_to_db(clamped_value))
AudioServer.set_bus_mute(bus_index, is_zero_approx(clamped_value))

View File

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

View File

@@ -0,0 +1,56 @@
extends VBoxContainer
class_name AudioOption
const MIN_VOLUME: float = 0.0
const MAX_VOLUME: float = 1.0
@export var bus_name: StringName = &"Master"
@export var label_text: String = "Master Volume"
@export_range(0.0, 1.0, 0.01) var default_volume: float = 1.0
@onready var label: Label = $Label
@onready var slider: HSlider = $HSlider
func _ready() -> void:
slider.min_value = MIN_VOLUME
slider.max_value = MAX_VOLUME
slider.step = 0.01
slider.value_changed.connect(_on_slider_value_changed)
refresh()
func refresh() -> void:
label.text = label_text
if not has_bus():
slider.set_value_no_signal(default_volume)
slider.editable = false
label.text = "%s (bus missing)" % label_text
return
slider.editable = true
var saved_volume := AudioManager.get_audio_bus_volume(bus_name, default_volume)
slider.set_value_no_signal(saved_volume)
apply_current_value()
func reset_to_default() -> void:
slider.value = default_volume
func apply_current_value() -> void:
_set_bus_volume(slider.value)
func has_bus() -> bool:
return get_bus_index() != -1
func get_bus_index() -> int:
return AudioServer.get_bus_index(bus_name)
func _set_bus_volume(linear_value: float) -> void:
if not has_bus():
return
var clamped_value := clampf(linear_value, MIN_VOLUME, MAX_VOLUME)
AudioManager.set_audio_bus_volume(bus_name, clamped_value)
func _on_slider_value_changed(value: float) -> void:
_set_bus_volume(value)

View File

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

View File

@@ -0,0 +1,14 @@
[gd_scene format=3 uid="uid://cescrovsjlwke"]
[ext_resource type="Script" uid="uid://bnh1y560h55vc" path="res://core/audio/settings/audio_option.gd" id="1_orafl"]
[node name="AudioOption" type="VBoxContainer" unique_id=1509773712]
custom_minimum_size = Vector2(300, 0)
script = ExtResource("1_orafl")
[node name="Label" type="Label" parent="." unique_id=781845786]
layout_mode = 2
text = "Master Volume"
[node name="HSlider" type="HSlider" parent="." unique_id=1286896253]
layout_mode = 2

View File

@@ -0,0 +1,36 @@
extends Control
class_name SettingsMenu
@onready var audio_options_container: VBoxContainer = $%AudioOptionsContainer
@onready var reset_button: Button = $%ResetToDefault_Button
@onready var close_button: Button = $%Close_Button
func _ready() -> void:
reset_button.pressed.connect(_on_reset_to_default_button_pressed)
close_button.pressed.connect(_on_close_button_pressed)
_refresh_audio_options()
func _get_audio_options() -> Array[AudioOption]:
var audio_options: Array[AudioOption] = []
for child in audio_options_container.get_children():
if child is AudioOption:
audio_options.append(child)
return audio_options
func _refresh_audio_options() -> void:
for audio_option in _get_audio_options():
audio_option.refresh()
func _apply_current_audio_values() -> void:
for audio_option in _get_audio_options():
audio_option.apply_current_value()
func _on_reset_to_default_button_pressed() -> void:
for audio_option in _get_audio_options():
audio_option.reset_to_default()
func _on_close_button_pressed() -> void:
visible = false

View File

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

View File

@@ -0,0 +1,74 @@
[gd_scene format=3 uid="uid://caqf471x0kttc"]
[ext_resource type="Script" uid="uid://cvr25q1blyxg" path="res://core/audio/settings/settings_menu.gd" id="1_4lekq"]
[ext_resource type="PackedScene" uid="uid://cescrovsjlwke" path="res://core/audio/settings/audio_option.tscn" id="1_hwcco"]
[node name="SettingsMenu" type="Control" unique_id=1639777294]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_4lekq")
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=315821242]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -79.5
offset_top = -110.0
offset_right = 79.5
offset_bottom = 110.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer" unique_id=648635784]
layout_mode = 2
size_flags_vertical = 0
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="AudioOptionsContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer" unique_id=824023653]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 12
[node name="MasterVolume_AudioOption" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=1509773712 instance=ExtResource("1_hwcco")]
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=1053430754]
layout_mode = 2
[node name="MusicVolume_AudioOption" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=690924616 instance=ExtResource("1_hwcco")]
layout_mode = 2
bus_name = &"Music"
label_text = "Music Volume"
[node name="HSeparator2" type="HSeparator" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=453716295]
layout_mode = 2
[node name="SFXVolume_AudioOption" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=2096291127 instance=ExtResource("1_hwcco")]
layout_mode = 2
bus_name = &"SFX"
label_text = "SFX Volume"
[node name="HSeparator3" type="HSeparator" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=498553283]
layout_mode = 2
[node name="ResetToDefault_Button" type="Button" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=720420423]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
text = "Reset To Default"
[node name="Close_Button" type="Button" parent="PanelContainer/MarginContainer/AudioOptionsContainer" unique_id=1137906684]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
text = "Close"

View File

@@ -1,5 +1,14 @@
extends Node3D extends Node3D
const CHUNK_TYPE_BIOME: int = 0
const CHUNK_TYPE_STRAIGHT_TRACK: int = 1
const CHUNK_TYPE_CURVED_TRACK: int = 2
const CHUNK_GENERATION_STEPS_PER_FRAME: int = 2
const CHUNK_CLEANUP_STEPS_PER_FRAME: int = 4
const LAMPPOST_WIRE_STEPS_PER_FRAME: int = 1
const RAILWAY_SCENE_DIRECTORY: String = "res://tgcc/chunk/railway/scene"
@export_group("Rails") @export_group("Rails")
@export var rail_path: Path3D @export var rail_path: Path3D
@@ -14,18 +23,29 @@ extends Node3D
@export_group("Lamppost") @export_group("Lamppost")
@export var lamppost_wire_material: ShaderMaterial @export var lamppost_wire_material: ShaderMaterial
@export_range(0.01, 1.0) var wire_thickness: float = 0.05 @export_range(0.01, 1.0) var wire_thickness: float = 0.05
@export var lamppost_dist_factor: int = 10
var board: Dictionary = {} var board: Dictionary = {}
var last_pos_train: Vector2i = Vector2i(999999, 999999) var last_pos_train: Vector2i = Vector2i(999999, 999999)
var noise_generator: FastNoiseLite var noise_generator: FastNoiseLite
var altitude_generator: FastNoiseLite var altitude_generator: FastNoiseLite
var wire_connections: Dictionary = {} var wire_connections: Dictionary = {}
var chunk_candidate_cache: Dictionary = {}
var pending_generation_cells: Array[Vector2i] = []
var pending_cleanup_cells: Array[Vector2i] = []
var pending_wire_cells: Array[Vector2i] = []
var pending_wire_lookup: Dictionary = {}
var pending_radar_update: bool = false
var rail_chunk_catalogue: Dictionary = {}
var manual_biome: Biome = null var manual_biome: Biome = null
func _ready() -> void: func _ready() -> void:
if biome_list.is_empty() or rail_path == null: return if biome_list.is_empty() or rail_path == null: return
#connect events
UIEvents.update_rail_chunks.connect(_update_rail_chunks)
noise_generator = FastNoiseLite.new() noise_generator = FastNoiseLite.new()
noise_generator.noise_type = FastNoiseLite.TYPE_PERLIN noise_generator.noise_type = FastNoiseLite.TYPE_PERLIN
noise_generator.seed = randi() noise_generator.seed = randi()
@@ -36,6 +56,8 @@ func _ready() -> void:
altitude_generator.seed = randi() altitude_generator.seed = randi()
altitude_generator.frequency = district_scale * 0.5 altitude_generator.frequency = district_scale * 0.5
_warm_chunk_candidate_cache()
_warm_rail_chunk_catalogue()
_update_set_pieces() _update_set_pieces()
func _update_set_pieces() -> void: func _update_set_pieces() -> void:
@@ -67,15 +89,18 @@ func _update_set_pieces() -> void:
print(" ❌ Temple is an invisible ghost.") print(" ❌ Temple is an invisible ghost.")
func _destroy_and_regenrate_world() -> void: func _destroy_and_regenrate_world() -> void:
_warm_chunk_candidate_cache()
_clear_pending_world_work()
#Turn off old temples #Turn off old temples
_update_set_pieces() _update_set_pieces()
#Destroy all models #Destroy all models
for pos in board.keys(): for pos in board.keys():
var cella = board[pos] var cella = board[pos]
if cella["tipo"] == "bioma": if cella["type"] != "obstacle":
if cella.has("nodo") and is_instance_valid(cella["nodo"]): if cella.has("node") and is_instance_valid(cella["node"]):
cella["nodo"].queue_free() cella["node"].queue_free()
#Clean board and wire connections #Clean board and wire connections
board.clear() board.clear()
@@ -83,9 +108,19 @@ func _destroy_and_regenrate_world() -> void:
#Create train #Create train
last_pos_train = Vector2i(999999, 999999) last_pos_train = Vector2i(999999, 999999)
_train_radar() if rail_path != null and rail_path.train_instance != null:
last_pos_train = Vector2i(999999, 999999) var train_pos = rail_path.train_instance.global_position
_train_radar() var current_pos = Vector2i(roundi(train_pos.x / chunk_size), roundi(train_pos.z / chunk_size))
_refresh_world(current_pos)
pending_radar_update = true
func _process(_delta: float) -> void:
_drain_cleanup_queue()
_drain_generation_queue()
_drain_wire_queue()
if pending_radar_update:
pending_radar_update = false
func _physics_process(_delta: float) -> void: func _physics_process(_delta: float) -> void:
if rail_path == null or rail_path.train_instance == null: return if rail_path == null or rail_path.train_instance == null: return
@@ -97,9 +132,8 @@ func _physics_process(_delta: float) -> void:
if current_pos != last_pos_train: if current_pos != last_pos_train:
last_pos_train = current_pos last_pos_train = current_pos
_generate_pieces_around_train(current_pos) _refresh_world(current_pos)
_clean_far_chunks(current_pos) pending_radar_update = true
_train_radar()
func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void: func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void:
if root == null: return if root == null: return
@@ -110,6 +144,292 @@ func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void:
for child in root.get_children(): for child in root.get_children():
collect_all_chunkinfo(child, list) collect_all_chunkinfo(child, list)
func _warm_chunk_candidate_cache() -> void:
var unique_scenes: Dictionary = {}
for biome in biome_list:
if biome == null:
continue
for scene in biome.available_chunks:
if scene == null:
continue
unique_scenes[_get_chunk_scene_cache_key(scene)] = scene
if manual_biome != null:
for scene in manual_biome.available_chunks:
if scene == null:
continue
unique_scenes[_get_chunk_scene_cache_key(scene)] = scene
for scene in unique_scenes.values():
_get_chunk_scene_metadata(scene)
func _warm_rail_chunk_catalogue() -> void:
rail_chunk_catalogue.clear()
var directory := DirAccess.open(RAILWAY_SCENE_DIRECTORY)
if directory == null:
return
directory.list_dir_begin()
var file_name := directory.get_next()
while file_name != "":
if not directory.current_is_dir() and file_name.ends_with(".tscn"):
var scene_path := "%s/%s" % [RAILWAY_SCENE_DIRECTORY, file_name]
var scene := load(scene_path) as PackedScene
if scene != null:
var chunk_type = _get_chunk_type_from_scene(scene)
if chunk_type == CHUNK_TYPE_STRAIGHT_TRACK or chunk_type == CHUNK_TYPE_CURVED_TRACK:
if not rail_chunk_catalogue.has(chunk_type):
rail_chunk_catalogue[chunk_type] = []
rail_chunk_catalogue[chunk_type].append(scene)
file_name = directory.get_next()
directory.list_dir_end()
func _get_chunk_scene_cache_key(scene: PackedScene) -> String:
if scene == null:
return ""
if scene.resource_path != "":
return scene.resource_path
return "scene_%s" % scene.get_instance_id()
func _get_chunk_scene_metadata(scene: PackedScene) -> Dictionary:
if scene == null:
return {}
var key = _get_chunk_scene_cache_key(scene)
if chunk_candidate_cache.has(key):
return chunk_candidate_cache[key]
var preview_chunk = scene.instantiate()
var info_list: Array[Node] = []
collect_all_chunkinfo(preview_chunk, info_list)
var info_node = info_list[0] if info_list.size() > 0 else null
if info_node == null or not info_node.has_method("get_rotated_data"):
preview_chunk.queue_free()
chunk_candidate_cache[key] = {}
return {}
var info_path: NodePath = NodePath(".")
if info_node != preview_chunk:
info_path = preview_chunk.get_path_to(info_node)
var rotations = []
for rot in range(4):
rotations.append({
"rotation": rot,
"data": info_node.get_rotated_data(rot)
})
var metadata = {
"info_path": info_path,
"rotations": rotations,
"has_lamppost": "have_lamppost" in info_node and info_node.have_lamppost,
}
preview_chunk.queue_free()
chunk_candidate_cache[key] = metadata
return metadata
func _get_cached_chunk_info(instance: Node, scene: PackedScene) -> Node:
var metadata = _get_chunk_scene_metadata(scene)
if metadata.is_empty():
return null
var info_path: NodePath = metadata["info_path"]
if String(info_path) == ".":
return instance
return instance.get_node_or_null(info_path)
func _get_chunk_type_from_scene(scene: PackedScene) -> int:
if scene == null:
return CHUNK_TYPE_BIOME
var preview_chunk = scene.instantiate()
var info_list: Array[Node] = []
collect_all_chunkinfo(preview_chunk, info_list)
var info_node = info_list[0] if info_list.size() > 0 else null
var chunk_type = CHUNK_TYPE_BIOME
if info_node != null and "chunk_type" in info_node:
chunk_type = info_node.chunk_type
preview_chunk.queue_free()
return chunk_type
func _clear_pending_world_work() -> void:
pending_generation_cells.clear()
pending_cleanup_cells.clear()
pending_wire_cells.clear()
pending_wire_lookup.clear()
pending_radar_update = false
func _collect_replaceable_rail_chunks(root: Node, result: Array[Node3D]) -> void:
if root == null:
return
if root is Node3D:
var node_3d := root as Node3D
if node_3d.scene_file_path.begins_with(RAILWAY_SCENE_DIRECTORY):
var info_list: Array[Node] = []
collect_all_chunkinfo(node_3d, info_list)
var info_node = info_list[0] if info_list.size() > 0 else null
if info_node != null and "chunk_type" in info_node:
var chunk_type = info_node.chunk_type
if chunk_type == CHUNK_TYPE_STRAIGHT_TRACK or chunk_type == CHUNK_TYPE_CURVED_TRACK:
result.append(node_3d)
return
for child in root.get_children():
_collect_replaceable_rail_chunks(child, result)
func _pick_replacement_rail_scene(chunk_type: int, current_scene_path: String) -> PackedScene:
var candidates: Array = rail_chunk_catalogue.get(chunk_type, [])
if candidates.is_empty():
return null
var alternatives: Array[PackedScene] = []
for candidate in candidates:
var scene := candidate as PackedScene
if scene != null and scene.resource_path != current_scene_path:
alternatives.append(scene)
if not alternatives.is_empty():
return alternatives.pick_random()
return candidates.pick_random() as PackedScene
func _replace_rail_chunk_instance(chunk_root: Node3D) -> bool:
if chunk_root == null or not is_instance_valid(chunk_root):
return false
var info_list: Array[Node] = []
collect_all_chunkinfo(chunk_root, info_list)
var info_node = info_list[0] if info_list.size() > 0 else null
if info_node == null or not "chunk_type" in info_node:
return false
var replacement_scene = _pick_replacement_rail_scene(info_node.chunk_type, chunk_root.scene_file_path)
if replacement_scene == null:
return false
var chunk_parent = chunk_root.get_parent()
var replacement = replacement_scene.instantiate() as Node3D
if chunk_parent == null or replacement == null:
return false
var chunk_index = chunk_root.get_index()
var chunk_name = chunk_root.name
chunk_root.name = "%s_old" % chunk_name
replacement.transform = chunk_root.transform
chunk_parent.add_child(replacement)
chunk_parent.move_child(replacement, chunk_index)
replacement.name = chunk_name
replacement.owner = chunk_root.owner
chunk_root.queue_free()
return true
func _refresh_rail_chunks() -> void:
print("update rail chunks")
_warm_rail_chunk_catalogue()
var current_scene: Node = get_tree().current_scene
if current_scene == null:
return
var replaceable_chunks: Array[Node3D] = []
_collect_replaceable_rail_chunks(current_scene, replaceable_chunks)
var replaced_count = 0
for chunk_root in replaceable_chunks:
if _replace_rail_chunk_instance(chunk_root):
replaced_count += 1
if replaced_count > 0:
await get_tree().process_frame
_destroy_and_regenrate_world()
func _update_rail_chunks() -> void:
call_deferred("_refresh_rail_chunks")
func _refresh_world(center: Vector2i) -> void:
_rebuild_generation_queue(center)
_rebuild_cleanup_queue(center)
func _rebuild_generation_queue(center: Vector2i) -> void:
pending_generation_cells.clear()
for radius in range(eye_line + 1):
for x in range(-radius, radius + 1):
for z in range(-radius, radius + 1):
if maxi(abs(x), abs(z)) != radius:
continue
var grid_pos = center + Vector2i(x, z)
if board.has(grid_pos):
continue
pending_generation_cells.append(grid_pos)
func _rebuild_cleanup_queue(center: Vector2i) -> void:
pending_cleanup_cells.clear()
var safe_margin = 2
for grid_pos in board.keys():
var cell = board[grid_pos]
if cell["type"] == "obstacle":
continue
var dist_x = abs(grid_pos.x - center.x)
var dist_z = abs(grid_pos.y - center.y)
if dist_x > eye_line + safe_margin or dist_z > eye_line + safe_margin:
pending_cleanup_cells.append(grid_pos)
func _drain_generation_queue() -> void:
var processed = 0
while processed < CHUNK_GENERATION_STEPS_PER_FRAME and not pending_generation_cells.is_empty():
var grid_pos = pending_generation_cells.pop_front()
if board.has(grid_pos):
continue
var is_obstacle = _register_cell_with_ray(grid_pos)
if not is_obstacle:
_add_compatible_biome(grid_pos)
processed += 1
func _drain_cleanup_queue() -> void:
var processed = 0
while processed < CHUNK_CLEANUP_STEPS_PER_FRAME and not pending_cleanup_cells.is_empty():
var grid_pos = pending_cleanup_cells.pop_front()
if not board.has(grid_pos):
continue
var cell = board[grid_pos]
if cell["type"] == "obstacle":
continue
if cell.has("node") and is_instance_valid(cell["node"]):
cell["node"].queue_free()
board.erase(grid_pos)
processed += 1
func _queue_lamppost_wire_connection(grid_pos: Vector2i) -> void:
if pending_wire_lookup.has(grid_pos):
return
pending_wire_lookup[grid_pos] = true
pending_wire_cells.append(grid_pos)
func _drain_wire_queue() -> void:
var processed = 0
while processed < LAMPPOST_WIRE_STEPS_PER_FRAME and not pending_wire_cells.is_empty():
var grid_pos = pending_wire_cells.pop_front()
pending_wire_lookup.erase(grid_pos)
if not board.has(grid_pos):
continue
if not board[grid_pos].get("have_lamppost", false):
continue
_connect_lamppost_wires(grid_pos)
processed += 1
func _generate_pieces_around_train(center: Vector2i) -> void: func _generate_pieces_around_train(center: Vector2i) -> void:
for x in range(-eye_line, eye_line + 1): for x in range(-eye_line, eye_line + 1):
for z in range(-eye_line, eye_line + 1): for z in range(-eye_line, eye_line + 1):
@@ -181,6 +501,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool:
right_info_node = info_list[0] right_info_node = info_list[0]
var exit_found = {"north": false, "est": false, "south": false, "west": false} var exit_found = {"north": false, "est": false, "south": false, "west": false}
var river_exit_found = {"north": false, "est": false, "south": false, "west": false}
var height_found = {"north": 0, "est": 0, "south": 0, "west": 0} var height_found = {"north": 0, "est": 0, "south": 0, "west": 0}
var have_lamppost = false var have_lamppost = false
@@ -190,6 +511,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool:
var rotation_steps = roundi(rad_to_deg(right_info_node.global_rotation.y) / -90.0) var rotation_steps = roundi(rad_to_deg(right_info_node.global_rotation.y) / -90.0)
var data = right_info_node.get_rotated_data(rotation_steps) var data = right_info_node.get_rotated_data(rotation_steps)
exit_found = data["connections"] exit_found = data["connections"]
river_exit_found = data["river_connections"]
height_found = data["heights"] height_found = data["heights"]
if "have_lamppost" in right_info_node: if "have_lamppost" in right_info_node:
@@ -199,6 +521,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool:
board[grid_pos] = { board[grid_pos] = {
"type": "obstacle", "type": "obstacle",
"exit": exit_found, "exit": exit_found,
"river_exit": river_exit_found,
"heights": height_found, "heights": height_found,
"node": root_chunk, "node": root_chunk,
"info": right_info_node, "info": right_info_node,
@@ -206,7 +529,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool:
} }
if have_lamppost: if have_lamppost:
_connect_lamppost_wires(grid_pos) _queue_lamppost_wire_connection(grid_pos)
return true return true
return false return false
@@ -216,6 +539,10 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
var req_conn_est = _needed_connection(grid_pos + Vector2i(1, 0), "west") var req_conn_est = _needed_connection(grid_pos + Vector2i(1, 0), "west")
var req_conn_south = _needed_connection(grid_pos + Vector2i(0, 1), "north") var req_conn_south = _needed_connection(grid_pos + Vector2i(0, 1), "north")
var req_conn_west = _needed_connection(grid_pos + Vector2i(-1, 0), "est") var req_conn_west = _needed_connection(grid_pos + Vector2i(-1, 0), "est")
var req_river_north = _needed_river_connection(grid_pos + Vector2i(0, -1), "south")
var req_river_est = _needed_river_connection(grid_pos + Vector2i(1, 0), "west")
var req_river_south = _needed_river_connection(grid_pos + Vector2i(0, 1), "north")
var req_river_west = _needed_river_connection(grid_pos + Vector2i(-1, 0), "est")
var req_height_north = _needed_heights(grid_pos + Vector2i(0, -1), "south") var req_height_north = _needed_heights(grid_pos + Vector2i(0, -1), "south")
var req_height_est = _needed_heights(grid_pos + Vector2i(1, 0), "west") var req_height_est = _needed_heights(grid_pos + Vector2i(1, 0), "west")
@@ -229,43 +556,45 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
var valid_candidates = [] var valid_candidates = []
for scene in zone_catalogue: for scene in zone_catalogue:
var test_chunk = scene.instantiate() var metadata = _get_chunk_scene_metadata(scene)
var test_list: Array[Node] = [] if metadata.is_empty():
collect_all_chunkinfo(test_chunk, test_list) continue
var info_test = test_list[0] if test_list.size() > 0 else null
if info_test != null and info_test.has_method("get_rotated_data"): for candidate in metadata["rotations"]:
for rot in range(4): var rot = candidate["rotation"]
var data = test_chunk.get_rotated_data(rot) var data = candidate["data"]
var u_conn = data["connections"] var u_conn = data["connections"]
var u_height = data["heights"] var u_river_conn = data["river_connections"]
var u_height = data["heights"]
var match_conn_n = (req_conn_north == -1) or ((req_conn_north == 1) == u_conn["north"]) var match_conn_n = (req_conn_north == -1) or ((req_conn_north == 1) == u_conn["north"])
var match_conn_e = (req_conn_est == -1) or ((req_conn_est == 1) == u_conn["est"]) var match_conn_e = (req_conn_est == -1) or ((req_conn_est == 1) == u_conn["est"])
var match_conn_s = (req_conn_south == -1) or ((req_conn_south == 1) == u_conn["south"]) var match_conn_s = (req_conn_south == -1) or ((req_conn_south == 1) == u_conn["south"])
var match_conn_o = (req_conn_west == -1) or ((req_conn_west == 1) == u_conn["west"]) var match_conn_o = (req_conn_west == -1) or ((req_conn_west == 1) == u_conn["west"])
var match_river_n = (req_river_north == -1) or ((req_river_north == 1) == u_river_conn["north"])
var match_river_e = (req_river_est == -1) or ((req_river_est == 1) == u_river_conn["est"])
var match_river_s = (req_river_south == -1) or ((req_river_south == 1) == u_river_conn["south"])
var match_river_o = (req_river_west == -1) or ((req_river_west == 1) == u_river_conn["west"])
var match_alt_n = (req_height_north == -1) or (req_height_north == u_height["north"]) var match_alt_n = (req_height_north == -1) or (req_height_north == u_height["north"])
var match_alt_e = (req_height_est == -1) or (req_height_est == u_height["est"]) var match_alt_e = (req_height_est == -1) or (req_height_est == u_height["est"])
var match_alt_s = (req_height_south == -1) or (req_height_south == u_height["south"]) var match_alt_s = (req_height_south == -1) or (req_height_south == u_height["south"])
var match_alt_o = (req_height_west == -1) or (req_height_west == u_height["west"]) var match_alt_o = (req_height_west == -1) or (req_height_west == u_height["west"])
var diff_n = abs(u_height["north"] - height_target) if req_height_north == -1 else 0 var diff_n = abs(u_height["north"] - height_target) if req_height_north == -1 else 0
var diff_e = abs(u_height["est"] - height_target) if req_height_est == -1 else 0 var diff_e = abs(u_height["est"] - height_target) if req_height_est == -1 else 0
var diff_s = abs(u_height["south"] - height_target) if req_height_south == -1 else 0 var diff_s = abs(u_height["south"] - height_target) if req_height_south == -1 else 0
var diff_o = abs(u_height["west"] - height_target) if req_height_west == -1 else 0 var diff_o = abs(u_height["west"] - height_target) if req_height_west == -1 else 0
var excessive_jumps = diff_n > 1 or diff_e > 1 or diff_s > 1 or diff_o > 1 var excessive_jumps = diff_n > 1 or diff_e > 1 or diff_s > 1 or diff_o > 1
if match_conn_n and match_conn_e and match_conn_s and match_conn_o and match_alt_n and match_alt_e and match_alt_s and match_alt_o and not excessive_jumps: if match_conn_n and match_conn_e and match_conn_s and match_conn_o and match_river_n and match_river_e and match_river_s and match_river_o and match_alt_n and match_alt_e and match_alt_s and match_alt_o and not excessive_jumps:
var score = 0 var score = 0
if req_height_north == -1 and u_height["north"] == height_target: score += 1 if req_height_north == -1 and u_height["north"] == height_target: score += 1
if req_height_est == -1 and u_height["est"] == height_target: score += 1 if req_height_est == -1 and u_height["est"] == height_target: score += 1
if req_height_south == -1 and u_height["south"] == height_target: score += 1 if req_height_south == -1 and u_height["south"] == height_target: score += 1
if req_height_west == -1 and u_height["west"] == height_target: score += 1 if req_height_west == -1 and u_height["west"] == height_target: score += 1
valid_candidates.append({"scene": scene, "rotation": rot, "data": data, "score": score}) valid_candidates.append({"scene": scene, "rotation": rot, "data": data, "score": score})
test_chunk.queue_free()
if valid_candidates.size() > 0: if valid_candidates.size() > 0:
var max_score = -1 var max_score = -1
@@ -278,21 +607,20 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
var choise = best_candidate.pick_random() var choise = best_candidate.pick_random()
var new_chunk = choise.scene.instantiate() var new_chunk = choise.scene.instantiate()
add_child(new_chunk)
new_chunk.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size) new_chunk.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
new_chunk.rotation.y = choise.rotation * (-PI / 2.0) new_chunk.rotation.y = choise.rotation * (-PI / 2.0)
add_child(new_chunk)
var new_list: Array[Node] = [] var info_new = _get_cached_chunk_info(new_chunk, choise.scene)
collect_all_chunkinfo(new_chunk, new_list)
var info_new = new_list[0] if new_list.size() > 0 else null
var have_lamppost = false var have_lamppost = false
if info_new != null and "have_lamppost" in new_chunk: if info_new != null and "have_lamppost" in info_new:
have_lamppost = info_new.have_lamppost have_lamppost = info_new.have_lamppost
board[grid_pos] = { board[grid_pos] = {
"type": "bioma", "type": "bioma",
"exit": choise.data["connections"], "exit": choise.data["connections"],
"river_exit": choise.data["river_connections"],
"heights": choise.data["heights"], "heights": choise.data["heights"],
"node": new_chunk, "node": new_chunk,
"info": info_new, "info": info_new,
@@ -300,16 +628,14 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
} }
if have_lamppost: if have_lamppost:
_connect_lamppost_wires(grid_pos) _queue_lamppost_wire_connection(grid_pos)
else: else:
var backup = zone_catalogue[0].instantiate() var backup = zone_catalogue[0].instantiate()
add_child(backup)
backup.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size) backup.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
add_child(backup)
var backup_list: Array[Node] = [] var info_backup = _get_cached_chunk_info(backup, zone_catalogue[0])
collect_all_chunkinfo(backup, backup_list)
var info_backup = backup_list[0] if backup_list.size() > 0 else null
var safe_heights = { var safe_heights = {
"north": req_height_north if req_height_north != -1 else height_target, "north": req_height_north if req_height_north != -1 else height_target,
@@ -320,6 +646,7 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
board[grid_pos] = { board[grid_pos] = {
"type": "biome", "type": "biome",
"exit": {"north":false, "est":false, "south":false, "west":false}, "exit": {"north":false, "est":false, "south":false, "west":false},
"river_exit": {"north":false, "est":false, "south":false, "west":false},
"heights": safe_heights, "heights": safe_heights,
"node": backup, "node": backup,
"info": info_backup, "info": info_backup,
@@ -333,75 +660,28 @@ func _needed_connection(near_pos: Vector2i, side_needed: String) -> int:
return 1 if board[near_pos]["exit"][side_needed] else 0 return 1 if board[near_pos]["exit"][side_needed] else 0
return -1 return -1
func _needed_river_connection(near_pos: Vector2i, side_needed: String) -> int:
if not board.has(near_pos):
_register_cell_with_ray(near_pos)
if board.has(near_pos) and board[near_pos].has("river_exit"):
return 1 if board[near_pos]["river_exit"][side_needed] else 0
return -1
func _needed_heights(near_pos: Vector2i, side_needed: String) -> int: func _needed_heights(near_pos: Vector2i, side_needed: String) -> int:
if board.has(near_pos) and board[near_pos].has("heights"): if board.has(near_pos) and board[near_pos].has("heights"):
return board[near_pos]["heights"][side_needed] return board[near_pos]["heights"][side_needed]
return -1 return -1
func _train_radar() -> void:
if rail_path == null or rail_path.curve == null or rail_path.train_instance == null: return
if manual_biome != null:
return
var train_pos = rail_path.train_instance.global_position
var grid_x = roundi(train_pos.x / chunk_size)
var grid_z = roundi(train_pos.z / chunk_size)
var current_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(grid_x, grid_z))
var current_progress = rail_path.train_progress
var total_length = rail_path.curve.get_baked_length()
#var exchange_distance = 0.0
#var next_biome = ""
for step in range(1, 31):
var next_progress = wrapf(current_progress + (step * chunk_size), 0.0, total_length)
var next_local_pos = rail_path.curve.sample_baked(next_progress, true)
var next_global_pos = rail_path.to_global(next_local_pos)
var f_x = roundi(next_global_pos.x / chunk_size)
var f_z = roundi(next_global_pos.z / chunk_size)
var future_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(f_x, f_z))
if future_biome != current_biome:
#exchange_distance = step * chunk_size
#next_biome = future_biome
break
func _clean_far_chunks(centro_attuale: Vector2i) -> void:
var cells_to_remove = []
var safe_margin = 2
for grid_pos in board.keys():
var cell = board[grid_pos]
if cell["type"] == "obstacle": continue
var dist_x = abs(grid_pos.x - centro_attuale.x)
var dist_z = abs(grid_pos.y - centro_attuale.y)
if dist_x > eye_line + safe_margin or dist_z > eye_line + safe_margin:
if cell.has("node") and is_instance_valid(cell["node"]):
cell["node"].queue_free()
cells_to_remove.append(grid_pos)
for pos in cells_to_remove:
board.erase(pos)
func _get_lampposts(info_node: Node, side: String) -> Array[Node3D]: func _get_lampposts(info_node: Node, side: String) -> Array[Node3D]:
var lampposts: Array[Node3D] = [] var lampposts: Array[Node3D] = []
if side == "sx": if side == "sx":
if "connection_left" in info_node and info_node.connection_left != null: if "connection_left" in info_node and info_node.connection_left != null:
for p in info_node.connection_left: for p in info_node.connection_left:
if is_instance_valid(p): lampposts.append(p) if is_instance_valid(p): lampposts.append(p)
#if lampposts.is_empty() and "nodo_attacco_sx" in nodo_info and is_instance_valid(nodo_info.get("nodo_attacco_sx")):
# lampposts.append(nodo_info.get("nodo_attacco_sx"))
else: else:
if "connection_right" in info_node and info_node.connection_right != null: if "connection_right" in info_node and info_node.connection_right != null:
for p in info_node.connection_right: for p in info_node.connection_right:
if is_instance_valid(p): lampposts.append(p) if is_instance_valid(p): lampposts.append(p)
#if lampposts.is_empty() and "nodo_attacco_dx" in nodo_info and is_instance_valid(nodo_info.get("nodo_attacco_dx")):
# lampposts.append(nodo_info.get("nodo_attacco_dx"))
return lampposts return lampposts
func _connect_lamppost_wires(new_board_pos: Vector2i) -> void: func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
@@ -425,7 +705,7 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
var p_sx_your_best = null; var p_dx_your_best = null var p_sx_your_best = null; var p_dx_your_best = null
var best_closest_to_root = null var best_closest_to_root = null
var best_distance = 999999.0 var best_distance = 999999.0
var ray_research = 3 var ray_research = 6
for x in range(-ray_research, ray_research + 1): for x in range(-ray_research, ray_research + 1):
for z in range(-ray_research, ray_research + 1): for z in range(-ray_research, ray_research + 1):
@@ -493,7 +773,7 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
p_sx_your_best = closest_sx[i_t] p_sx_your_best = closest_sx[i_t]
p_dx_your_best = closest_dx[i_t] p_dx_your_best = closest_dx[i_t]
var max_dist = chunk_size * 8.0 var max_dist = chunk_size * lamppost_dist_factor
if best_closest_to_root != null and best_distance < max_dist: if best_closest_to_root != null and best_distance < max_dist:
var dist_streight = p_sx_my_best.global_position.distance_to(p_sx_your_best.global_position) + p_dx_my_best.global_position.distance_to(p_dx_your_best.global_position) var dist_streight = p_sx_my_best.global_position.distance_to(p_sx_your_best.global_position) + p_dx_my_best.global_position.distance_to(p_dx_your_best.global_position)
@@ -511,6 +791,7 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
if not wire_connections.has(closest_id): wire_connections[closest_id] = 0 if not wire_connections.has(closest_id): wire_connections[closest_id] = 0
wire_connections[closest_id] += 1 wire_connections[closest_id] += 1
#draw lamppost
func _draw_parable(p1: Vector3, p2: Vector3, parent: Node3D) -> void: func _draw_parable(p1: Vector3, p2: Vector3, parent: Node3D) -> void:
var segments = 15 var segments = 15
var lowering = 1.5 var lowering = 1.5

View File

@@ -6,17 +6,23 @@ class_name ChunkInfo
@export_group("Set Piece Rules (Unique pieces)") @export_group("Set Piece Rules (Unique pieces)")
@export var exclusive_biome: String = "" # Es: "Forest" @export var exclusive_biome: String = "" # Es: "Forest"
@export_group("Base exit (no roation)") @export_group("Base exit")
@export var north: bool = false @export var north: bool = false
@export var est: bool = false @export var est: bool = false
@export var south: bool = false @export var south: bool = false
@export var west: bool = false @export var west: bool = false
@export_group("River exit")
@export var river_north: bool = false
@export var river_est: bool = false
@export var river_south: bool = false
@export var river_west: bool = false
@export_group("Margin heights (level 0, 1, 2)") @export_group("Margin heights (level 0, 1, 2)")
@export var height_north: int = 0 @export_range(0, 2, 1) var height_north: int = 0
@export var height_est: int = 0 @export_range(0, 2, 1) var height_est: int = 0
@export var height_south: int = 0 @export_range(0, 2, 1) var height_south: int = 0
@export var height_west: int = 0 @export_range(0, 2, 1) var height_west: int = 0
@export_group("Lamppost") @export_group("Lamppost")
@export var have_lamppost: bool = false @export var have_lamppost: bool = false
@@ -29,14 +35,17 @@ func _ready() -> void:
func get_rotated_data(steps_90: int) -> Dictionary: func get_rotated_data(steps_90: int) -> Dictionary:
var original_exit = [north, est, south, west] var original_exit = [north, est, south, west]
var original_river_exit = [river_north, river_est, river_south, river_west]
var original_height = [height_north, height_est, height_south, height_west] var original_height = [height_north, height_est, height_south, height_west]
var calculated_exit = [] var calculated_exit = []
var calculated_river_exit = []
var calculated_height = [] var calculated_height = []
for i in range(4): for i in range(4):
var index = (i - steps_90 + 4) % 4 var index = (i - steps_90 + 4) % 4
calculated_exit.append(original_exit[index]) calculated_exit.append(original_exit[index])
calculated_river_exit.append(original_river_exit[index])
calculated_height.append(original_height[index]) calculated_height.append(original_height[index])
return { return {
@@ -44,6 +53,10 @@ func get_rotated_data(steps_90: int) -> Dictionary:
"north": calculated_exit[0], "est": calculated_exit[1], "north": calculated_exit[0], "est": calculated_exit[1],
"south": calculated_exit[2], "west": calculated_exit[3] "south": calculated_exit[2], "west": calculated_exit[3]
}, },
"river_connections": {
"north": calculated_river_exit[0], "est": calculated_river_exit[1],
"south": calculated_river_exit[2], "west": calculated_river_exit[3]
},
"heights": { "heights": {
"north": calculated_height[0], "est": calculated_height[1], "north": calculated_height[0], "est": calculated_height[1],
"south": calculated_height[2], "west": calculated_height[3] "south": calculated_height[2], "west": calculated_height[3]

View File

@@ -7,7 +7,7 @@ extends Path3D
@export var axes_distance: float = 3.0 @export var axes_distance: float = 3.0
@export var rail_distance: float = 1.2 @export var rail_distance: float = 1.2
@export var cameras: Node3D @export var cameras: Node3D
@export_range(0.0, 1.0) var basic_swing: float = 0.5 @export_range(0.0, 1.0) var basic_swing: float = 0.1
@export_group("Rails Bulding") @export_group("Rails Bulding")
@export var sleepers_distance: float = 1.0 @export var sleepers_distance: float = 1.0
@@ -37,6 +37,8 @@ var fireworks_colors: Array[Color] = [
var train_instance: Node3D var train_instance: Node3D
var train_progress: float = 0.0 var train_progress: float = 0.0
var swing_time: float = 0.0 var swing_time: float = 0.0
var curve_roll: float = 0.0
var curve_pitch: float = 0.0
var stop_offset: Array[float] = [] var stop_offset: Array[float] = []
var stops_position: Array[Vector3] = [] var stops_position: Array[Vector3] = []
@@ -45,14 +47,111 @@ var next_stop_index: int = 0
var stop_multiply: float = 1.0 var stop_multiply: float = 1.0
var is_restarting: bool = false var is_restarting: bool = false
var tween_restart: Tween var tween_restart: Tween
var environment_config: EnvironmentConfig
func _ready() -> void: func _ready() -> void:
_cache_environment_config()
if curve != null and curve.get_baked_length() > 0: if curve != null and curve.get_baked_length() > 0:
build_rails() build_rails()
build_train() build_train()
_plan_stops() _plan_stops()
_apply_initial_train_start()
else: else:
print("WARNING: Draw your Path3D!") print("WARNING: Draw Path3D for rails!")
func _cache_environment_config() -> void:
var parent_node: Node = get_parent()
if parent_node != null:
var day_night_node := parent_node.get_node_or_null("DayNight") as EnvironmentManagerRoot
if day_night_node != null:
environment_config = day_night_node.environment_config
return
var current_scene: Node = get_tree().current_scene
if current_scene == null:
return
for child in current_scene.get_children():
var environment_manager := child as EnvironmentManagerRoot
if environment_manager != null:
environment_config = environment_manager.environment_config
return
func _apply_initial_train_start() -> void:
if train_instance == null or curve == null:
return
var total_length: float = curve.get_baked_length()
if total_length <= 0.0:
return
#start from a random position
if environment_config != null and environment_config.train_start_from_random_position:
train_progress = randf() * total_length
_update_next_stop_index_from_progress()
elif environment_config != null and not stop_offset.is_empty():
#start from a specific stop
var stop_index: int = clampi(environment_config.train_start_stop_index, 0, stop_offset.size() - 1)
train_progress = stop_offset[stop_index]
_set_next_stop_index_from_current(stop_index)
else:
train_progress = wrapf(train_progress, 0.0, total_length)
_update_next_stop_index_from_progress()
_snap_train_to_progress()
func _set_next_stop_index_from_current(current_stop_index: int) -> void:
if stop_offset.is_empty():
next_stop_index = 0
return
if train_speed >= 0.0:
next_stop_index = (current_stop_index + 1) % stop_offset.size()
else:
next_stop_index = current_stop_index - 1
if next_stop_index < 0:
next_stop_index = stop_offset.size() - 1
func _update_next_stop_index_from_progress() -> void:
if stop_offset.is_empty():
next_stop_index = 0
return
if train_speed >= 0.0:
next_stop_index = 0
for i in range(stop_offset.size()):
if stop_offset[i] > train_progress + 0.001:
next_stop_index = i
return
return
next_stop_index = stop_offset.size() - 1
for i in range(stop_offset.size() - 1, -1, -1):
if stop_offset[i] < train_progress - 0.001:
next_stop_index = i
return
func _snap_train_to_progress() -> void:
if train_instance == null or curve == null:
return
var total_length: float = curve.get_baked_length()
if total_length <= 0.0:
return
train_progress = wrapf(train_progress, 0.0, total_length)
var center_position: Vector3 = to_global(curve.sample_baked(train_progress, true))
var prog_forward: float = wrapf(train_progress + 2.0, 0.0, total_length)
var forward_position: Vector3 = to_global(curve.sample_baked(prog_forward, true))
train_instance.global_position = center_position
if center_position.distance_to(forward_position) > 0.01:
train_instance.look_at(forward_position, Vector3.UP)
train_instance.rotate_y(PI)
if cameras:
cameras.global_position = center_position
cameras.global_basis = train_instance.global_basis
func build_rails() -> void: func build_rails() -> void:
var mat_wood = StandardMaterial3D.new() var mat_wood = StandardMaterial3D.new()
@@ -112,16 +211,16 @@ func build_rails() -> void:
rail_piece.add_child(rail_dx) rail_piece.add_child(rail_dx)
func _plan_stops() -> void: func _plan_stops() -> void:
var currnet_stops = [] var current_stops = []
for child in get_children(): for child in get_children():
if child is Marker3D: if child is Marker3D:
var offset = curve.get_closest_offset(child.position) var offset = curve.get_closest_offset(child.position)
currnet_stops.append({ current_stops.append({
"offset": offset, "offset": offset,
"position": child.global_position "position": child.global_position
}) })
currnet_stops.sort_custom(func(a, b): return a["offset"] < b["offset"]) current_stops.sort_custom(func(a, b): return a["offset"] < b["offset"])
for data in currnet_stops: for data in current_stops:
stop_offset.append(data["offset"]) stop_offset.append(data["offset"])
stops_position.append(data["position"]) stops_position.append(data["position"])
@@ -130,9 +229,9 @@ func _input(event: InputEvent) -> void:
if event.keycode == KEY_T: if event.keycode == KEY_T:
_goto_next_stop() _goto_next_stop()
elif event.keycode == KEY_F: elif event.keycode == KEY_F:
_test_manual_fires() _test_manual_fireworks()
func _test_manual_fires() -> void: func _test_manual_fireworks() -> void:
if fireworks_scene == null or train_instance == null: return if fireworks_scene == null or train_instance == null: return
var firework_number = randi_range(5, 8) var firework_number = randi_range(5, 8)
@@ -146,8 +245,8 @@ func _test_manual_fires() -> void:
if fire_root.has_method("set_color"): if fire_root.has_method("set_color"):
fire_root.set_color(fireworks_colors.pick_random()) fire_root.set_color(fireworks_colors.pick_random())
if fire_root.has_method("turnon"): if fire_root.has_method("turn_on"):
fire_root.turnon() fire_root.turn_on()
await get_tree().create_timer(randf_range(0.2, 0.6)).timeout await get_tree().create_timer(randf_range(0.2, 0.6)).timeout
@@ -168,6 +267,8 @@ func input_controls_management(delta: float) -> void:
train_speed += manual_acceleration * delta train_speed += manual_acceleration * delta
elif Input.is_action_pressed("ui_down"): elif Input.is_action_pressed("ui_down"):
train_speed -= manual_acceleration * delta train_speed -= manual_acceleration * delta
elif Input.is_action_pressed("ui_text_backspace"):
train_speed = 0
train_speed = clamp(train_speed, speed_min, speed_max) train_speed = clamp(train_speed, speed_min, speed_max)
func train_move(delta: float) -> void: func train_move(delta: float) -> void:
@@ -209,6 +310,8 @@ func train_move(delta: float) -> void:
if stop_offset.size() > 0: next_stop_index = stop_offset.size() - 1 if stop_offset.size() > 0: next_stop_index = stop_offset.size() - 1
var center_position = to_global(curve.sample_baked(train_progress, true)) var center_position = to_global(curve.sample_baked(train_progress, true))
var prog_back = wrapf(train_progress - 2.0, 0.0, total_length)
var back_position = to_global(curve.sample_baked(prog_back, true))
var prog_forward = wrapf(train_progress + 2.0, 0.0, total_length) var prog_forward = wrapf(train_progress + 2.0, 0.0, total_length)
var forward_position = to_global(curve.sample_baked(prog_forward, true)) var forward_position = to_global(curve.sample_baked(prog_forward, true))
@@ -217,17 +320,32 @@ func train_move(delta: float) -> void:
train_instance.look_at(forward_position, Vector3.UP) train_instance.look_at(forward_position, Vector3.UP)
train_instance.rotate_y(PI) train_instance.rotate_y(PI)
if cameras: if cameras:
cameras.global_position = center_position cameras.global_position = center_position
cameras.global_basis = train_instance.global_basis cameras.global_basis = train_instance.global_basis
var real_speed = abs(train_speed * stop_multiply)
var speed_factor = clamp(real_speed / max(speed_max, 0.001), 0.0, 1.0)
var dir_prev = (center_position - back_position).normalized()
var dir_next = (forward_position - center_position).normalized()
var signed_curve = dir_prev.cross(dir_next).y
var curve_amount = clamp(abs(signed_curve) * 8.0, 0.0, 1.0)
curve_roll = lerp(curve_roll, (-signed_curve * 0.08) * speed_factor, delta * 4.0)
curve_pitch = lerp(curve_pitch, curve_amount * 0.012 * speed_factor, delta * 4.0)
if not stop_ongoing: if not stop_ongoing:
var real_speed = abs(train_speed * stop_multiply) swing_time += delta * real_speed * (2.0 + curve_amount)
swing_time += delta * real_speed * 2.0
var amplitude_z = 0.006 * basic_swing var amplitude_z = 0.006 * basic_swing
var amplitude_x = 0.004 * basic_swing var amplitude_x = 0.004 * basic_swing
var curve_sway = sin(swing_time * 1.35) * 0.01 * curve_amount * speed_factor
train_instance.rotation.z += sin(swing_time) * amplitude_z train_instance.rotation.z += sin(swing_time) * amplitude_z
train_instance.rotation.x += cos(swing_time * 0.8) * amplitude_x train_instance.rotation.x += cos(swing_time * 0.8) * amplitude_x
train_instance.rotation.z += curve_roll + curve_sway
train_instance.rotation.x += curve_pitch
else:
train_instance.rotation.z += curve_roll
train_instance.rotation.x += curve_pitch
func _execute_stop(go_forward: bool = true) -> void: func _execute_stop(go_forward: bool = true) -> void:
stop_ongoing = true stop_ongoing = true
@@ -258,8 +376,8 @@ func _execute_stop(go_forward: bool = true) -> void:
if fire_root.has_method("set_color"): if fire_root.has_method("set_color"):
fire_root.set_color(fireworks_colors.pick_random()) fire_root.set_color(fireworks_colors.pick_random())
if fire_root.has_method("turnon"): if fire_root.has_method("turn_on"):
fire_root.turnon() fire_root.turn_on()
await get_tree().create_timer(randf_range(0.3, 0.8)).timeout await get_tree().create_timer(randf_range(0.3, 0.8)).timeout

View File

@@ -6,19 +6,24 @@ uniform vec4 albedo_color : source_color = vec4(0.1, 0.1, 0.1, 1.0);
global uniform float global_wind_strength; global uniform float global_wind_strength;
global uniform float global_wind_speed; global uniform float global_wind_speed;
global uniform float global_wind_fade;
void vertex() { void vertex() {
//COLOR.r is the drawing. 0 -> lampost (no move); 1.0 is the center of the wire //COLOR.r is the drawing. 0 -> lampost (no move); 1.0 is the center of the wire
float movement = COLOR.r; float movement = COLOR.r;
float wind_amount = global_wind_strength * global_wind_fade;
//Create a wave (based on position and time to be random) //Create a wave (based on position and time to be random)
float wind_wave = 0.0; float wind_wave = 0.0;
if (global_wind_speed > 0.0 && global_wind_strength > 0.0) { float vertical_wave = 0.0;
wind_wave = sin(TIME * global_wind_speed + NODE_POSITION_WORLD.x * 0.5) * global_wind_strength; if (global_wind_speed > 0.0 && wind_amount > 0.0) {
wind_wave = sin(TIME * global_wind_speed + NODE_POSITION_WORLD.x * 0.5 + NODE_POSITION_WORLD.z * 0.35) * wind_amount;
vertical_wave = sin(TIME * global_wind_speed * 0.7 + NODE_POSITION_WORLD.z * 0.35) * wind_amount;
} }
VERTEX.x += wind_wave * 0.1 * movement; VERTEX.x += wind_wave * 0.28 * movement;
VERTEX.z += wind_wave * 0.05 * movement; VERTEX.y += vertical_wave * 0.125 * movement;
VERTEX.z += wind_wave * 0.24 * movement;
} }
void fragment() { void fragment() {

View File

@@ -0,0 +1,7 @@
[gd_resource type="NoiseTexture2D" format=3 uid="uid://b2xcpt2y8v32x"]
[sub_resource type="FastNoiseLite" id="FastNoiseLite_oqwsk"]
[resource]
noise = SubResource("FastNoiseLite_oqwsk")
seamless = true

View File

@@ -1,9 +1,17 @@
shader_type canvas_item; shader_type canvas_item;
// Cattura tutto quello che c'è sotto questo nodo
uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap; uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
uniform float blur_amount : hint_range(0.0, 5.0) = 2.5; uniform float blur_amount : hint_range(0.0, 5.0) = 2.5;
uniform float focus_size : hint_range(0.0, 1.0) = 0.4;
uniform float transition_softness : hint_range(0.01, 1.0) = 0.3;
void fragment() { void fragment() {
COLOR = textureLod(screen_texture, SCREEN_UV, blur_amount); float dist_from_center = abs(SCREEN_UV.y - 0.5);
float mask = smoothstep(focus_size / 2.0, (focus_size / 2.0) + transition_softness, dist_from_center);
// Applica il blur
vec4 blurred_screen = textureLod(screen_texture, SCREEN_UV, blur_amount * mask);
COLOR = blurred_screen;
} }

View File

@@ -1,16 +1,21 @@
class_name DayNightController class_name DayNightController
extends Node extends Node
const TIME_OPTION_SUNRISE: int = 0
const TIME_OPTION_DAY: int = 1
const TIME_OPTION_SUNSET: int = 2
const TIME_OPTION_NIGHT: int = 3
var environment_config: EnvironmentConfig var environment_config: EnvironmentConfig
#Multiply for debug #Multiply for debug
@export var time_scale: float = 1.0 @export var time_scale: float = 1.0
@export var paused: bool = false @export var paused: bool = false
signal time_changed(time: float) signal time_changed(time: float)
var current_time: float = 0.0 var current_time: float = 0.0
var _current_time_option_index: int = -1
func _init(environmentconfig: EnvironmentConfig) -> void: func _init(environmentconfig: EnvironmentConfig) -> void:
environment_config = environmentconfig environment_config = environmentconfig
@@ -19,6 +24,7 @@ func _ready() -> void:
#connect events #connect events
UIEvents.set_day_time.connect(_on_set_day_time) UIEvents.set_day_time.connect(_on_set_day_time)
UIEvents.toggle_pause_daytime.connect(_on_toggle_pause_daytime) UIEvents.toggle_pause_daytime.connect(_on_toggle_pause_daytime)
UIEvents.time_option_item_changed.connect(_time_option_changed)
current_time = environment_config.start_time current_time = environment_config.start_time
update_time(current_time) update_time(current_time)
@@ -44,9 +50,34 @@ func set_time(t: float) -> void:
update_time(current_time) update_time(current_time)
func update_time(currtime: float) -> void: func update_time(currtime: float) -> void:
var time_option_index: int = get_time_option_index(currtime)
if time_option_index != _current_time_option_index:
_current_time_option_index = time_option_index
UIEvents.day_time_option_changed.emit(time_option_index)
emit_signal("time_changed", currtime) emit_signal("time_changed", currtime)
UIEvents.day_time_changed.emit(currtime, get_time_string()) UIEvents.day_time_changed.emit(currtime, get_time_string())
func get_time_option_index(currtime: float) -> int:
if currtime >= environment_config.night or currtime < environment_config.sunrise:
return TIME_OPTION_NIGHT
if currtime < environment_config.day:
return TIME_OPTION_SUNRISE
if currtime < environment_config.sunset:
return TIME_OPTION_DAY
return TIME_OPTION_SUNSET
func _time_option_changed(index: int) -> void:
match index:
TIME_OPTION_SUNRISE:
set_time(environment_config.sunrise)
TIME_OPTION_DAY:
set_time(environment_config.day)
TIME_OPTION_SUNSET:
set_time(environment_config.sunset)
TIME_OPTION_NIGHT:
set_time(environment_config.night)
func _on_set_day_time(value: float) -> void: func _on_set_day_time(value: float) -> void:
set_time(value) set_time(value)

View File

@@ -2,6 +2,7 @@ shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap; uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
global uniform float global_wind_speed; global uniform float global_wind_speed;
global uniform float global_wind_fade;
uniform vec4 dust_color : source_color = vec4(1.0, 0.456, 0.125, 0.6); uniform vec4 dust_color : source_color = vec4(1.0, 0.456, 0.125, 0.6);
@@ -30,7 +31,7 @@ void fragment() {
float base_time = TIME * dust_speed; float base_time = TIME * dust_speed;
vec2 osc = vec2(sin(TIME * 0.5), cos(TIME * 0.3)) * wind_oscillation; vec2 osc = vec2(sin(TIME * 0.5), cos(TIME * 0.3)) * wind_oscillation;
float wind_active = step(0.0001, abs(global_wind_speed)); float wind_active = global_wind_fade * step(0.0001, abs(global_wind_speed));
vec2 final_wind = ((wind_speed + osc) * dust_speed) * wind_active; vec2 final_wind = ((wind_speed + osc) * dust_speed) * wind_active;
float final_particle_alpha = 0.0; float final_particle_alpha = 0.0;

View File

@@ -9,6 +9,7 @@
[ext_resource type="Material" uid="uid://codq5gx71rj4o" path="res://core/daynight/environment_dust_material.tres" id="8_amaf0"] [ext_resource type="Material" uid="uid://codq5gx71rj4o" path="res://core/daynight/environment_dust_material.tres" id="8_amaf0"]
[ext_resource type="Shader" uid="uid://bkn6eqgbn37jx" path="res://core/daynight/blur.gdshader" id="9_amaf0"] [ext_resource type="Shader" uid="uid://bkn6eqgbn37jx" path="res://core/daynight/blur.gdshader" id="9_amaf0"]
[ext_resource type="Shader" uid="uid://b5wa82soyhsqm" path="res://core/daynight/environment_shadows.gdshader" id="10_tuauy"] [ext_resource type="Shader" uid="uid://b5wa82soyhsqm" path="res://core/daynight/environment_shadows.gdshader" id="10_tuauy"]
[ext_resource type="Texture2D" uid="uid://b2xcpt2y8v32x" path="res://core/daynight/FogNoise.tres" id="11_tuauy"]
[ext_resource type="FastNoiseLite" uid="uid://c0mtwrkptjcuw" path="res://core/daynight/environment_shadows_noise_clouds.tres" id="11_yn8v8"] [ext_resource type="FastNoiseLite" uid="uid://c0mtwrkptjcuw" path="res://core/daynight/environment_shadows_noise_clouds.tres" id="11_yn8v8"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_ruhh7"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_ruhh7"]
@@ -40,9 +41,58 @@ shader_parameter/sun_color = Color(0.2841828, 0.2023238, 0.27953526, 1)
[sub_resource type="Resource" id="Resource_r4tfj"] [sub_resource type="Resource" id="Resource_r4tfj"]
script = ExtResource("2_h6hjk") script = ExtResource("2_h6hjk")
morning_color = Color(0.93333334, 0.78039217, 0.6039216, 1)
afternoon_color = Color(0.9490196, 0.62352943, 0.38039216, 1)
night_color = Color(0.18431373, 0.18431373, 0.4862745, 1)
sun_rotation_morning = Vector3(-50, -90, 0)
sun_rotation_afternoon = Vector3(-120, -90, 0)
sun_rotation_night = Vector3(-130, -90, 0)
night_light_energy = 1.0
exposure_morning = 0.8
exposure_afternoon = 0.7
exposure_night = 0.7
sky_top_morning = Color(0.11764706, 0.35686275, 0.5411765, 1)
sky_top_afternoon = Color(0.7019608, 0.4509804, 0.2, 1)
sky_top_night = Color(0.050980393, 0.050980393, 0.14901961, 1)
sky_horizon_morning = Color(0.4627451, 0.6745098, 0.8627451, 1)
sky_horizon_afternoon = Color(0.93333334, 0.6, 0.8039216, 1)
sky_horizon_night = Color(0.14901961, 0.101960786, 0.2509804, 1)
grad_top_morning = Color(0.7921569, 0.8784314, 0.95686275, 1)
grad_top_afternoon = Color(0.8627451, 0.24313726, 0.5411765, 1)
grad_top_night = Color(0.16470589, 0.16470589, 0.26666668, 1)
grad_bot_morning = Color(0.05882353, 0.078431375, 0.21568628, 1)
grad_bot_afternoon = Color(0.2509804, 0.019607844, 0.043137256, 1)
grad_intensity_morning = 0.05
grad_intensity_afternoon = 0.1
grad_intensity_night = 0.5
fog_color_morning = Color(0.7490196, 0.8509804, 0.9490196, 1)
fog_color_afternoon = Color(0.9647059, 0.5882353, 0.7607843, 1)
fog_color_night = Color(0.14901961, 0.101960786, 0.2509804, 1)
fog_density_morning = 0.01
fog_density_afternoon = 0.02
glow_morning = 0.4
glow_night = 0.6
material_fog = SubResource("ShaderMaterial_b5atu") material_fog = SubResource("ShaderMaterial_b5atu")
material_drops = SubResource("StandardMaterial3D_r4tfj") material_drops = SubResource("StandardMaterial3D_r4tfj")
material_clouds = SubResource("ShaderMaterial_ruhh7") material_clouds = SubResource("ShaderMaterial_ruhh7")
lightning_color = Color(1, 0.9843137, 0.4627451, 1)
lightning_min = 2
lightning_max = 4
lightning_scale_min = 2.0
lightning_scale_max = 5.0
godray_max_rain = 30
godray_spawn_radius = 100.0
godray_spawn_offset = Vector3(20, 80, 20)
godray_rotation_degrees = Vector3(50, 30, 0)
godray_scale = Vector3(2, 25, 50)
snow_amount = 2000.0
wind_amount = 50
cloud_speed = 0.01
fireflies_amount = 550
fireflies_spawn_ray = 60.0
water_color_morning = Color(0.33333334, 0.654902, 0.5294118, 1)
water_color_afternoon = Color(0.5803922, 0.5137255, 0.2901961, 1)
water_color_night = Color(0.48235294, 0.45490196, 0.69411767, 1)
metadata/_custom_type_script = "uid://butda6k2tli3o" metadata/_custom_type_script = "uid://butda6k2tli3o"
[sub_resource type="Gradient" id="Gradient_i3hjl"] [sub_resource type="Gradient" id="Gradient_i3hjl"]
@@ -85,7 +135,7 @@ gradient = SubResource("Gradient_djqkg")
lifetime_randomness = 0.5 lifetime_randomness = 0.5
emission_shape_scale = Vector3(5, 5, 5) emission_shape_scale = Vector3(5, 5, 5)
emission_shape = 3 emission_shape = 3
emission_box_extents = Vector3(25, 5, 25) emission_box_extents = Vector3(1, 1, 1)
direction = Vector3(0, 1, 0) direction = Vector3(0, 1, 0)
initial_velocity_min = 6.0 initial_velocity_min = 6.0
initial_velocity_max = 6.0 initial_velocity_max = 6.0
@@ -99,10 +149,9 @@ point_count = 3
[sub_resource type="ShaderMaterial" id="ShaderMaterial_cer0u"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_cer0u"]
render_priority = 0 render_priority = 0
shader = ExtResource("5_djqkg") shader = ExtResource("5_djqkg")
shader_parameter/wave_frequency = 1.0 shader_parameter/time_scale = 3.065
shader_parameter/base_amplitude = 0.5 shader_parameter/wave_amplitude = 0.3
shader_parameter/secondary_ratio = 0.6 shader_parameter/wave_frequency = 0.25
shader_parameter/time_scale_base = 3.0
[sub_resource type="TubeTrailMesh" id="TubeTrailMesh_vo82p"] [sub_resource type="TubeTrailMesh" id="TubeTrailMesh_vo82p"]
material = SubResource("ShaderMaterial_cer0u") material = SubResource("ShaderMaterial_cer0u")
@@ -174,7 +223,9 @@ size = Vector2(0.05, 0.8)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_kavln"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_kavln"]
shader = ExtResource("9_amaf0") shader = ExtResource("9_amaf0")
shader_parameter/blur_amount = 0.6000000285 shader_parameter/blur_amount = 0.50000002375
shader_parameter/focus_size = 0.54400002584
shader_parameter/transition_softness = 0.00999999977648
[sub_resource type="QuadMesh" id="QuadMesh_sxgg7"] [sub_resource type="QuadMesh" id="QuadMesh_sxgg7"]
size = Vector2(2, 2) size = Vector2(2, 2)
@@ -184,12 +235,12 @@ noise = ExtResource("11_yn8v8")
seamless = true seamless = true
[sub_resource type="ShaderMaterial" id="ShaderMaterial_oqt1s"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_oqt1s"]
render_priority = 0 render_priority = -1
shader = ExtResource("10_tuauy") shader = ExtResource("10_tuauy")
shader_parameter/noise_texture = SubResource("NoiseTexture2D_0fcg4") shader_parameter/noise_texture = SubResource("NoiseTexture2D_0fcg4")
shader_parameter/cloud_scale = 0.0070000003325 shader_parameter/cloud_scale = 0.0070000003325
shader_parameter/cloud_speed = 0.0030000001425 shader_parameter/cloud_speed = 0.0030000001425
shader_parameter/direction = Vector2(0.5, 0.2) shader_parameter/direction = Vector2(0.1, 0.025)
shader_parameter/sun_angle = Vector2(0.5, 0.5) shader_parameter/sun_angle = Vector2(0.5, 0.5)
shader_parameter/cloud_density = 0.400000019 shader_parameter/cloud_density = 0.400000019
shader_parameter/center_sharpness = 0.138000006555 shader_parameter/center_sharpness = 0.138000006555
@@ -201,7 +252,23 @@ shader_parameter/fade_end = 800.000035625
shader_parameter/height_fade_start = 10.0 shader_parameter/height_fade_start = 10.0
shader_parameter/height_fade_end = 50.0 shader_parameter/height_fade_end = 50.0
[node name="EnvironmentManager" type="Node3D" unique_id=1611939731 node_paths=PackedStringArray("particles_wind", "particles_snow", "particles_fireflies", "particles_rain", "environment_dust", "blur", "environment_shadows")] [sub_resource type="QuadMesh" id="QuadMesh_yn8v8"]
size = Vector2(2, 2)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_sxgg7"]
render_priority = 2
shader = ExtResource("2_r4tfj")
shader_parameter/fog_noise = ExtResource("11_tuauy")
shader_parameter/use_red_as_alpha = true
shader_parameter/fog_color = Color(0.8, 0.8509804, 0.9019608, 0.2509804)
shader_parameter/scroll_speed = Vector2(0, 0.01)
shader_parameter/texture_scale = Vector2(1, 1)
shader_parameter/edge_softness_y = 0.16400000779
shader_parameter/edge_softness_x = 0.5
shader_parameter/night_intensity = 0.0
shader_parameter/sun_color = Color(1, 1, 1, 1)
[node name="EnvironmentManager" type="Node3D" unique_id=1611939731 node_paths=PackedStringArray("particles_wind", "particles_snow", "particles_fireflies", "particles_rain", "environment_dust", "blur", "environment_shadows", "fog")]
script = ExtResource("1_wecen") script = ExtResource("1_wecen")
environment_config = SubResource("Resource_r4tfj") environment_config = SubResource("Resource_r4tfj")
particles_wind = NodePath("Particles_Wind") particles_wind = NodePath("Particles_Wind")
@@ -212,6 +279,7 @@ godray = ExtResource("5_411rw")
environment_dust = NodePath("EnvironmentDust") environment_dust = NodePath("EnvironmentDust")
blur = NodePath("Blur") blur = NodePath("Blur")
environment_shadows = NodePath("EnvironmentCloudsShadows") environment_shadows = NodePath("EnvironmentCloudsShadows")
fog = NodePath("Fog")
[node name="Particles_Fireflies" type="GPUParticles3D" parent="." unique_id=947538133] [node name="Particles_Fireflies" type="GPUParticles3D" parent="." unique_id=947538133]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
@@ -222,9 +290,10 @@ process_material = SubResource("ParticleProcessMaterial_i3hjl")
draw_pass_1 = SubResource("QuadMesh_b5atu") draw_pass_1 = SubResource("QuadMesh_b5atu")
[node name="Particles_Wind" type="GPUParticles3D" parent="." unique_id=1238214694] [node name="Particles_Wind" type="GPUParticles3D" parent="." unique_id=1238214694]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 0) transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 50, 0)
cast_shadow = 0
emitting = false emitting = false
amount = 100 amount = 25
lifetime = 3.0 lifetime = 3.0
fixed_fps = 60 fixed_fps = 60
process_material = SubResource("ParticleProcessMaterial_ruhh7") process_material = SubResource("ParticleProcessMaterial_ruhh7")
@@ -271,3 +340,15 @@ mouse_filter = 2
extra_cull_margin = 16384.0 extra_cull_margin = 16384.0
mesh = SubResource("QuadMesh_sxgg7") mesh = SubResource("QuadMesh_sxgg7")
surface_material_override/0 = SubResource("ShaderMaterial_oqt1s") surface_material_override/0 = SubResource("ShaderMaterial_oqt1s")
[node name="Fog" type="Node3D" parent="." unique_id=1626352238]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Fog" unique_id=96635311]
transform = Transform3D(-42.075005, 7.553328e-06, 1.7196172e-13, 0, -3.7766642e-06, 45, 3.6783138e-06, 86.4, 1.9670128e-06, -41.926987, 14, 0)
mesh = SubResource("QuadMesh_yn8v8")
surface_material_override/0 = SubResource("ShaderMaterial_sxgg7")
[node name="MeshInstance3D2" type="MeshInstance3D" parent="Fog" unique_id=10121049]
transform = Transform3D(-42.075005, 7.553328e-06, 1.7196172e-13, 0, -3.7766642e-06, 45, 3.6783138e-06, 86.4, 1.9670128e-06, 59.320038, 14, 0)
mesh = SubResource("QuadMesh_yn8v8")
surface_material_override/0 = SubResource("ShaderMaterial_sxgg7")

View File

@@ -1,6 +1,10 @@
class_name EnvironmentManagerRoot class_name EnvironmentManagerRoot
extends Node3D extends Node3D
const NOISE_TEXTURE: Texture2D = preload("res://core/daynight/noise.tres")
const WEATHER_SHADER: Material = preload("res://core/daynight/weather_overlay.tres")
const DYNAMIC_ENVIRONMENT_UPDATES_PER_FRAME: int = 2 #how many update of the environment (apply materials) will be done per frame
@export var environment_config: EnvironmentConfig @export var environment_config: EnvironmentConfig
@export_group("Camera") @export_group("Camera")
@@ -16,6 +20,7 @@ extends Node3D
@export var environment_dust: ColorRect @export var environment_dust: ColorRect
@export var blur: ColorRect @export var blur: ColorRect
@export var environment_shadows: MeshInstance3D @export var environment_shadows: MeshInstance3D
@export var fog: Node3D
@export_group("Sound") @export_group("Sound")
@export var thunder_sounds: Array[AudioStream] @export var thunder_sounds: Array[AudioStream]
@@ -30,9 +35,13 @@ var weather_controller: WeatherController
var day_tween: Tween var day_tween: Tween
var day_time: float = 0.0 var day_time: float = 0.0
var pending_environment_nodes: Dictionary = {}
func _ready() -> void: func _ready() -> void:
#connect shader when new node is added
get_tree().node_added.connect(_on_tree_node_added)
#set noise texture 2d to materials in special group #set noise texture 2d to materials in special group
ApplyWindNoiseToMaterials() ApplyWindNoiseToMaterials()
#set weather overlay (snow + rain) to materials in special group #set weather overlay (snow + rain) to materials in special group
@@ -55,6 +64,7 @@ func _ready() -> void:
godray, godray,
environment_dust, environment_dust,
blur, blur,
fog,
environment_shadows, environment_shadows,
sun, sun,
environment, environment,
@@ -67,10 +77,67 @@ func _ready() -> void:
weather_controller.name = "WeatherController" weather_controller.name = "WeatherController"
add_child(weather_controller) add_child(weather_controller)
func _process(_delta: float) -> void:
_process_pending_environment_nodes()
func _on_tree_node_added(node: Node) -> void:
if node.is_in_group("wind_node") or node.is_in_group("weather_node") or node.is_in_group("weather_vegetables_node"):
_queue_dynamic_environment_node(node)
func _queue_dynamic_environment_node(node: Node) -> void:
if not is_instance_valid(node):
return
var ancestor = node.get_parent()
while ancestor != null:
if pending_environment_nodes.has(ancestor.get_instance_id()):
return
ancestor = ancestor.get_parent()
var stale_ids: Array[int] = []
for node_id in pending_environment_nodes.keys():
var pending_node = pending_environment_nodes[node_id]
if not is_instance_valid(pending_node):
stale_ids.append(node_id)
continue
if node.is_ancestor_of(pending_node):
stale_ids.append(node_id)
for node_id in stale_ids:
pending_environment_nodes.erase(node_id)
pending_environment_nodes[node.get_instance_id()] = node
func _process_pending_environment_nodes() -> void:
var processed = 0
for node_id in pending_environment_nodes.keys():
if processed >= DYNAMIC_ENVIRONMENT_UPDATES_PER_FRAME:
break
var node = pending_environment_nodes[node_id]
pending_environment_nodes.erase(node_id)
if not is_instance_valid(node):
continue
_apply_dynamic_environment_materials(node)
processed += 1
func _apply_dynamic_environment_materials(node: Node) -> void:
if not is_instance_valid(node):
return
if node.is_in_group("wind_node"):
_apply_wind_noise_to_node(node, NOISE_TEXTURE)
if node.is_in_group("weather_node"):
_apply_weather_overlay_to_node(node, WEATHER_SHADER)
if node.is_in_group("weather_vegetables_node"):
_clear_weather_overlay_from_node(node)
func ApplyWindNoiseToMaterials(): func ApplyWindNoiseToMaterials():
var noise_tex = preload("res://core/daynight/noise.tres")
for node in get_tree().get_nodes_in_group("wind_node"): for node in get_tree().get_nodes_in_group("wind_node"):
_apply_wind_noise_to_node(node, noise_tex) _apply_wind_noise_to_node(node, NOISE_TEXTURE)
func _apply_wind_noise_to_node(node: Node, noise_tex: Texture2D) -> void: func _apply_wind_noise_to_node(node: Node, noise_tex: Texture2D) -> void:
if node is GeometryInstance3D: if node is GeometryInstance3D:
@@ -88,15 +155,57 @@ func _apply_wind_noise_to_node(node: Node, noise_tex: Texture2D) -> void:
_apply_wind_noise_to_node(child, noise_tex) _apply_wind_noise_to_node(child, noise_tex)
func ApplyWeatherShaderToMaterials(): func ApplyWeatherShaderToMaterials():
var weather_shader = preload("res://core/daynight/weather_overlay.tres")
for node in get_tree().get_nodes_in_group("weather_node"): for node in get_tree().get_nodes_in_group("weather_node"):
if node is GeometryInstance3D: _apply_weather_overlay_to_node(node, WEATHER_SHADER)
node.material_overlay = weather_shader
for node in get_tree().get_nodes_in_group("weather_vegetables_node"):
_clear_weather_overlay_from_node(node)
func _apply_weather_overlay_to_node(node: Node, material: Material) -> void:
if node.is_in_group("weather_vegetables_node"):
_clear_weather_overlay_from_node(node)
return
if node is GeometryInstance3D:
if _geometry_uses_alpha_texture(node):
node.material_overlay = null
else: else:
for child in node.get_children(): node.material_overlay = material
if child is GeometryInstance3D:
child.material_overlay = weather_shader for child in node.get_children():
_apply_weather_overlay_to_node(child, material)
func _clear_weather_overlay_from_node(node: Node) -> void:
if node is GeometryInstance3D:
node.material_overlay = null
for child in node.get_children():
_clear_weather_overlay_from_node(child)
func _geometry_uses_alpha_texture(node: GeometryInstance3D) -> bool:
var material_override := node.material_override as ShaderMaterial
if _shader_material_uses_alpha_texture(material_override):
return true
if node is MeshInstance3D:
for surface_index in node.get_surface_override_material_count():
var surface_material := node.get_surface_override_material(surface_index) as ShaderMaterial
if _shader_material_uses_alpha_texture(surface_material):
return true
if node.mesh:
for surface_index in node.mesh.get_surface_count():
var mesh_material := node.mesh.surface_get_material(surface_index) as ShaderMaterial
if _shader_material_uses_alpha_texture(mesh_material):
return true
return false
func _shader_material_uses_alpha_texture(material: ShaderMaterial) -> bool:
if material == null or material.shader == null:
return false
return material.shader.code.find("alpha_texture") != -1
func select_day_time(normalized_time: float) -> void: func select_day_time(normalized_time: float) -> void:
#set show_day_time_debug = true to show debug on screen #set show_day_time_debug = true to show debug on screen

View File

@@ -67,7 +67,6 @@ void fragment() {
float is_center = step(center_sharpness, internal_n); float is_center = step(center_sharpness, internal_n);
ALBEDO = shadow_color; ALBEDO = shadow_color;
ALPHA = mix(opacity_edge, opacity_center, is_center) * final_fade; ALPHA = mix(opacity_edge, opacity_center, is_center) * final_fade;
} }
} }

View File

@@ -8,7 +8,12 @@ global uniform float global_wind_speed;
global uniform float global_wind_strength; global uniform float global_wind_strength;
global uniform vec2 global_wind_direction; global uniform vec2 global_wind_direction;
//global uniform sampler2D global_wind_noise : filter_linear_mipmap; //global uniform sampler2D global_wind_noise : filter_linear_mipmap;
global uniform float global_snow_amount; global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform float global_rain_intensity;
// --- PARAMETRI ESTETICI --- // --- PARAMETRI ESTETICI ---
uniform bool billboard_enabled = true; uniform bool billboard_enabled = true;
@@ -28,6 +33,7 @@ uniform vec4 variance_color : source_color = vec4(0.3, 0.5, 0.2, 1.0); // Colore
uniform float variance_intensity : hint_range(0.0, 1.0) = 0.4; // Quanto si vedono le chiazze uniform float variance_intensity : hint_range(0.0, 1.0) = 0.4; // Quanto si vedono le chiazze
uniform vec4 snow_color : source_color = vec4(0.85, 0.9, 0.95, 1.0); uniform vec4 snow_color : source_color = vec4(0.85, 0.9, 0.95, 1.0);
uniform float snow_visibility : hint_range(0.0, 1.0) = 1.0;
// --- CONTROLLO GRADIENTE E RANDOM --- // --- CONTROLLO GRADIENTE E RANDOM ---
uniform float height_min = 0.0; uniform float height_min = 0.0;
@@ -38,6 +44,7 @@ uniform float light_steps : hint_range(1.0, 10.0) = 4.0;
uniform float random_mix : hint_range(0.0, 1.0) = 0.3; uniform float random_mix : hint_range(0.0, 1.0) = 0.3;
uniform float cast_shadow_strength : hint_range(0.0, 1.0) = 0.6; uniform float cast_shadow_strength : hint_range(0.0, 1.0) = 0.6;
uniform float wetness_darkening : hint_range(0.0, 0.5) = 0.25;
varying vec3 v_final_color; varying vec3 v_final_color;
varying float v_shade_factor; varying float v_shade_factor;
@@ -49,6 +56,21 @@ float hash(vec3 p) {
return fract((p.x + p.y) * p.z); return fract((p.x + p.y) * p.z);
} }
float get_snow_progress() {
float snow_progress = clamp(global_snow_amount, 0.0, 1.0);
if (global_snow_start_time >= 0.0) {
float timed_progress = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
snow_progress = max(snow_progress, timed_progress);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_progress = min(snow_progress, 1.0 - melt);
}
return snow_progress;
}
void vertex() { void vertex() {
vec3 instance_pos = MODEL_MATRIX[3].xyz; vec3 instance_pos = MODEL_MATRIX[3].xyz;
v_world_pos = instance_pos; // Salviamo la posizione dell'istanza v_world_pos = instance_pos; // Salviamo la posizione dell'istanza
@@ -110,21 +132,26 @@ void fragment() {
// Applichiamo la variazione al colore finale dell'erba // Applichiamo la variazione al colore finale dell'erba
vec3 varied_grass_color = mix(v_final_color, variance_color.rgb, noise_sample * variance_intensity); vec3 varied_grass_color = mix(v_final_color, variance_color.rgb, noise_sample * variance_intensity);
float snow_amount = smoothstep(0.0, 1.0, get_snow_progress());
float top_mask = 1.0 - shifted_uv.y; float top_mask = 1.0 - shifted_uv.y;
float snow_mask = smoothstep(1.0 - global_snow_amount, 1.2 - global_snow_amount, top_mask); float snow_mask = smoothstep(0.65, 1.0, top_mask) * snow_amount * snow_visibility;
snow_mask *= step(0.01, global_snow_amount); snow_mask *= step(0.01, snow_amount);
vec3 dark_snow = snow_color.rgb * (1.0 - shadow_intensity); vec3 dark_snow = snow_color.rgb * (1.0 - shadow_intensity);
vec3 shaded_snow = mix(dark_snow, snow_color.rgb, v_shade_factor); vec3 shaded_snow = mix(dark_snow, snow_color.rgb, v_shade_factor);
// Mescoliamo il colore variato con la neve // Mescoliamo il colore variato con la neve
vec3 final_albedo = mix(varied_grass_color, shaded_snow, snow_mask); vec3 final_albedo = mix(varied_grass_color, shaded_snow, snow_mask);
float rain_int = clamp(global_rain_intensity, 0.0, 1.0);
final_albedo *= mix(1.0, 1.0 - wetness_darkening, rain_int);
float final_roughness = mix(0.02, 0.005, rain_int);
ALBEDO = final_albedo; ALBEDO = final_albedo;
ALPHA = tex.r * opacity; ALPHA = tex.r * opacity;
ALPHA_SCISSOR_THRESHOLD = 0.5; ALPHA_SCISSOR_THRESHOLD = 0.5;
ROUGHNESS = 0.02; ROUGHNESS = final_roughness;
} }
void light() { void light() {

107
core/daynight/snow_cap.gd Normal file
View File

@@ -0,0 +1,107 @@
## Procedural snow cap geometry for flat roofs and vehicles.
extends Node3D
enum PlacementMode {
AUTO_TARGET_AABB,
MANUAL_SURFACE,
}
const SNOW_CAP_SHADER: Shader = preload("res://core/daynight/snow_cap.gdshader")
@export var placement_mode: PlacementMode = PlacementMode.AUTO_TARGET_AABB
@export var target_path: NodePath
@export var size_inset: Vector2 = Vector2(0.25, 1.0)
@export var position_offset: Vector3 = Vector3(0.0, -0.08, 0.0)
@export var max_snow_height: float = 0.2
@export_range(1, 24) var subdivide_width: int = 6
@export_range(1, 24) var subdivide_depth: int = 18
@export var manual_surface_center: Vector3 = Vector3.ZERO
@export var manual_surface_size: Vector2 = Vector2(2.0, 6.0)
var _mesh_instance: MeshInstance3D
func _ready() -> void:
add_to_group("weather_overlay_ignore")
_ensure_mesh_instance()
_rebuild()
func rebuild() -> void:
_rebuild()
func _ensure_mesh_instance() -> void:
_mesh_instance = get_node_or_null("SnowCapMesh") as MeshInstance3D
if _mesh_instance != null:
return
_mesh_instance = MeshInstance3D.new()
_mesh_instance.name = "SnowCapMesh"
_mesh_instance.add_to_group("weather_overlay_ignore")
add_child(_mesh_instance)
func _rebuild() -> void:
var cap_width: float = 0.0
var cap_depth: float = 0.0
var cap_surface_center := Vector3.ZERO
if placement_mode == PlacementMode.MANUAL_SURFACE:
cap_width = manual_surface_size.x
cap_depth = manual_surface_size.y
cap_surface_center = manual_surface_center
else:
var target_mesh: MeshInstance3D = get_node_or_null(target_path) as MeshInstance3D
if target_mesh == null:
if _mesh_instance:
_mesh_instance.visible = false
return
var target_aabb: AABB = _get_target_aabb(target_mesh)
cap_width = max(target_aabb.size.x - size_inset.x, 0.1)
cap_depth = max(target_aabb.size.z - size_inset.y, 0.1)
cap_surface_center = Vector3(
target_aabb.position.x + target_aabb.size.x * 0.5,
target_aabb.position.y + target_aabb.size.y,
target_aabb.position.z + target_aabb.size.z * 0.5
)
var cap_center := cap_surface_center + Vector3(0.0, max_snow_height * 0.5, 0.0)
var box_mesh := BoxMesh.new()
box_mesh.size = Vector3(cap_width, max_snow_height, cap_depth)
box_mesh.subdivide_width = subdivide_width
box_mesh.subdivide_depth = subdivide_depth
_mesh_instance.mesh = box_mesh
_mesh_instance.position = cap_center + position_offset
_mesh_instance.material_override = _build_material(cap_width, cap_depth)
_mesh_instance.visible = true
func _build_material(cap_width: float, cap_depth: float) -> ShaderMaterial:
var material := ShaderMaterial.new()
material.shader = SNOW_CAP_SHADER
material.set_shader_parameter("max_height", max_snow_height)
material.set_shader_parameter("half_width", cap_width * 0.5)
material.set_shader_parameter("half_depth", cap_depth * 0.5)
return material
func _get_target_aabb(target_mesh: MeshInstance3D) -> AABB:
var points: Array[Vector3] = []
for point in _get_aabb_points(target_mesh.get_aabb()):
points.append(target_mesh.transform * point)
var merged := AABB(points[0], Vector3.ZERO)
for point in points:
merged = merged.expand(point)
return merged
func _get_aabb_points(aabb: AABB) -> Array[Vector3]:
var p: Vector3 = aabb.position
var s: Vector3 = aabb.size
return [
p,
p + Vector3(s.x, 0.0, 0.0),
p + Vector3(0.0, s.y, 0.0),
p + Vector3(0.0, 0.0, s.z),
p + Vector3(s.x, s.y, 0.0),
p + Vector3(s.x, 0.0, s.z),
p + Vector3(0.0, s.y, s.z),
p + s,
]

View File

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

View File

@@ -0,0 +1,116 @@
shader_type spatial;
render_mode blend_mix, cull_back, depth_draw_opaque;
global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0);
uniform float max_height : hint_range(0.02, 1.0) = 0.2;
uniform float half_width : hint_range(0.01, 20.0) = 1.0;
uniform float half_depth : hint_range(0.01, 20.0) = 1.0;
uniform float noise_scale : hint_range(0.05, 2.0) = 0.35;
uniform float noise_strength : hint_range(0.0, 0.4) = 0.18;
uniform float edge_rounding : hint_range(0.0, 0.3) = 0.12;
uniform float side_shade : hint_range(0.2, 1.0) = 0.82;
uniform float sparkle_strength : hint_range(0.0, 0.1) = 0.03;
varying float v_snow_amount;
varying float v_noise;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float value_noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < 4; i++) {
value += value_noise(p * frequency) * amplitude;
frequency *= 2.0;
amplitude *= 0.5;
}
return clamp(value, 0.0, 1.0);
}
float get_snow_amount() {
float snow_amount = clamp(global_snow_amount, 0.0, 1.0);
if (global_snow_start_time >= 0.0) {
float timed_amount = clamp(
(TIME - global_snow_start_time) * global_snow_accumulation_speed,
0.0,
1.0
);
snow_amount = max(snow_amount, timed_amount);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_amount = min(snow_amount, 1.0 - melt);
}
return snow_amount;
}
void vertex() {
vec3 base_vertex = VERTEX;
vec3 world_pos = (MODEL_MATRIX * vec4(base_vertex, 1.0)).xyz;
float snow_amount = get_snow_amount();
float noise_val = fbm(world_pos.xz * noise_scale);
float thickness = max_height * snow_amount * mix(
1.0 - noise_strength,
1.0 + noise_strength,
noise_val
);
float footprint_growth = smoothstep(0.0, 0.35, snow_amount);
float from_bottom = clamp((base_vertex.y + max_height * 0.5) / max_height, 0.0, 1.0);
VERTEX.y = -max_height * 0.5 + thickness * from_bottom;
VERTEX.x *= mix(0.92, 1.0, footprint_growth);
VERTEX.z *= mix(0.94, 1.0, footprint_growth);
float edge_x = abs(base_vertex.x) / max(half_width, 0.001);
float edge_z = abs(base_vertex.z) / max(half_depth, 0.001);
float edge_mask = smoothstep(0.55, 1.0, max(edge_x, edge_z));
float top_mask = smoothstep(0.6, 1.0, from_bottom) * snow_amount;
float round_factor = edge_rounding * edge_mask * top_mask;
VERTEX.x *= 1.0 - round_factor;
VERTEX.z *= 1.0 - round_factor;
v_snow_amount = snow_amount;
v_noise = noise_val;
}
void fragment() {
if (v_snow_amount <= 0.001) {
ALPHA = 0.0;
ALBEDO = global_snow_color.rgb;
ROUGHNESS = 1.0;
SPECULAR = 0.0;
} else {
float facing_up = clamp(NORMAL.y, 0.0, 1.0);
float shade = mix(-sparkle_strength, sparkle_strength, v_noise);
vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0);
ALBEDO = mix(snow_albedo * side_shade, snow_albedo, facing_up);
ROUGHNESS = mix(0.95, 0.72, facing_up);
SPECULAR = 0.18;
ALPHA = smoothstep(0.0, 0.28, v_snow_amount);
}
}

View File

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

View File

@@ -5,10 +5,12 @@ global uniform float global_wind_speed;
global uniform vec2 global_wind_direction; global uniform vec2 global_wind_direction;
global uniform float global_wind_scale; global uniform float global_wind_scale;
global uniform float global_wind_strength; global uniform float global_wind_strength;
global uniform float global_snow_start_time; global uniform float global_wind_fade;
global uniform float global_snow_accumulation_speed; global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_melt_time; global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_speed; global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform vec4 global_snow_color; global uniform vec4 global_snow_color;
global uniform float global_rain_intensity; global uniform float global_rain_intensity;
@@ -29,6 +31,7 @@ uniform float light_steps : hint_range(1.0, 10.0) = 4.0;
uniform float random_mix : hint_range(0.0, 1.0) = 0.3; uniform float random_mix : hint_range(0.0, 1.0) = 0.3;
uniform float cast_shadow_strength : hint_range(0.0, 1.0) = 0.6; uniform float cast_shadow_strength : hint_range(0.0, 1.0) = 0.6;
uniform float wetness_darkening : hint_range(0.0, 0.5) = 0.25; uniform float wetness_darkening : hint_range(0.0, 0.5) = 0.25;
uniform float snow_visibility : hint_range(0.0, 1.0) = 1.0;
varying vec3 v_final_color; varying vec3 v_final_color;
varying float v_shade_factor; varying float v_shade_factor;
@@ -39,16 +42,31 @@ float hash(vec3 p) {
return fract((p.x + p.y) * p.z); return fract((p.x + p.y) * p.z);
} }
float get_snow_progress() {
float snow_progress = clamp(global_snow_amount, 0.0, 1.0);
if (global_snow_start_time >= 0.0) {
float timed_progress = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
snow_progress = max(snow_progress, timed_progress);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_progress = min(snow_progress, 1.0 - melt);
}
return snow_progress;
}
void vertex() { void vertex() {
vec3 instance_pos = MODEL_MATRIX[3].xyz; vec3 instance_pos = MODEL_MATRIX[3].xyz;
float total_angle = radians(rotation_degrees); float total_angle = radians(rotation_degrees);
if (wind_enabled && global_wind_speed > 0.0 && global_wind_strength > 0.0) { if (wind_enabled && global_wind_speed > 0.0 && global_wind_strength > 0.0 && global_wind_fade > 0.0) {
float time = TIME * global_wind_speed; float time = TIME * global_wind_speed;
vec2 noise_uv = (instance_pos.xz * global_wind_scale) + (time * global_wind_direction * 0.5); vec2 noise_uv = (instance_pos.xz * global_wind_scale) + (time * global_wind_direction * 0.5);
float noise_val = textureLod(wind_noise, noise_uv, 2.0).r; float noise_val = textureLod(wind_noise, noise_uv, 2.0).r;
float sway = sin(time + (noise_val * 10.0)); float sway = sin(time + (noise_val * 10.0));
total_angle += (sway * global_wind_strength); total_angle += (sway * global_wind_strength * global_wind_fade);
} }
float h_factor = clamp((instance_pos.y - height_min) / (height_max - height_min), 0.0, 1.0); float h_factor = clamp((instance_pos.y - height_min) / (height_max - height_min), 0.0, 1.0);
@@ -87,17 +105,10 @@ void fragment() {
vec4 tex = texture(alpha_texture, shifted_uv); vec4 tex = texture(alpha_texture, shifted_uv);
// Snow accumulation // Snow accumulation
float snow_amount = 0.0; float snow_amount = pow(get_snow_progress(), 0.55);
if (global_snow_start_time >= 0.0) {
snow_amount = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_amount *= (1.0 - melt);
}
float top_mask = 1.0 - shifted_uv.y; float top_mask = 1.0 - shifted_uv.y;
float snow_mask = smoothstep(1.0 - snow_amount, 1.2 - snow_amount, top_mask); float snow_mask = smoothstep(1.0 - snow_amount * 1.35, 1.08 - snow_amount * 1.35, top_mask) * snow_visibility;
snow_mask *= step(0.01, snow_amount); snow_mask *= step(0.01, snow_amount);
vec3 dark_snow = global_snow_color.rgb * (1.0 - shadow_intensity); vec3 dark_snow = global_snow_color.rgb * (1.0 - shadow_intensity);

View File

@@ -1,22 +1,28 @@
shader_type spatial; shader_type spatial;
render_mode blend_mix, depth_draw_always; render_mode blend_mix, depth_draw_always;
// --- PARAMETRI COLORI ACQUA --- global uniform float global_rain_intensity;
global uniform vec4 global_water_color = vec4(0.285, 0.534, 0.487, 1.0);
global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0);
//Water color
uniform vec4 deep_water_color : source_color = vec4(0.0, 0.1, 0.2, 1.0); uniform vec4 deep_water_color : source_color = vec4(0.0, 0.1, 0.2, 1.0);
uniform vec4 shallow_water_color : source_color = vec4(0.0, 0.4, 0.7, 1.0); uniform vec4 shallow_water_color : source_color = vec4(0.0, 0.4, 0.7, 1.0);
// Impostato a 5.0 come piace a te per un'acqua super profonda!
uniform float beer_law_factor : hint_range(0.0, 10.0) = 5.0; uniform float beer_law_factor : hint_range(0.0, 10.0) = 5.0;
// ---> PARAMETRI FAKE REFLECTION (SPECCHIATA) <---
group_uniforms Fake_Reflection_Proximity; group_uniforms Fake_Reflection_Proximity;
uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap; uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
uniform float reflection_strength : hint_range(0.0, 1.0) = 0.6; uniform float reflection_strength : hint_range(0.0, 1.0) = 0.6;
uniform float reflection_offset : hint_range(-0.5, 0.5) = 0.05; // Offset base uniform float reflection_offset : hint_range(-0.5, 0.5) = 0.05; // Offset base
// LA MAGIA DELLO SPECCHIO: Valori positivi (es. 0.1 o 0.2) capovolgeranno l'immagine
uniform float reflection_stretch : hint_range(-1.0, 1.0) = 0.1; uniform float reflection_stretch : hint_range(-1.0, 1.0) = 0.1;
uniform float max_reflection_distance : hint_range(0.1, 10.0) = 1.5; uniform float max_reflection_distance : hint_range(0.1, 10.0) = 1.5;
// ---> PARAMETRI RIPPLES <--- //Ripples parameters
group_uniforms Ripple_Settings; group_uniforms Ripple_Settings;
uniform vec4 ripple_color : source_color = vec4(1.0, 1.0, 1.0, 0.7); uniform vec4 ripple_color : source_color = vec4(1.0, 1.0, 1.0, 0.7);
uniform float ripple_density : hint_range(0.0, 1.0) = 0.05; uniform float ripple_density : hint_range(0.0, 1.0) = 0.05;
@@ -29,14 +35,27 @@ uniform sampler2D depth_texture : hint_depth_texture, filter_linear_mipmap;
varying vec2 world_pos_xz; varying vec2 world_pos_xz;
varying vec2 local_uv; varying vec2 local_uv;
float get_snow_progress() {
float snow_progress = clamp(global_snow_amount, 0.0, 1.0);
if (global_snow_start_time >= 0.0) {
float timed_progress = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
snow_progress = max(snow_progress, timed_progress);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_progress = min(snow_progress, 1.0 - melt);
}
return snow_progress;
}
void vertex() { void vertex() {
// Salviamo la posizione XZ e la UV locale per l'effetto specchio
world_pos_xz = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xz; world_pos_xz = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xz;
local_uv = UV; local_uv = UV;
} }
void fragment() { void fragment() {
// --- 1. LOGICA PROFONDITÀ (Beer's Law) ---
float depth = texture(depth_texture, SCREEN_UV).r; float depth = texture(depth_texture, SCREEN_UV).r;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth); vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0); vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
@@ -46,13 +65,15 @@ void fragment() {
float water_depth_gradient = exp(-depth_difference * beer_law_factor); float water_depth_gradient = exp(-depth_difference * beer_law_factor);
vec4 base_water = mix(deep_water_color, shallow_water_color, water_depth_gradient); vec4 base_water = mix(deep_water_color, shallow_water_color, water_depth_gradient);
float rain_intensity = clamp(global_rain_intensity, 0.0, 1.0);
base_water.rgb = mix(base_water.rgb, global_water_color.rgb, 0.7);
// --- 2. LOGICA RIPPLES CASUALI ---
vec2 pos = world_pos_xz * ripple_scale; vec2 pos = world_pos_xz * ripple_scale;
vec2 cell = floor(pos); vec2 cell = floor(pos);
vec2 local_pos = fract(pos) - 0.5; vec2 local_pos = fract(pos) - 0.5;
float random_val = fract(sin(dot(cell, vec2(12.9898, 78.233))) * 43758.5453); float random_val = fract(sin(dot(cell, vec2(12.9898, 78.233))) * 43758.5453);
float is_active_cell = step(1.0 - ripple_density, random_val); float active_ripple_density = ripple_density * rain_intensity;
float is_active_cell = step(1.0 - active_ripple_density, random_val);
float ring = 0.0; float ring = 0.0;
if (is_active_cell > 0.0) { if (is_active_cell > 0.0) {
@@ -62,19 +83,15 @@ void fragment() {
- smoothstep(local_time, local_time + ripple_thickness, dist); - smoothstep(local_time, local_time + ripple_thickness, dist);
ring *= (1.0 - local_time); ring *= (1.0 - local_time);
} }
ring *= rain_intensity;
// --- 3. FAKE REFLECTION SPECCHIATA E A CORTO RAGGIO ---
vec2 ref_uv = SCREEN_UV; vec2 ref_uv = SCREEN_UV;
// Il trucco per "capovolgere" il riflesso usando l'UV del piano dell'acqua
ref_uv.y -= reflection_offset + (local_uv.y * reflection_stretch); ref_uv.y -= reflection_offset + (local_uv.y * reflection_stretch);
// Distorciamo il riflesso dove ci sono i ripples
ref_uv += ring * 0.01; ref_uv += ring * 0.01;
vec3 screen_ref = textureLod(screen_texture, ref_uv, 0.0).rgb; vec3 screen_ref = textureLod(screen_texture, ref_uv, 0.0).rgb;
// Maschera di distanza (limita il riflesso solo alle cose vicine)
float ref_depth_raw = texture(depth_texture, ref_uv).r; float ref_depth_raw = texture(depth_texture, ref_uv).r;
vec3 ref_ndc = vec3(ref_uv * 2.0 - 1.0, ref_depth_raw); vec3 ref_ndc = vec3(ref_uv * 2.0 - 1.0, ref_depth_raw);
vec4 ref_view = INV_PROJECTION_MATRIX * vec4(ref_ndc, 1.0); vec4 ref_view = INV_PROJECTION_MATRIX * vec4(ref_ndc, 1.0);
@@ -86,13 +103,18 @@ void fragment() {
float is_sky = step(ref_depth_raw, 0.00001); // Protezione anti-cielo float is_sky = step(ref_depth_raw, 0.00001); // Protezione anti-cielo
reflection_mask *= (1.0 - is_sky); reflection_mask *= (1.0 - is_sky);
// --- 4. MIX FINALE --- float snow_progress = get_snow_progress();
vec3 final_rgb = mix(base_water.rgb, screen_ref, reflection_strength * reflection_mask); float active_snowfall = step(0.0, global_snow_start_time) * (1.0 - step(0.0, global_snow_melt_time));
float reflection_snow_damping = max(smoothstep(0.0, 0.08, snow_progress), active_snowfall * 0.75);
float effective_reflection_strength = reflection_strength * (1.0 - reflection_snow_damping * 0.85);
vec3 final_rgb = mix(base_water.rgb, screen_ref, effective_reflection_strength * reflection_mask);
final_rgb = mix(final_rgb, ripple_color.rgb, ring * ripple_color.a); final_rgb = mix(final_rgb, ripple_color.rgb, ring * ripple_color.a);
float water_snow_amount = smoothstep(0.15, 1.0, snow_progress) * 0.18;
final_rgb = mix(final_rgb, global_snow_color.rgb, water_snow_amount);
ALBEDO = final_rgb; ALBEDO = final_rgb;
// Alpha base dell'acqua + opacità dei ripples per farli sempre risaltare
ALPHA = mix(0.98, 0.4, water_depth_gradient); ALPHA = mix(0.98, 0.4, water_depth_gradient);
ALPHA = clamp(ALPHA + (ring * ripple_color.a), 0.0, 1.0); ALPHA = clamp(ALPHA + (ring * ripple_color.a), 0.0, 1.0);

View File

@@ -0,0 +1,212 @@
#define USE_CAUSTICS 1
#define USE_REFRACTION 1
#define USE_DISPLACEMENT 1
#define USE_STYLIZED_LIGHTING 0
#define USE_UNSHADED 0
shader_type spatial;
#if USE_UNSHADED
render_mode unshaded, depth_draw_never;
#else
render_mode depth_draw_never;
#endif
uniform sampler2D DEPTH_TEXTURE: hint_depth_texture;
group_uniforms Color;
uniform vec4 surface_color : source_color = vec4(0.2,1.0,0.8,1.0);
uniform vec4 depth_color : source_color = vec4(0.08,0.2,0.4,1.0);
uniform vec4 foam_color : source_color = vec4(1.0);
uniform float depth_size = 12.0;
group_uniforms Roughness;
uniform float surface_roughness : hint_range(0.0, 1.0, 0.01) = 0.05;
uniform float foam_roughness : hint_range(0.0, 1.0, 0.01) = 0.05;
#if USE_CAUSTICS
group_uniforms Caustics;
uniform sampler2D caustics_texture;
uniform float caustics_strength = 2.0;
uniform vec2 caustics_scale = vec2(0.5);
#endif
group_uniforms Wave;
uniform sampler2D wave_texture;
#if !USE_UNSHADED
uniform sampler2D wave_normal_texture;
#endif
uniform float wave_softness : hint_range(0.0, 10.0, 0.1) = 3.0;
uniform vec2 wave_scale = vec2(0.2);
uniform vec2 wave_layer_scale = vec2(1.5);
uniform float wave_highlight : hint_range(0.0, 1.0, 0.05) = 0.5;
group_uniforms Wave.Motion;
uniform vec2 wave_velocity = vec2(0.02);
group_uniforms Foam;
uniform sampler2D foam_texture;
uniform float edge_foam_depth_size = 1.0;
uniform float wave_foam_amount : hint_range(0.0, 1.0, 0.01) = 0.8;
uniform float foam_start : hint_range(0.0, 1.0, 0.05) = 0.15;
uniform float foam_end : hint_range(0.0, 1.0, 0.05) = 0.3;
uniform float foam_exponent = 2.0;
#if USE_REFRACTION
group_uniforms Refraction;
uniform float refraction_amount = 0.5;
uniform float refraction_exponent = 0.5;
#endif
#if USE_DISPLACEMENT
group_uniforms Displacement;
uniform float displacement_amount = 0.3;
#endif
#if USE_STYLIZED_LIGHTING && !USE_UNSHADED
group_uniforms Lighting;
uniform float diffuse_steps = 12.0;
uniform float diffuse_smoothness : hint_range(0.0, 1.0, 0.01) = 0.2;
uniform float specular_steps = 12.0;
uniform float specular_smoothness : hint_range(0.0, 1.0, 0.01) = 0.2;
#endif
uniform sampler2D screen_texture : hint_screen_texture;
varying vec3 world_pos;
#if USE_CAUSTICS
vec3 sample_caustics(vec2 uv){
vec2 caustics_uv = uv * caustics_scale;
return vec3(
texture(caustics_texture, caustics_uv).r,
texture(caustics_texture, caustics_uv+vec2(0.02,0.02)).r,
texture(caustics_texture, caustics_uv+vec2(0.03,0.01)).r
);
}
#endif
vec4 sample_world_dpos(vec2 screen_uv, mat4 inv_proj_mat, mat4 inv_view_mat){
vec4 clip_pos = vec4(screen_uv * 2.0 - 1.0, texture(DEPTH_TEXTURE, screen_uv).r, 1.0);
vec4 view_pos = inv_proj_mat * clip_pos;
view_pos /= view_pos.w;
vec4 world_dpos = inv_view_mat * view_pos;
return world_dpos;
}
vec4 sample_wave(sampler2D tex, vec2 uv, vec2 velocity, float lod){
vec2 base_uv = uv * wave_scale;
vec2 wave_uv1 = (base_uv * wave_layer_scale) + (TIME * -velocity);
float wave1 = textureLod(tex, wave_uv1, lod).r;
vec2 wave_uv2 = base_uv + (TIME * velocity);
vec4 wave2 = textureLod(tex, wave_uv2 - (wave1 * 0.1), lod);
return wave2;
}
void vertex(){
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
#if USE_DISPLACEMENT
float wave = sample_wave(wave_texture, world_pos.xz, wave_velocity, wave_softness).r;
VERTEX.y += wave*displacement_amount;
#endif
}
void fragment() {
float wave = sample_wave(wave_texture, world_pos.xz, wave_velocity, wave_softness).r;
wave = smoothstep(0.0,1.0,wave);
vec2 screen_uv = SCREEN_UV;
// Refraction
#if USE_REFRACTION
screen_uv += ((pow(wave, refraction_exponent)*2.0 - 0.5) * 0.01 * refraction_amount);
vec4 world_dpos = sample_world_dpos(screen_uv, INV_PROJECTION_MATRIX, INV_VIEW_MATRIX);
float pre_depth = pow(clamp((world_dpos.y - world_pos.y + depth_size)/depth_size, 0.0, 1.0), 4.0);
screen_uv = mix(screen_uv, SCREEN_UV, pre_depth);
if(world_dpos.y - world_pos.y > 0.0){
screen_uv = SCREEN_UV;
}
#else
vec4 world_dpos = sample_world_dpos(screen_uv, INV_PROJECTION_MATRIX, INV_VIEW_MATRIX);
#endif
world_dpos = sample_world_dpos(screen_uv, INV_PROJECTION_MATRIX, INV_VIEW_MATRIX);
vec2 surface_uv = world_dpos.xz * 0.2;
float depth = pow(clamp((world_dpos.y - world_pos.y + depth_size)/depth_size, 0.0, 1.0), 4.0);
// Caustics
#if USE_CAUSTICS
vec3 caustics1 = sample_caustics(surface_uv + (TIME * -wave_velocity));
vec3 caustics2 = sample_caustics((surface_uv + (caustics1.r*0.05)) + (TIME * (wave_velocity*0.5)));
vec3 caustics = caustics2 * (1.0 - depth);
#endif
// Edge Foam
float edge_foam_depth = clamp((world_dpos.y - world_pos.y + edge_foam_depth_size)/edge_foam_depth_size, 0.0, 1.0);
// Wave Foam
float wave_foam = wave;
float foam = max(edge_foam_depth, wave_foam * wave_foam_amount);
float foam_shape = 1.0 - texture(foam_texture, world_pos.xz* 0.5).r;
foam = clamp((foam - foam_start) / (foam_end - foam_start), 0.0, 1.0);
foam = clamp((foam - foam_shape) / (1.0 - foam_shape), 0.0, 1.0);
foam = pow(foam, foam_exponent);
vec3 flat_color = mix(depth_color, surface_color, depth).rgb;
vec4 screen = texture(screen_texture, screen_uv);
vec3 color = screen.rgb;
#if USE_CAUSTICS
color += vec3(pow(caustics * caustics_strength, vec3(2.0)));
#endif
color = mix(flat_color, color, 0.4 * depth);
color = mix(color, surface_color.rgb, wave * wave_highlight);
color = mix(color, foam_color.rgb, foam);
#if !USE_UNSHADED
vec3 wave_normal_map = sample_wave(wave_normal_texture, world_pos.xz, wave_velocity, wave_softness).rgb;
NORMAL_MAP = wave_normal_map;
#endif
ROUGHNESS = mix(surface_roughness, foam_roughness, foam);
ALBEDO = color;
}
#if USE_STYLIZED_LIGHTING && !USE_UNSHADED
void light(){
float ndotl = dot(NORMAL, LIGHT) * ATTENUATION;
//ndotl = smoothstep(0.0,1.0-ROUGHNESS,ndotl);
float light = ndotl;
float light_mult = light * diffuse_steps;
float light_step_base = floor(light_mult);
float light_factor = light_mult - light_step_base;
light_factor = smoothstep(0.5 - diffuse_smoothness * 0.5, 0.5 + diffuse_smoothness * 0.5, light_factor);
light = (light_step_base + light_factor) / diffuse_steps;
DIFFUSE_LIGHT += (LIGHT_COLOR+ALBEDO) * light / PI;
float roughness = mix(0.01, 0.99, ROUGHNESS);
vec3 h = normalize(VIEW + LIGHT);
float ndoth = clamp(dot(NORMAL, h), 0.0, 1.0) * ATTENUATION;
float specular = clamp(pow(ndoth, 16.0/(roughness)), 0.1, 0.99);
specular = mix(pow(specular, 2.0-roughness),0.00,pow(roughness, 0.1));
float specular_mult = specular * specular_steps;
float specular_step_base = floor(specular_mult);
float specular_factor = specular_mult - specular_step_base;
specular_factor = smoothstep(0.5 - specular_smoothness * 0.5, 0.5 + specular_smoothness * 0.5, specular_factor);
specular = (specular_step_base + specular_factor) / specular_steps;
SPECULAR_LIGHT += (LIGHT_COLOR + ALBEDO) * specular;
}
#endif

View File

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

View File

@@ -14,6 +14,7 @@ var godray: PackedScene
var environment_dust: ColorRect var environment_dust: ColorRect
var blur: ColorRect var blur: ColorRect
var environment_shadows: MeshInstance3D var environment_shadows: MeshInstance3D
var fog: Node3D
var sun: DirectionalLight3D var sun: DirectionalLight3D
var environment: WorldEnvironment var environment: WorldEnvironment
@@ -33,21 +34,32 @@ var rain_tween: Tween
var rain_audio_tween: Tween var rain_audio_tween: Tween
var puddle_tween: Tween var puddle_tween: Tween
var puddle_amount: float = 0.0 var puddle_amount: float = 0.0
var clouds_tween: Tween
var snow_tween: Tween var snow_tween: Tween
var snow_weather_tween: Tween
var snow_particles_tween: Tween var snow_particles_tween: Tween
var is_snowing: bool = false var is_snowing: bool = false
var is_snow_accumulated: bool = false var is_snow_accumulated: bool = false
var actual_snow_amount: float = 0.0 var actual_snow_amount: float = 0.0
var snow_weather_amount: float = 0.0
var is_storm: bool = false var is_storm: bool = false
var cold_tween: Tween var cold_tween: Tween
var wind_tween: Tween
var is_windy: bool = false var is_windy: bool = false
var thereare_fireflies: bool = false var thereare_fireflies: bool = false
var current_wind_speed: float = 0.0
var current_wind_strength: float = 0.0
var current_wind_fade: float = 0.0
var max_wind_amount: int = 0
var random_weather_restore_tween: Tween
var random_event_remaining: float = 0.0
var random_event_label_seconds: int = -1
func _init(wind: GPUParticles3D, snow: GPUParticles3D, func _init(wind: GPUParticles3D, snow: GPUParticles3D,
fireflies: GPUParticles3D, rain: GPUParticles3D, ray: PackedScene, fireflies: GPUParticles3D, rain: GPUParticles3D, ray: PackedScene,
dust: ColorRect, blurr: ColorRect, envshadows: MeshInstance3D, dust: ColorRect, blurr: ColorRect, thefog: Node3D, envshadows: MeshInstance3D,
thesun: DirectionalLight3D, theenvironment: WorldEnvironment, environmentconfig: EnvironmentConfig, thesun: DirectionalLight3D, theenvironment: WorldEnvironment, environmentconfig: EnvironmentConfig,
thecamera: Camera3D, thecamerapivot: Node3D, thundersounds: Array[AudioStream], rainsounds: Array[AudioStream]) -> void: thecamera: Camera3D, thecamerapivot: Node3D, thundersounds: Array[AudioStream], rainsounds: Array[AudioStream]) -> void:
particles_wind = wind particles_wind = wind
@@ -58,9 +70,11 @@ func _init(wind: GPUParticles3D, snow: GPUParticles3D,
sun = thesun sun = thesun
environment_dust = dust environment_dust = dust
blur = blurr blur = blurr
fog = thefog
environment_shadows = envshadows environment_shadows = envshadows
environment = theenvironment environment = theenvironment
environment_config = environmentconfig environment_config = environmentconfig
max_wind_amount = environment_config.wind_amount if environment_config != null else 0
camera = thecamera camera = thecamera
camera_pivot = thecamerapivot camera_pivot = thecamerapivot
thunder_sounds = thundersounds thunder_sounds = thundersounds
@@ -80,6 +94,7 @@ func _init(wind: GPUParticles3D, snow: GPUParticles3D,
toggle_dust(environment_config.enable_dust) toggle_dust(environment_config.enable_dust)
toggle_blur(environment_config.enable_blur) toggle_blur(environment_config.enable_blur)
toggle_fog(environment_config.enable_fog)
#set camera pivot if available #set camera pivot if available
if camera_pivot: if camera_pivot:
@@ -91,13 +106,18 @@ func _ready() -> void:
UIEvents.toggle_rain.connect(toggle_rain) UIEvents.toggle_rain.connect(toggle_rain)
UIEvents.toggle_snow.connect(toggle_snow) UIEvents.toggle_snow.connect(toggle_snow)
UIEvents.toggle_wind.connect(toggle_wind) UIEvents.toggle_wind.connect(toggle_wind)
UIEvents.wind_change.connect(change_wind_strength)
UIEvents.trigger_random_weather.connect(trigger_random_weather_event)
UIEvents.toggle_fireflies.connect(toggle_fireflies) UIEvents.toggle_fireflies.connect(toggle_fireflies)
UIEvents.toggle_storm.connect(toggle_storm) UIEvents.toggle_storm.connect(toggle_storm)
UIEvents.toggle_dust.connect(toggle_dust) UIEvents.toggle_dust.connect(toggle_dust)
UIEvents.toggle_blur.connect(toggle_blur) UIEvents.toggle_blur.connect(toggle_blur)
UIEvents.toggle_fog.connect(toggle_fog)
UIEvents.toggle_shadows.connect(toggle_shadows) UIEvents.toggle_shadows.connect(toggle_shadows)
_emit_weather_event_label()
func _process(delta: float) -> void: func _process(delta: float) -> void:
_update_random_event_label(delta)
_follow_camera() _follow_camera()
@@ -115,6 +135,7 @@ func _process(delta: float) -> void:
var base_grad_top: Color var base_grad_top: Color
var base_grad_bot: Color var base_grad_bot: Color
var base_grad_intensity: float var base_grad_intensity: float
var base_water_color: Color
var base_rotation_sun: Vector3 var base_rotation_sun: Vector3
if is_raining: if is_raining:
@@ -142,6 +163,7 @@ func _process(delta: float) -> void:
base_grad_top = environment_config.grad_top_morning.lerp(environment_config.grad_top_afternoon, t) base_grad_top = environment_config.grad_top_morning.lerp(environment_config.grad_top_afternoon, t)
base_grad_bot = environment_config.grad_top_morning.lerp(environment_config.grad_bot_afternoon, t) base_grad_bot = environment_config.grad_top_morning.lerp(environment_config.grad_bot_afternoon, t)
base_grad_intensity = lerp(environment_config.grad_intensity_morning, environment_config.grad_intensity_afternoon, t) base_grad_intensity = lerp(environment_config.grad_intensity_morning, environment_config.grad_intensity_afternoon, t)
base_water_color = environment_config.water_color_morning.lerp(environment_config.water_color_afternoon, t)
base_rotation_sun = environment_config.sun_rotation_morning.lerp(environment_config.sun_rotation_afternoon, t) base_rotation_sun = environment_config.sun_rotation_morning.lerp(environment_config.sun_rotation_afternoon, t)
else: else:
var t = day_time - 2.0 var t = day_time - 2.0
@@ -155,6 +177,7 @@ func _process(delta: float) -> void:
base_grad_top = environment_config.grad_top_afternoon.lerp(environment_config.grad_top_night, t) base_grad_top = environment_config.grad_top_afternoon.lerp(environment_config.grad_top_night, t)
base_grad_bot = environment_config.grad_bot_afternoon.lerp(environment_config.grad_bot_night, t) base_grad_bot = environment_config.grad_bot_afternoon.lerp(environment_config.grad_bot_night, t)
base_grad_intensity = lerp(environment_config.grad_intensity_afternoon, environment_config.grad_intensity_night, t) base_grad_intensity = lerp(environment_config.grad_intensity_afternoon, environment_config.grad_intensity_night, t)
base_water_color = environment_config.water_color_afternoon.lerp(environment_config.water_color_night, t)
base_rotation_sun = environment_config.sun_rotation_afternoon.lerp(environment_config.sun_rotation_night, t) base_rotation_sun = environment_config.sun_rotation_afternoon.lerp(environment_config.sun_rotation_night, t)
var weather_color = environment_config.rain_mode_color var weather_color = environment_config.rain_mode_color
@@ -166,11 +189,12 @@ func _process(delta: float) -> void:
var final_sky_horizon = base_sky_horizon.lerp(base_sky_horizon * weather_color, clamp(rain_intensity, 0.0, 1.0)) var final_sky_horizon = base_sky_horizon.lerp(base_sky_horizon * weather_color, clamp(rain_intensity, 0.0, 1.0))
var final_fog_color = base_fog_color.lerp(base_fog_color * weather_color, clamp(rain_intensity, 0.0, 1.0)) var final_fog_color = base_fog_color.lerp(base_fog_color * weather_color, clamp(rain_intensity, 0.0, 1.0))
var final_fog_density = lerp(base_fog_density, base_fog_density * 4.0, clamp(rain_intensity, 0.0, 1.0)) var final_fog_density = lerp(base_fog_density, base_fog_density * 4.0, clamp(rain_intensity, 0.0, 1.0))
var final_water_color = base_water_color.darkened(clamp(environment_config.water_darkening_rain, 0.0, 1.0) * clamp(rain_intensity, 0.0, 1.0))
final_tint = final_tint.lerp(final_tint * environment_config.snow_mode_color, actual_snow_amount) final_tint = final_tint.lerp(final_tint * environment_config.snow_mode_color, snow_weather_amount)
final_sky_top = final_sky_top.lerp(final_sky_top * environment_config.snow_mode_color, actual_snow_amount) final_sky_top = final_sky_top.lerp(final_sky_top * environment_config.snow_mode_color, snow_weather_amount)
final_sky_horizon = final_sky_horizon.lerp(final_sky_horizon * environment_config.snow_mode_color, actual_snow_amount) final_sky_horizon = final_sky_horizon.lerp(final_sky_horizon * environment_config.snow_mode_color, snow_weather_amount)
final_fog_color = final_fog_color.lerp(final_fog_color * environment_config.snow_mode_color, actual_snow_amount) final_fog_color = final_fog_color.lerp(final_fog_color * environment_config.snow_mode_color, snow_weather_amount)
#Shader parameters for global trunk_shader #Shader parameters for global trunk_shader
var final_grad_top = base_grad_top.lerp(base_grad_top * weather_color, clamp(rain_intensity, 0.0, 1.0)) var final_grad_top = base_grad_top.lerp(base_grad_top * weather_color, clamp(rain_intensity, 0.0, 1.0))
@@ -179,12 +203,13 @@ func _process(delta: float) -> void:
RenderingServer.global_shader_parameter_set("global_gradient_color_top", final_grad_top) RenderingServer.global_shader_parameter_set("global_gradient_color_top", final_grad_top)
RenderingServer.global_shader_parameter_set("global_gradient_color_bot", final_grad_bot) RenderingServer.global_shader_parameter_set("global_gradient_color_bot", final_grad_bot)
RenderingServer.global_shader_parameter_set("global_gradient_intensity", base_grad_intensity) RenderingServer.global_shader_parameter_set("global_gradient_intensity", base_grad_intensity)
RenderingServer.global_shader_parameter_set("global_water_color", final_water_color)
var night_val = clamp(day_time - 2.0, 0.0, 1.0) var night_val = clamp(day_time - 2.0, 0.0, 1.0)
#Snow exposure compensation #Snow exposure compensation
var snow_light_attenuation = lerp(1.0, 0.55, actual_snow_amount * (1.0 - night_val)) var snow_light_attenuation = lerp(1.0, 0.55, snow_weather_amount * (1.0 - night_val))
var snow_glow_attenuation = lerp(1.0, 0.5, actual_snow_amount) var snow_glow_attenuation = lerp(1.0, 0.5, snow_weather_amount)
var final_bloom = lerp(base_bloom, base_bloom * 0.5, rain_intensity) * snow_glow_attenuation var final_bloom = lerp(base_bloom, base_bloom * 0.5, rain_intensity) * snow_glow_attenuation
# We calculate the final exposure by applying snow damping directly to the camera exposure # We calculate the final exposure by applying snow damping directly to the camera exposure
@@ -225,12 +250,18 @@ func _process(delta: float) -> void:
sky_mat.set_shader_parameter("sun_color", final_tint) sky_mat.set_shader_parameter("sun_color", final_tint)
sky_mat.set_shader_parameter("night_intensity", night_val) sky_mat.set_shader_parameter("night_intensity", night_val)
var cloud_density_amount: float = maxf(clamp(rain_intensity, 0.0, 1.0), clamp(snow_weather_amount, 0.0, 1.0))
var current_cloud_density: float = lerp(environment_config.base_cloud_density, environment_config.rain_cloud_density, cloud_density_amount)
if environment_config.material_clouds: if environment_config.material_clouds:
var current_density = lerp(0.4, 1.0, rain_intensity) environment_config.material_clouds.set_shader_parameter("cloud_density", current_cloud_density)
environment_config.material_clouds.set_shader_parameter("cloud_density", current_density) var current_sharpness = lerp(0.14, 0.0, cloud_density_amount)
var current_sharpness = lerp(0.14, 0.0, rain_intensity)
environment_config.material_clouds.set_shader_parameter("center_sharpness", current_sharpness) environment_config.material_clouds.set_shader_parameter("center_sharpness", current_sharpness)
var env_shadow_mat = environment_shadows.get_surface_override_material(0) if environment_shadows else null
if env_shadow_mat:
env_shadow_mat.set_shader_parameter("cloud_density", current_cloud_density)
if environment_config.material_fog: if environment_config.material_fog:
environment_config.material_fog.set_shader_parameter("night_intensity", night_val) environment_config.material_fog.set_shader_parameter("night_intensity", night_val)
environment_config.material_fog.set_shader_parameter("sun_color", final_tint) environment_config.material_fog.set_shader_parameter("sun_color", final_tint)
@@ -258,6 +289,23 @@ func _get_weather_anchor_position() -> Variant:
return camera.global_position return camera.global_position
return null return null
func _get_weather_anchor_basis() -> Variant:
var source_basis: Basis
if camera_pivot and is_instance_valid(camera_pivot):
source_basis = camera_pivot.global_basis
elif camera and is_instance_valid(camera):
source_basis = camera.global_basis
else:
return null
var forward := -source_basis.z
forward.y = 0.0
if forward.length_squared() <= 0.0001:
return Basis.IDENTITY
forward = forward.normalized()
var right := forward.cross(Vector3.UP).normalized()
return Basis(right, Vector3.UP, -forward)
func _follow_camera() -> void: func _follow_camera() -> void:
var cam_pos = _get_weather_anchor_position() var cam_pos = _get_weather_anchor_position()
if cam_pos == null: if cam_pos == null:
@@ -270,6 +318,12 @@ func _follow_camera() -> void:
particles_wind.global_position = Vector3(cam_pos.x, cam_pos.y, cam_pos.z) particles_wind.global_position = Vector3(cam_pos.x, cam_pos.y, cam_pos.z)
if particles_fireflies: if particles_fireflies:
particles_fireflies.global_position = Vector3(cam_pos.x, particles_fireflies.position.y, cam_pos.z) particles_fireflies.global_position = Vector3(cam_pos.x, particles_fireflies.position.y, cam_pos.z)
if fog:
fog.global_position = Vector3(cam_pos.x, fog.global_position.y, cam_pos.z)
#used to follow camera pivot or camera
var anchor_basis = _get_weather_anchor_basis()
if anchor_basis != null:
fog.global_basis = anchor_basis
#endregion #endregion
#region Fireflies #region Fireflies
@@ -300,9 +354,11 @@ func toggle_wind(value: bool):
particles_wind.emitting = is_windy particles_wind.emitting = is_windy
_apply_wind_state() _apply_wind_state()
_emit_weather_event_label()
#disable wind and set default values and materials #disable wind and set default values and materials
func init_wind(): func init_wind():
_update_wind_amount_from_strength(environment_config.wind_strength)
if particles_wind: if particles_wind:
particles_wind.visible = true particles_wind.visible = true
particles_wind.emitting = false particles_wind.emitting = false
@@ -334,16 +390,134 @@ func _apply_wind_config() -> void:
proc_mat_wind.direction = wind_axis proc_mat_wind.direction = wind_axis
particles_wind.global_transform = Transform3D(Basis(wind_cross, wind_axis, wind_up), particles_wind.global_position) particles_wind.global_transform = Transform3D(Basis(wind_cross, wind_axis, wind_up), particles_wind.global_position)
_apply_wind_state() _apply_wind_state(true)
func _apply_wind_state() -> void: func _apply_wind_state(immediate: bool = false) -> void:
if environment_config == null: if environment_config == null:
return return
if wind_tween and wind_tween.is_valid():
wind_tween.kill()
var active_wind_speed := environment_config.wind_speed if is_windy else 0.0 var active_wind_speed := environment_config.wind_speed if is_windy else 0.0
var active_wind_strength := environment_config.wind_strength if is_windy else 0.0 var active_wind_strength := environment_config.wind_strength if is_windy else 0.0
RenderingServer.global_shader_parameter_set("global_wind_speed", active_wind_speed) var active_wind_fade := 1.0 if is_windy else 0.0
RenderingServer.global_shader_parameter_set("global_wind_strength", active_wind_strength) if immediate:
_set_current_wind_speed(active_wind_speed)
_set_current_wind_strength(active_wind_strength)
_set_current_wind_fade(active_wind_fade)
return
_set_current_wind_speed(environment_config.wind_speed)
_set_current_wind_strength(environment_config.wind_strength)
wind_tween = create_tween()
wind_tween.tween_method(_set_current_wind_fade, current_wind_fade, active_wind_fade, environment_config.wind_fade_in_out_time)
wind_tween.tween_callback(_finish_wind_fade_out)
func _set_current_wind_speed(value: float) -> void:
current_wind_speed = value
RenderingServer.global_shader_parameter_set("global_wind_speed", value)
func _set_current_wind_strength(value: float) -> void:
current_wind_strength = value
RenderingServer.global_shader_parameter_set("global_wind_strength", value)
func _set_current_wind_fade(value: float) -> void:
current_wind_fade = value
RenderingServer.global_shader_parameter_set("global_wind_fade", value)
func _finish_wind_fade_out() -> void:
if is_windy:
return
_set_current_wind_speed(0.0)
_set_current_wind_strength(0.0)
_set_current_wind_fade(0.0)
func change_wind_strength(value: float) -> void:
if environment_config == null:
return
environment_config.wind_strength = value
_update_wind_amount_from_strength(value)
if is_windy or current_wind_fade > 0.0:
_set_current_wind_strength(value)
if particles_wind:
particles_wind.amount = environment_config.wind_amount
func _update_wind_amount_from_strength(value: float) -> void:
if environment_config == null:
return
if max_wind_amount <= 0:
max_wind_amount = max(environment_config.wind_amount, 1)
var normalized_strength: float = clamp(value, 0.0, 1.0)
var amount_ratio: float = normalized_strength
if normalized_strength > 0.0 and normalized_strength < 0.3:
amount_ratio = lerp(0.08, 0.25, normalized_strength / 0.3)
environment_config.wind_amount = roundi(max_wind_amount * amount_ratio)
func trigger_random_weather_event(duration: float = 0.0) -> void:
if random_weather_restore_tween and random_weather_restore_tween.is_valid():
random_weather_restore_tween.kill()
random_event_remaining = 0.0
random_event_label_seconds = -1
var previous_rain: bool = is_raining
var previous_snow: bool = is_snowing
var previous_wind: bool = is_windy
var previous_storm: bool = is_storm
var random_event_index: int = randi_range(0, 3)
match random_event_index:
0:
_apply_weather_event_state(false, true, false, false)
1:
_apply_weather_event_state(true, false, false, false)
2:
_apply_weather_event_state(false, false, true, false)
_:
_apply_weather_event_state(true, false, false, true)
if duration > 0.0:
random_event_remaining = duration
_emit_weather_event_label()
random_weather_restore_tween = create_tween()
random_weather_restore_tween.tween_interval(duration)
random_weather_restore_tween.tween_callback(func():
random_event_remaining = 0.0
random_event_label_seconds = -1
_apply_weather_event_state(previous_rain, previous_snow, previous_wind, previous_storm)
)
func _update_random_event_label(delta: float) -> void:
if random_event_remaining <= 0.0:
return
random_event_remaining = maxf(random_event_remaining - delta, 0.0)
var display_seconds: int = ceili(random_event_remaining)
if display_seconds != random_event_label_seconds:
random_event_label_seconds = display_seconds
_emit_weather_event_label()
func _apply_weather_event_state(rain_enabled: bool, snow_enabled: bool, wind_enabled: bool, storm_enabled: bool = false) -> void:
if is_storm and not storm_enabled:
toggle_storm(false)
if is_raining and not rain_enabled:
toggle_rain(false)
if is_snowing and not snow_enabled:
toggle_snow(false)
if is_windy and not wind_enabled:
toggle_wind(false)
if not is_raining and rain_enabled:
toggle_rain(true)
if not is_snowing and snow_enabled:
toggle_snow(true)
if not is_windy and wind_enabled:
toggle_wind(true)
if not is_storm and storm_enabled:
toggle_storm(true)
func _apply_cloud_config() -> void: func _apply_cloud_config() -> void:
var dir = environment_config.wind_direction var dir = environment_config.wind_direction
@@ -376,6 +550,7 @@ func _apply_cloud_config() -> void:
func toggle_storm(value: bool): func toggle_storm(value: bool):
is_storm = value is_storm = value
_emit_weather_event_label()
#set lightning #set lightning
func init_lightning(): func init_lightning():
@@ -489,6 +664,7 @@ func toggle_rain(value: bool):
is_raining = value is_raining = value
if not particles_rain: if not particles_rain:
_emit_weather_event_label()
return return
if is_raining and is_snowing: if is_raining and is_snowing:
@@ -522,6 +698,7 @@ func toggle_rain(value: bool):
trigger_after_rain()) trigger_after_rain())
puddle_tween = create_tween() puddle_tween = create_tween()
puddle_tween.tween_method(set_puddle_amount, puddle_amount, 0.0, environment_config.puddle_dry_time) puddle_tween.tween_method(set_puddle_amount, puddle_amount, 0.0, environment_config.puddle_dry_time)
_emit_weather_event_label()
func trigger_morning() -> void: func trigger_morning() -> void:
spawn_single_godray() spawn_single_godray()
@@ -618,12 +795,47 @@ func toggle_snow(value: bool):
var target_snow_amount: float = 1.0 if is_snowing else 0.0 var target_snow_amount: float = 1.0 if is_snowing else 0.0
if snow_tween and snow_tween.is_valid(): if snow_tween and snow_tween.is_valid():
snow_tween.kill() snow_tween.kill()
if snow_weather_tween and snow_weather_tween.is_valid():
snow_weather_tween.kill()
var snow_amount_transition_duration: float = _get_snow_amount_transition_duration(is_snowing)
snow_tween = create_tween() snow_tween = create_tween()
snow_tween.tween_method(init_snow_amount, actual_snow_amount, target_snow_amount, environment_config.snow_transaction_time) snow_tween.tween_method(
init_snow_amount,
actual_snow_amount,
target_snow_amount,
snow_amount_transition_duration
)
snow_weather_tween = create_tween()
snow_weather_tween.tween_method(
init_snow_weather_amount,
snow_weather_amount,
target_snow_amount,
environment_config.snow_fade_time
)
_emit_weather_event_label()
func _emit_weather_event_label() -> void:
var weather_label := "Weather: Clear"
if is_storm:
weather_label = "Weather: Storm"
else:
var active_events: Array[String] = []
if is_snowing:
active_events.append("Snow")
if is_raining:
active_events.append("Rain")
if is_windy:
active_events.append("Wind")
if not active_events.is_empty():
weather_label = "Weather: %s" % " + ".join(active_events)
if random_event_remaining > 0.0:
weather_label += " (%s'')" % ceili(random_event_remaining)
UIEvents.weather_event_changed.emit(weather_label)
#disable snow and set default values and shader #disable snow and set default values and shader
func init_snow(value: float = 0.0): func init_snow(value: float = 0.0):
actual_snow_amount = value actual_snow_amount = value
snow_weather_amount = value
RenderingServer.global_shader_parameter_set("global_snow_amount", value) RenderingServer.global_shader_parameter_set("global_snow_amount", value)
if particles_snow: if particles_snow:
@@ -634,6 +846,13 @@ func init_snow(value: float = 0.0):
RenderingServer.global_shader_parameter_set("global_snow_color", environment_config.snow_color) RenderingServer.global_shader_parameter_set("global_snow_color", environment_config.snow_color)
RenderingServer.global_shader_parameter_set("global_snow_accumulation_speed", environment_config.snow_accumulation_speed) RenderingServer.global_shader_parameter_set("global_snow_accumulation_speed", environment_config.snow_accumulation_speed)
RenderingServer.global_shader_parameter_set("global_snow_melt_speed", environment_config.snow_melt_speed) RenderingServer.global_shader_parameter_set("global_snow_melt_speed", environment_config.snow_melt_speed)
RenderingServer.global_shader_parameter_set("global_snow_max_accumulation", environment_config.snow_max_accumulation)
RenderingServer.global_shader_parameter_set("global_snow_cap_enabled", environment_config.show_snow_accumulation_volume)
RenderingServer.global_shader_parameter_set("global_snow_cap_height", environment_config.snow_cap_height)
RenderingServer.global_shader_parameter_set("global_snow_cap_flatness_start", environment_config.snow_cap_flatness_start)
RenderingServer.global_shader_parameter_set("global_snow_cap_flatness_end", environment_config.snow_cap_flatness_end)
RenderingServer.global_shader_parameter_set("global_snow_cap_noise_scale", environment_config.snow_cap_noise_scale)
RenderingServer.global_shader_parameter_set("global_snow_cap_noise_strength", environment_config.snow_cap_noise_strength)
RenderingServer.global_shader_parameter_set("global_snow_start_time", -1.0) RenderingServer.global_shader_parameter_set("global_snow_start_time", -1.0)
RenderingServer.global_shader_parameter_set("global_snow_melt_time", -1.0) RenderingServer.global_shader_parameter_set("global_snow_melt_time", -1.0)
@@ -641,6 +860,9 @@ func init_snow_amount(value: float):
actual_snow_amount = value actual_snow_amount = value
RenderingServer.global_shader_parameter_set("global_snow_amount", value) RenderingServer.global_shader_parameter_set("global_snow_amount", value)
func init_snow_weather_amount(value: float):
snow_weather_amount = value
func start_snow_accumulation() -> void: func start_snow_accumulation() -> void:
RenderingServer.global_shader_parameter_set("global_snow_melt_time", -1.0) RenderingServer.global_shader_parameter_set("global_snow_melt_time", -1.0)
RenderingServer.global_shader_parameter_set("global_snow_start_time", Time.get_ticks_msec() / 1000.0) RenderingServer.global_shader_parameter_set("global_snow_start_time", Time.get_ticks_msec() / 1000.0)
@@ -649,6 +871,18 @@ func start_snow_melt() -> void:
RenderingServer.global_shader_parameter_set("global_snow_melt_time", Time.get_ticks_msec() / 1000.0) RenderingServer.global_shader_parameter_set("global_snow_melt_time", Time.get_ticks_msec() / 1000.0)
RenderingServer.global_shader_parameter_set("global_snow_melt_speed", environment_config.snow_melt_speed) RenderingServer.global_shader_parameter_set("global_snow_melt_speed", environment_config.snow_melt_speed)
func _get_snow_amount_transition_duration(is_accumulating: bool) -> float:
if environment_config == null:
return 0.0
var speed: float = environment_config.snow_melt_speed
if is_accumulating:
speed = environment_config.snow_accumulation_speed
if speed <= 0.0:
return environment_config.snow_transaction_time
return 1.0 / speed
#endregion #endregion
#region Post-Process #region Post-Process
@@ -663,6 +897,10 @@ func toggle_blur(value: bool) -> void:
ApplyPostProcessBlurConfig() ApplyPostProcessBlurConfig()
func toggle_fog(value: bool) -> void:
if fog:
fog.visible = value
func toggle_shadows(value: bool) -> void: func toggle_shadows(value: bool) -> void:
if environment_shadows: if environment_shadows:
environment_shadows.visible = value environment_shadows.visible = value

View File

@@ -3,11 +3,19 @@ render_mode blend_mix, depth_draw_never;
//Snow globals //Snow globals
global uniform float global_snow_start_time = -1.0; global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_accumulation_speed = 0.1; global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_time = -1.0; global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed = 0.1; global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform float global_snow_threshold = 0.5; global uniform float global_snow_threshold = 0.5;
global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0); global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0);
global uniform float global_snow_max_accumulation = 0.72;
global uniform bool global_snow_cap_enabled = true;
global uniform float global_snow_cap_height = 0.06;
global uniform float global_snow_cap_flatness_start = 0.72;
global uniform float global_snow_cap_flatness_end = 0.96;
global uniform float global_snow_cap_noise_scale = 0.22;
global uniform float global_snow_cap_noise_strength = 0.18;
uniform float snow_noise_scale : hint_range(0.01, 1.0) = 0.15; uniform float snow_noise_scale : hint_range(0.01, 1.0) = 0.15;
uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.2; uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.2;
uniform float snow_roughness_variation : hint_range(0.0, 0.3) = 0.15; uniform float snow_roughness_variation : hint_range(0.0, 0.3) = 0.15;
@@ -29,6 +37,55 @@ uniform float puddle_threshold : hint_range(0.0, 0.8) = 0.45;
uniform sampler2D noise_texture : filter_linear_mipmap, repeat_enable; uniform sampler2D noise_texture : filter_linear_mipmap, repeat_enable;
varying float v_snow_amount;
varying float v_snow_cap_mask;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float value_noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < 3; i++) {
value += value_noise(p * frequency) * amplitude;
frequency *= 2.0;
amplitude *= 0.5;
}
return clamp(value, 0.0, 1.0);
}
float get_snow_progress() {
float snow_progress = clamp(global_snow_amount, 0.0, 1.0);
if (global_snow_start_time >= 0.0) {
float timed_progress = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
snow_progress = max(snow_progress, timed_progress);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_progress = min(snow_progress, 1.0 - melt);
}
return snow_progress;
}
float ripple_ring(vec2 uv, float time_offset) { float ripple_ring(vec2 uv, float time_offset) {
float d = length(uv); float d = length(uv);
float ring = sin(d * 30.0 - TIME * ripple_speed + time_offset) * 0.5 + 0.5; float ring = sin(d * 30.0 - TIME * ripple_speed + time_offset) * 0.5 + 0.5;
@@ -36,6 +93,27 @@ float ripple_ring(vec2 uv, float time_offset) {
return ring * fade; return ring * fade;
} }
void vertex() {
float snow_progress = get_snow_progress();
float snow_accumulation = snow_progress * global_snow_max_accumulation;
vec3 world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec3 world_normal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
float flat_surface = smoothstep(
max(global_snow_threshold, global_snow_cap_flatness_start),
global_snow_cap_flatness_end,
world_normal.y
);
float accumulation_growth = smoothstep(0.08, 0.65, snow_progress);
float cap_noise = fbm(world_pos.xz * global_snow_cap_noise_scale + vec2(11.3, 4.7));
float cap_variation = mix(1.0 - global_snow_cap_noise_strength, 1.0 + global_snow_cap_noise_strength, cap_noise);
v_snow_amount = snow_progress;
v_snow_cap_mask = flat_surface * accumulation_growth * cap_variation * (global_snow_cap_enabled ? 1.0 : 0.0);
VERTEX += NORMAL * (global_snow_cap_height * snow_accumulation * v_snow_cap_mask);
}
void fragment() { void fragment() {
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec3 world_normal = normalize((INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); vec3 world_normal = normalize((INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
@@ -43,25 +121,24 @@ void fragment() {
float noise_val = texture(noise_texture, world_pos.xz * snow_noise_scale).r; float noise_val = texture(noise_texture, world_pos.xz * snow_noise_scale).r;
//Snow //Snow
float snow_amount = 0.0; float snow_progress = v_snow_amount;
if (global_snow_start_time >= 0.0) { float snow_accumulation = snow_progress * global_snow_max_accumulation;
snow_amount = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_amount *= (1.0 - melt);
}
float snow_edge = smoothstep( float snow_edge = smoothstep(
global_snow_threshold - snow_edge_softness, global_snow_threshold - snow_edge_softness,
global_snow_threshold + snow_edge_softness, global_snow_threshold + snow_edge_softness,
facing_up + (noise_val - 0.5) * 0.4 facing_up
); );
float snow_coverage = smoothstep(0.0, 0.6, noise_val + snow_amount - 0.4); float flat_accumulation = smoothstep(0.0, 0.35, v_snow_cap_mask) * snow_accumulation;
float snow_factor = snow_edge * snow_coverage * snow_amount; float snow_variation = mix(0.75, 1.15, noise_val);
float snow_coverage = clamp(snow_progress * snow_variation + flat_accumulation * 0.25, 0.0, 1.0);
float snow_factor = max(snow_edge * snow_coverage, flat_accumulation);
float snow_opacity = clamp(snow_factor, 0.0, 1.0);
snow_opacity = max(snow_opacity, flat_accumulation * 0.9);
float shade = mix(-snow_color_variation, snow_color_variation, noise_val); float shade = mix(-snow_color_variation, snow_color_variation, noise_val);
vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0); vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0);
snow_albedo = mix(snow_albedo, vec3(1.0), 0.18 + snow_opacity * 0.12);
float snow_rough = mix(0.15 - snow_roughness_variation, 0.15 + snow_roughness_variation, noise_val); float snow_rough = mix(0.15 - snow_roughness_variation, 0.15 + snow_roughness_variation, noise_val);
//Rain //Rain
@@ -173,7 +250,7 @@ void fragment() {
ALBEDO = snow_albedo; ALBEDO = snow_albedo;
ROUGHNESS = snow_rough; ROUGHNESS = snow_rough;
METALLIC = 0.0; METALLIC = 0.0;
ALPHA = snow_factor; ALPHA = snow_opacity;
} else { } else {
ALBEDO = rain_albedo; ALBEDO = rain_albedo;
ROUGHNESS = rain_rough; ROUGHNESS = rain_rough;

View File

@@ -5,3 +5,17 @@
[resource] [resource]
render_priority = 0 render_priority = 0
shader = ExtResource("1_weather") shader = ExtResource("1_weather")
shader_parameter/snow_noise_scale = 0.15
shader_parameter/snow_edge_softness = 0.2
shader_parameter/snow_roughness_variation = 0.15
shader_parameter/snow_color_variation = 0.05
shader_parameter/ripple_scale = 1.5
shader_parameter/ripple_speed = 2.0
shader_parameter/ripple_layers = 3.0
shader_parameter/streak_scale = 2.0
shader_parameter/streak_speed = 0.8
shader_parameter/wetness_darkening = 0.25
shader_parameter/wet_roughness = 0.05
shader_parameter/rain_normal_strength = 0.4
shader_parameter/puddle_noise_scale = 0.08
shader_parameter/puddle_threshold = 0.45

View File

@@ -2,21 +2,19 @@ shader_type spatial;
render_mode blend_mix, depth_draw_never, cull_disabled; render_mode blend_mix, depth_draw_never, cull_disabled;
// Snow globals // Snow globals
global uniform float global_snow_start_time; global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_accumulation_speed; global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_time; global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed; global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform vec4 global_snow_color; global uniform vec4 global_snow_color;
// Rain globals
global uniform float global_rain_intensity;
global uniform float global_rain_puddle_amount;
// Snow parameters
uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.15; uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.15;
uniform float snow_color_variation : hint_range(0.0, 0.15) = 0.05; uniform float snow_color_variation : hint_range(0.0, 0.15) = 0.05;
// Rain wetness parameters // Rain wetness parameters
global uniform float global_rain_intensity;
global uniform float global_rain_puddle_amount;
uniform float ripple_scale : hint_range(0.1, 5.0) = 1.5; uniform float ripple_scale : hint_range(0.1, 5.0) = 1.5;
uniform float ripple_speed : hint_range(0.5, 5.0) = 2.0; uniform float ripple_speed : hint_range(0.5, 5.0) = 2.0;
uniform float ripple_layers : hint_range(1.0, 4.0) = 3.0; uniform float ripple_layers : hint_range(1.0, 4.0) = 3.0;
@@ -37,6 +35,21 @@ float ripple_ring(vec2 uv, float time_offset) {
return ring * fade; return ring * fade;
} }
float get_snow_progress() {
float snow_progress = clamp(global_snow_amount, 0.0, 1.0);
if (global_snow_start_time >= 0.0) {
float timed_progress = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
snow_progress = max(snow_progress, timed_progress);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_progress = min(snow_progress, 1.0 - melt);
}
return snow_progress;
}
void fragment() { void fragment() {
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
@@ -44,18 +57,10 @@ void fragment() {
float facing_up = 1.0; float facing_up = 1.0;
// Snow accumulation // Snow accumulation
float snow_amount = 0.0; float snow_amount = get_snow_progress();
if (global_snow_start_time >= 0.0) {
snow_amount = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0);
}
if (global_snow_melt_time >= 0.0) {
float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
snow_amount *= (1.0 - melt);
}
// UV-based snow mask: accumulates from the top of the quad // Plain surfaces fade in uniformly to avoid patchy strip-like accumulation.
float top_mask = 1.0 - UV.y; float snow_mask = smoothstep(0.0, 1.0, snow_amount);
float snow_mask = smoothstep(1.0 - snow_amount, 1.0 - snow_amount + snow_edge_softness, top_mask);
snow_mask *= step(0.01, snow_amount); snow_mask *= step(0.01, snow_amount);
// Snow color with slight variation // Snow color with slight variation

View File

@@ -1,7 +1,21 @@
[gd_resource type="ShaderMaterial" format=3] [gd_resource type="ShaderMaterial" format=3 uid="uid://b34bnqbrtxktw"]
[ext_resource type="Shader" path="res://core/daynight/weather_plain_shader.gdshader" id="1_weather_plain"] [ext_resource type="Shader" uid="uid://do8puw7u8dvry" path="res://core/daynight/weather_plain_shader.gdshader" id="1_weather_plain"]
[ext_resource type="Texture2D" uid="uid://bpswbjh4jj1ou" path="res://core/daynight/noise.tres" id="2_noise"]
[resource] [resource]
render_priority = 0 render_priority = 0
shader = ExtResource("1_weather_plain") shader = ExtResource("1_weather_plain")
shader_parameter/snow_edge_softness = 0.15
shader_parameter/snow_color_variation = 0.05
shader_parameter/ripple_scale = 1.5
shader_parameter/ripple_speed = 2.0
shader_parameter/ripple_layers = 3.0
shader_parameter/streak_scale = 2.0
shader_parameter/streak_speed = 0.8
shader_parameter/wetness_darkening = 0.25
shader_parameter/wet_roughness = 0.05
shader_parameter/rain_normal_strength = 0.4
shader_parameter/puddle_noise_scale = 0.08
shader_parameter/puddle_threshold = 0.45
shader_parameter/noise_texture = ExtResource("2_noise")

View File

@@ -1,31 +1,26 @@
shader_type spatial; shader_type spatial;
render_mode unshaded; render_mode unshaded;
global uniform float global_wind_speed = 1.0; //0.0 = calm, 1.0 = normal, 2.0+ = strong // --- PARAMETRI ---
global uniform vec2 global_wind_direction = vec2(1.0, 0.0); uniform float time_scale = 3.0;
uniform float wave_amplitude = 0.5; // Quanto curva a destra/sinistra
uniform float wave_frequency = 1.0; uniform float wave_frequency = 1.0; // Ogni quanto fa la curva
uniform float base_amplitude = 0.5;
uniform float secondary_ratio = 0.6;
uniform float time_scale_base = 3.0;
void vertex() { void vertex() {
// Creiamo un offset unico per ogni particella in modo che non si muovano tutte uguali
float random_seed_offset = INSTANCE_CUSTOM.x * 100.0; float random_seed_offset = INSTANCE_CUSTOM.x * 100.0;
float scaled_time = (TIME + random_seed_offset) * (time_scale_base * global_wind_speed); float scaled_time = (TIME + random_seed_offset) * time_scale;
float amplitude = base_amplitude * global_wind_speed;
vec2 wind_dir = normalize(global_wind_direction); // Applichiamo SOLO la curva morbida all'asse X (o Z, a seconda di come orienti la scena)
vec2 wind_perp = vec2(-wind_dir.y, wind_dir.x); //90° perpendicular // Usiamo VERTEX.y per far sì che la curva si propaghi lungo tutta la lunghezza della scia
VERTEX.x += sin(VERTEX.y * wave_frequency + scaled_time) * wave_amplitude;
float primary_sway = sin(VERTEX.y * wave_frequency + scaled_time) * amplitude; // Niente più "VERTEX.y += t;", quindi niente più teletrasporti!
float secondary_sway = sin(VERTEX.y * wave_frequency * 1.3 + scaled_time * 0.7 + 1.57)
* (amplitude * secondary_ratio);
VERTEX.x += primary_sway * wind_dir.x + secondary_sway * wind_perp.x;
VERTEX.z += primary_sway * wind_dir.y + secondary_sway * wind_perp.y;
} }
void fragment() { void fragment() {
// Il colore e l'alpha (per lo spawn dolce e la morte lenta)
// vengono comandati dalla Color Ramp del GPUParticles3D!
ALBEDO = COLOR.rgb; ALBEDO = COLOR.rgb;
ALPHA = COLOR.a; ALPHA = COLOR.a;
} }

View File

@@ -81,8 +81,9 @@ extends Resource
#Post-process effect toggles (initial values) #Post-process effect toggles (initial values)
@export_group("Post Process") @export_group("Post Process")
@export var enable_dust: bool = false #Floating dust particles overlay @export var enable_dust: bool = false #Floating dust particles overlay
@export var enable_blur: bool = false #Screen blur effect @export var enable_blur: bool = true #Screen blur effect
@export var enable_environment_shadows: bool = true #Cloud shadow projection on terrain @export var enable_environment_shadows: bool = true #Cloud shadow projection on terrain
@export var enable_fog: bool = false #Enable fog around the train
@export var blur_amount: float = 0.6 @export var blur_amount: float = 0.6
@export_group("Dust") @export_group("Dust")
@@ -97,19 +98,21 @@ extends Resource
@export var material_fog: ShaderMaterial #Fog overlay shader material @export var material_fog: ShaderMaterial #Fog overlay shader material
@export var material_drops: StandardMaterial3D #Rain drops material (albedo tinted by sky) @export var material_drops: StandardMaterial3D #Rain drops material (albedo tinted by sky)
@export var material_clouds: ShaderMaterial #Cloud layer shader material @export var material_clouds: ShaderMaterial #Cloud layer shader material
@export var base_cloud_density: float = 0.4 #default cloud density
#Rain weather settings #Rain weather settings
@export_group("Rain") @export_group("Rain")
@export var rain_mode_color: Color = Color(0.5, 0.6, 0.7, 1.0) #Sky/light darkening color during rain @export var rain_mode_color: Color = Color(0.85, 0.89, 0.93, 1.0) #Sky/light darkening color during rain
@export var rain_fade_time: float = 5.0 #Seconds for rain particles to fade in/out @export var rain_fade_time: float = 5.0 #Seconds for rain particles to fade in/out
@export var rain_audio_volume_db: float = -10.0 #Target rain loop volume in decibels @export var rain_audio_volume_db: float = -10.0 #Target rain loop volume in decibels
@export var puddle_form_time: float = 15.0 #Seconds for puddles to fully form @export var puddle_form_time: float = 15.0 #Seconds for puddles to fully form
@export var puddle_dry_time: float = 20.0 #Seconds for puddles to fully dry after rain stops @export var puddle_dry_time: float = 20.0 #Seconds for puddles to fully dry after rain stops
@export var rain_cloud_density: float = 1.0 #the cloud density when is rainy
#Storm settings (applied on top of rain when storm is active) #Storm settings (applied on top of rain when storm is active)
@export_group("Storm") @export_group("Storm")
@export var storm_rain_intensity_multiplier: float = 1.5 #Rain intensity target during storm (1.0 = normal rain) @export var storm_rain_intensity_multiplier: float = 1.5 #Rain intensity target during storm (1.0 = normal rain)
@export var storm_mode_color: Color = Color(0.476, 0.531, 0.639, 1.0) #Additional sky darkening color during storm @export var storm_mode_color: Color = Color(0.78, 0.83, 0.89, 1.0) #Additional sky darkening color during storm
#Lightning and thunder configuration #Lightning and thunder configuration
@export_group("Lightning and Thunders") @export_group("Lightning and Thunders")
@@ -133,15 +136,27 @@ extends Resource
@export var godray_spawn_height: float = 5.0 #Y world position where god rays spawn @export var godray_spawn_height: float = 5.0 #Y world position where god rays spawn
@export var godray_scale: Vector3 = Vector3(2.0, 6.0, 2.0) #Scale of the god ray mesh @export var godray_scale: Vector3 = Vector3(2.0, 6.0, 2.0) #Scale of the god ray mesh
#Train start settings
@export_group("Train Start")
@export var train_start_from_random_position: bool = true #When enabled the train starts from a random offset on the rail curve instead of a stop
@export_range(0, 64, 1, "or_greater") var train_start_stop_index: int = 1 #Stop index used for the train start when random start is disabled
#Snow settings #Snow settings
@export_group("Snow") @export_group("Snow")
@export var snow_amount: float = 0.0 #Initial snow coverage amount (0.0 = none, 1.0 = full) @export var snow_amount: float = 0.0 #Initial snow coverage amount (0.0 = none, 1.0 = full)
@export var snow_transaction_time: float = 10.0 #Seconds for snow shader to fully transition in/out @export var snow_transaction_time: float = 10.0 #Seconds for snow shader to fully transition in/out
@export var snow_fade_time: float = 5.0 #Seconds for snow particles to fade in/out @export var snow_fade_time: float = 5.0 #Seconds for snow particles to fade in/out
@export var snow_threshold: float = 0.4 #Normal Y threshold for snow accumulation on surfaces @export var snow_threshold: float = 0.4 #Normal Y threshold for snow accumulation on surfaces
@export var snow_accumulation_speed: float = 0.02 #Accumulation speed: 0.1 -> 10s, 0.05 -> 20s, 0.02 -> 50s, 0.2 -> 5s @export var snow_accumulation_speed: float = 0.005 #Accumulation speed: 0.005 -> ~200s, 0.01 -> 100s, 0.02 -> 50s
@export var snow_melt_speed: float = 0.05 #Speed at which accumulated snow melts away @export var snow_melt_speed: float = 0.05 #Speed at which accumulated snow melts away
@export var show_snow_accumulation_volume: bool = true #Enables vertex snow buildup; when false snow only changes surface color
@export var snow_max_accumulation: float = 0.25 #Maximum accumulated snow factor applied to coverage and thickness
@export var snow_color: Color = Color(0.9, 0.95, 1.0) #Albedo color of snow on surfaces @export var snow_color: Color = Color(0.9, 0.95, 1.0) #Albedo color of snow on surfaces
@export var snow_cap_height: float = 0.03 #Maximum snow thickness extruded on flat surfaces
@export var snow_cap_flatness_start: float = 0.85 #Surface normal Y where cap buildup starts
@export var snow_cap_flatness_end: float = 0.96 #Surface normal Y where cap buildup reaches full effect
@export var snow_cap_noise_scale: float = 0.22 #Noise scale used to vary the snow cap silhouette
@export var snow_cap_noise_strength: float = 0.12 #How much the cap noise changes the accumulated thickness
@export var snow_mode_color: Color = Color(0.556, 0.62, 0.683, 1.0) #Sky/light darkening color during snow @export var snow_mode_color: Color = Color(0.556, 0.62, 0.683, 1.0) #Sky/light darkening color during snow
#Wind settings #Wind settings
@@ -152,14 +167,26 @@ extends Resource
@export var wind_direction: Vector2 = Vector2(0.5, 0.4) #Global wind direction (XZ) @export var wind_direction: Vector2 = Vector2(0.5, 0.4) #Global wind direction (XZ)
@export var wind_speed: float = 0.2 #Global wind animation speed @export var wind_speed: float = 0.2 #Global wind animation speed
@export var wind_scale: float = 0.025 #Global wind noise scale @export var wind_scale: float = 0.025 #Global wind noise scale
@export var wind_strength: float = 0.6 #Global wind displacement strength @export var wind_strength: float = 0.3 #Global wind displacement strength
@export var cloud_scale: float = 0.5 #Cloud noise scale in sky shader @export var cloud_scale: float = 0.5 #Cloud noise scale in sky shader
@export var cloud_speed: float = 0.05 #Cloud movement speed in sky shader @export var cloud_speed: float = 0.05 #Cloud movement speed in sky shader
@export_range(0.01, 1.0) var cloud_scale_envshadows_rate: float = 0.01 #Cloud scale multiplier for environment shadows @export_range(0.01, 1.0) var cloud_scale_envshadows_rate: float = 0.01 #Cloud scale multiplier for environment shadows
@export_range(0.01, 1.0) var cloud_speed_envshadows_rate: float = 0.5 #Cloud speed multiplier for environment shadows @export_range(0.01, 1.0) var cloud_speed_envshadows_rate: float = 0.5 #Cloud speed multiplier for environment shadows
@export var wind_fade_in_out_time: float = 1.2 #fade out to time when wind is turned off or when is turned on
#Firefly settings (visible only at night) #Firefly settings (visible only at night)
@export_group("Fireflies") @export_group("Fireflies")
@export var fireflies_amount: int = 60 #Number of firefly particles @export var fireflies_amount: int = 60 #Number of firefly particles
@export var fireflies_spawn_ray: float = 20.0 #Horizontal spawn radius @export var fireflies_spawn_ray: float = 20.0 #Horizontal spawn radius
@export var fireflies_spawn_height: float = 3.0 #Vertical spawn range @export var fireflies_spawn_height: float = 3.0 #Vertical spawn range
#Water settings
@export_group("Water")
@export var water_color_morning: Color = Color(0.285, 0.534, 0.487, 1.0) #Tint for morning
@export var water_color_afternoon: Color = Color(0.889, 0.733, 0.205, 1.0) #Tint for afternoon (sunset)
@export var water_color_night: Color = Color(0.728, 0.54, 0.986, 1.0); #Tint for night
@export var water_darkening_rain: float = 0.7 #Percentage of darkening when is rainy
#Random events
@export_group("Random Events")
@export var random_event_duration: float = 30.0 #Duration time for random event

View File

@@ -19,7 +19,7 @@ func set_particles_node_color(node: GPUParticles3D, new_color: Color) -> void:
unique_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED unique_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mesh.material = unique_mat mesh.material = unique_mat
func turnon() -> void: func turn_on() -> void:
if rocket: if rocket:
rocket.emitting = true rocket.emitting = true

View File

@@ -0,0 +1,32 @@
extends Node
const SAVE_PATH: String = "user://savegame.json"
var save_data: SaveGameData = SaveGameData.new()
var is_loaded: bool = false
func _ready() -> void:
load_game()
func save_game() -> void:
var save_file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
var json_string = JSON.stringify(save_data.to_dictionary())
save_file.store_line(json_string)
func load_game() -> void:
if not FileAccess.file_exists(SAVE_PATH):
save_data = SaveGameData.new()
is_loaded = true
return
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
var json_string = file.get_as_text()
var json = JSON.new()
var parse_result = json.parse(json_string)
save_data = SaveGameData.new()
if parse_result == OK and json.data is Dictionary:
save_data = SaveGameData.from_dictionary(json.data)
is_loaded = true

View File

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

View File

@@ -0,0 +1,34 @@
class_name SaveGameData
extends Resource
var unlocked_collectibles_ids: Array[StringName] = []
var saved_photos: Array[String] = []
var audio_bus_volumes: Dictionary[String, float] = {}
func to_dictionary() -> Dictionary:
var serialized_collectible_ids: Array[String] = []
for collectible_id in unlocked_collectibles_ids:
serialized_collectible_ids.append(str(collectible_id))
return {
"unlocked_collectibles_ids": serialized_collectible_ids,
"saved_photos": saved_photos,
"audio_bus_volumes": audio_bus_volumes,
}
static func from_dictionary(data: Dictionary) -> SaveGameData:
var save_game_data := SaveGameData.new()
if data.has("unlocked_collectibles_ids"):
for collectible_id in data["unlocked_collectibles_ids"]:
save_game_data.unlocked_collectibles_ids.append(StringName(str(collectible_id)))
if data.has("saved_photos"):
for photo_path in data["saved_photos"]:
save_game_data.saved_photos.append(str(photo_path))
if data.has("audio_bus_volumes") and data["audio_bus_volumes"] is Dictionary:
for bus_name in data["audio_bus_volumes"]:
save_game_data.audio_bus_volumes[str(bus_name)] = float(data["audio_bus_volumes"][bus_name])
return save_game_data

View File

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

View File

@@ -0,0 +1,4 @@
class_name CollectibleLibrary
extends Resource
@export var collectibles: Array[CollectibleResource]

View File

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

View File

@@ -0,0 +1,31 @@
[gd_resource type="Resource" script_class="CollectibleLibrary" format=3 uid="uid://c6pkpvsvimafb"]
[ext_resource type="Script" uid="uid://bj34o0org55ei" path="res://core/photo_mode/data/collectible_resource.gd" id="1_3cgvf"]
[ext_resource type="Script" uid="uid://hhuufh87skq5" path="res://core/photo_mode/data/collectible_library.gd" id="2_ggu10"]
[ext_resource type="Texture2D" uid="uid://cx8d233y32kmu" path="res://icon.svg" id="2_pxmdy"]
[sub_resource type="Resource" id="Resource_3cgvf"]
script = ExtResource("1_3cgvf")
id = &"cane"
title = "Cane"
image = ExtResource("2_pxmdy")
metadata/_custom_type_script = "uid://bj34o0org55ei"
[sub_resource type="Resource" id="Resource_ggu10"]
script = ExtResource("1_3cgvf")
id = &"gatto"
title = "Gatto"
image = ExtResource("2_pxmdy")
metadata/_custom_type_script = "uid://bj34o0org55ei"
[sub_resource type="Resource" id="Resource_pxmdy"]
script = ExtResource("1_3cgvf")
id = &"farfalla"
title = "Farfalla"
image = ExtResource("2_pxmdy")
metadata/_custom_type_script = "uid://bj34o0org55ei"
[resource]
script = ExtResource("2_ggu10")
collectibles = Array[ExtResource("1_3cgvf")]([SubResource("Resource_3cgvf"), SubResource("Resource_ggu10"), SubResource("Resource_pxmdy")])
metadata/_custom_type_script = "uid://hhuufh87skq5"

View File

@@ -0,0 +1,6 @@
class_name CollectibleResource
extends Resource
@export var id: StringName
@export var title: String
@export var image: Texture2D

View File

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

View File

@@ -0,0 +1,60 @@
extends Node
signal on_collectible_unlocked(collectible_id: StringName)
signal on_photo_saved(filePath: String)
var collectibles_library: CollectibleLibrary = preload("res://core/photo_mode/data/collectible_library.tres")
var unlocked_collectibles_ids: Array[StringName] = []
var saved_photos: Array[String] = []
func _ready() -> void:
load_save_data()
func unlock_collectible(collectible_id: StringName) -> void:
if not collectible_id in unlocked_collectibles_ids:
var new_collectible = get_collectible_by_id(collectible_id)
if new_collectible:
unlocked_collectibles_ids.append(collectible_id)
sync_save_data()
on_collectible_unlocked.emit(collectible_id)
func get_collectible_by_id(id: StringName) -> CollectibleResource:
for collectible in collectibles_library.collectibles:
if collectible.id == id:
return collectible
return null
func get_all_collectibles() -> Array[CollectibleResource]:
return collectibles_library.collectibles
func get_unlocked_collectible_ids() -> Array[StringName]:
return unlocked_collectibles_ids
func save_photo_to_disk_async() -> void:
await RenderingServer.frame_post_draw
var image = get_viewport().get_texture().get_image()
if not DirAccess.dir_exists_absolute("user://photos"):
DirAccess.make_dir_absolute("user://photos")
var timestamp = str(Time.get_unix_time_from_system())
var file_path = "user://photos/photo_" + timestamp + ".png"
image.save_png(file_path)
saved_photos.append(file_path)
sync_save_data()
on_photo_saved.emit(file_path)
func sync_save_data() -> void:
GameState.save_data.unlocked_collectibles_ids = unlocked_collectibles_ids.duplicate()
GameState.save_data.saved_photos = saved_photos.duplicate()
func load_save_data() -> void:
unlocked_collectibles_ids = GameState.save_data.unlocked_collectibles_ids.duplicate()
saved_photos = GameState.save_data.saved_photos.duplicate()
if unlocked_collectibles_ids.is_empty() and saved_photos.is_empty():
unlocked_collectibles_ids.append(&"cane")
sync_save_data()

View File

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

View File

@@ -0,0 +1,6 @@
extends Area3D
@export var collectible_data: CollectibleResource
func _ready() -> void:
add_to_group("collectible")

View File

@@ -0,0 +1 @@
uid://62d14boivr3g

View File

@@ -0,0 +1,11 @@
[gd_scene format=3 uid="uid://bmkxt6btcx8qr"]
[ext_resource type="Script" uid="uid://62d14boivr3g" path="res://core/photo_mode/runtime/collectible.gd" id="1_xse8c"]
[sub_resource type="BoxShape3D" id="BoxShape3D_xse8c"]
[node name="Collectible" type="Area3D" unique_id=1229019813]
script = ExtResource("1_xse8c")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1479480920]
shape = SubResource("BoxShape3D_xse8c")

View File

@@ -0,0 +1,94 @@
extends Node3D
@export_group("Speed")
@export var pan_speed: float = 5.0
@export var rotation_speed: float = 0.003
@export_group("Bounds")
@export var movement_bounds: AABB
@export_group("Ref")
@export var rotation_target: Node3D
@onready var camera: Camera3D = $%Camera3D
var total_yaw: float = 0.0
var total_pitch: float = 0.0
var orbit_distance: float = 0.0
var current_pan: Vector2 = Vector2.ZERO
var is_active: bool = false
func _ready() -> void:
total_yaw = global_rotation.y
total_pitch = global_rotation.x
if rotation_target:
orbit_distance = global_position.distance_to(rotation_target.global_position)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_photo_mode"):
is_active = !is_active
get_tree().paused = is_active
if !is_active:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
if event is InputEventMouseMotion and is_active:
total_yaw -= event.relative.x * rotation_speed
if event.is_action_pressed("take_photo"):
take_photo_async()
func _process(delta: float) -> void:
if not rotation_target:
return
var pan_input = Input.get_vector("photo_pan_left", "photo_pan_right", "photo_pan_up", "photo_pan_down")
var rot_basis = Basis.from_euler(Vector3(total_pitch, total_yaw, 0))
if pan_input != Vector2.ZERO and is_active:
current_pan.x += pan_input.x * pan_speed * delta
current_pan.y += -pan_input.y * pan_speed * delta
if movement_bounds.size != Vector3.ZERO:
current_pan.x = clampf(current_pan.x, movement_bounds.position.x, movement_bounds.position.x + movement_bounds.size.x)
current_pan.y = clampf(current_pan.y, movement_bounds.position.y, movement_bounds.position.y + movement_bounds.size.y)
var right_dir = rot_basis.x.normalized()
var up_dir = rot_basis.y.normalized()
var final_pan_offset = (right_dir * current_pan.x) + (up_dir * current_pan.y)
var orbit_pos = rotation_target.global_position + (rot_basis * Vector3(0, 0, orbit_distance))
global_position = orbit_pos + final_pan_offset
global_basis = rot_basis
func take_photo_async() -> void:
if !is_active:
return
var targets = get_tree().get_nodes_in_group("collectible")
var space_state = get_world_3d().direct_space_state
await CollectionManager.save_photo_to_disk_async()
for target in targets:
if not target.collectible_data:
continue
if camera.is_position_in_frustum(target.global_position):
var query = PhysicsRayQueryParameters3D.create(camera.global_position, target.global_position)
query.collide_with_areas = true
query.collide_with_bodies = true
var result = space_state.intersect_ray(query)
if result and result.collider == target:
CollectionManager.unlock_collectible(target.collectible_data.id)
GameState.save_game()

View File

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

View File

@@ -0,0 +1,10 @@
[gd_scene format=3 uid="uid://vni5kjalum6d"]
[ext_resource type="Script" uid="uid://d7ln6iru6mq6" path="res://core/photo_mode/runtime/photo_mode_controller.gd" id="1_uhpya"]
[node name="PhotoModeController" type="Node3D" unique_id=695158870]
process_mode = 3
script = ExtResource("1_uhpya")
[node name="Camera3D" type="Camera3D" parent="." unique_id=1369039645]
unique_name_in_owner = true

View File

@@ -0,0 +1,34 @@
extends Control
@export var collectible_ui_scene: PackedScene
@onready var grid_container: GridContainer = $%CollectibleGrid
func _ready() -> void:
CollectionManager.on_collectible_unlocked.connect(_unlock_collectible)
setup()
func setup() -> void:
var unlocked_collectible_ids = CollectionManager.get_unlocked_collectible_ids()
var collectibles = CollectionManager.get_all_collectibles()
for child in grid_container.get_children():
child.queue_free()
for collectible in collectibles:
_add_collectible(collectible)
if unlocked_collectible_ids.has(collectible.id):
_unlock_collectible(collectible.id)
func on_collectible_unlocked(collectible_id: StringName) -> void:
_unlock_collectible(collectible_id)
func _add_collectible(collectible: CollectibleResource) -> void:
var collectible_ui: CollectibleUI = collectible_ui_scene.instantiate()
collectible_ui.setup(collectible)
grid_container.add_child(collectible_ui)
func _unlock_collectible(collectible_id: StringName) -> void:
for collectible_ui in grid_container.get_children():
if collectible_ui.collectible_resource.id == collectible_id:
collectible_ui.unlock()

View File

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

View File

@@ -0,0 +1,50 @@
[gd_scene format=3 uid="uid://bvw086glfpcba"]
[ext_resource type="Script" uid="uid://dq3qtcrdnikl7" path="res://core/photo_mode/ui/collectible_gallery.gd" id="1_67tug"]
[ext_resource type="PackedScene" uid="uid://dp7dvfauh5rpx" path="res://core/photo_mode/ui/collectible_ui.tscn" id="2_or234"]
[node name="CollectibleGallery" type="Control" unique_id=354419843]
layout_mode = 3
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_67tug")
collectible_ui_scene = ExtResource("2_or234")
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=640179265]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -350.0
offset_top = -350.0
offset_right = 350.0
offset_bottom = 350.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer" unique_id=125147979]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/MarginContainer" unique_id=348576028]
layout_mode = 2
draw_focus_border = true
[node name="CollectibleGrid" type="GridContainer" parent="PanelContainer/MarginContainer/ScrollContainer" unique_id=1187865988]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_vertical = 2
theme_override_constants/h_separation = 12
theme_override_constants/v_separation = 12
columns = 3

View File

@@ -0,0 +1,22 @@
extends Control
class_name CollectibleUI
@onready var label: Label = $%CollectibleName
@onready var texture_rect: TextureRect = $%CollectibleTexture
var collectible_resource: CollectibleResource
func setup(collectible: CollectibleResource) -> void:
collectible_resource = collectible
if !label:
label = $%CollectibleName
label.text = collectible_resource.title
if collectible.image:
if !texture_rect:
texture_rect = $%CollectibleTexture
texture_rect.texture = collectible_resource.image
func unlock() -> void:
if !texture_rect:
texture_rect = $%CollectibleTexture
texture_rect.set_modulate(Color(1,1,1,1))

View File

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

View File

@@ -0,0 +1,49 @@
[gd_scene format=3 uid="uid://dp7dvfauh5rpx"]
[ext_resource type="Script" uid="uid://dxh8e1n16qjpp" path="res://core/photo_mode/ui/collectible_ui.gd" id="1_tu5nx"]
[ext_resource type="Texture2D" uid="uid://cx8d233y32kmu" path="res://icon.svg" id="1_x3wje"]
[node name="CollectibleUI" type="Control" unique_id=201865221]
custom_minimum_size = Vector2(200, 200)
layout_mode = 3
anchors_preset = 0
offset_right = 200.0
offset_bottom = 200.0
script = ExtResource("1_tu5nx")
[node name="Panel" type="PanelContainer" parent="." unique_id=1372516531]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="Panel" unique_id=1141643525]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer" unique_id=1841409877]
layout_mode = 2
alignment = 1
[node name="CollectibleName" type="Label" parent="Panel/MarginContainer/VBoxContainer" unique_id=117774359]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 0
text = "Collectible"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer" unique_id=1345822244]
layout_mode = 2
[node name="CollectibleTexture" type="TextureRect" parent="Panel/MarginContainer/VBoxContainer" unique_id=506844785]
unique_name_in_owner = true
modulate = Color(0.16206557, 0.1620656, 0.16206557, 1)
layout_mode = 2
size_flags_vertical = 3
texture = ExtResource("1_x3wje")
stretch_mode = 3

View File

@@ -0,0 +1,11 @@
extends Control
class_name Photo
@onready var texture_rect: TextureRect = $%PhotoTexture
func setup(texture: Texture) -> void:
if texture:
if !texture_rect:
texture_rect = $%PhotoTexture
texture_rect.texture = texture

View File

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

View File

@@ -0,0 +1,38 @@
[gd_scene format=3 uid="uid://cvus62qkop3qi"]
[ext_resource type="Script" uid="uid://dsey5dvc11vpq" path="res://core/photo_mode/ui/photo.gd" id="1_u0arp"]
[node name="Photo" type="Control" unique_id=201865221]
custom_minimum_size = Vector2(200, 200)
layout_mode = 3
anchors_preset = 0
offset_right = 200.0
offset_bottom = 200.0
script = ExtResource("1_u0arp")
[node name="ColorRect" type="ColorRect" parent="." unique_id=1208008621]
layout_mode = 0
offset_right = 200.0
offset_bottom = 200.0
[node name="MarginContainer" type="MarginContainer" parent="ColorRect" unique_id=1141643525]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="Panel" type="PanelContainer" parent="ColorRect/MarginContainer" unique_id=1372516531]
layout_mode = 2
[node name="PhotoTexture" type="TextureRect" parent="ColorRect/MarginContainer/Panel" unique_id=506844785]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
expand_mode = 1
stretch_mode = 6

View File

@@ -0,0 +1,27 @@
extends Control
@export var photo_scene: PackedScene
@onready var grid_container: GridContainer = $%PhotoGrid
func _ready() -> void:
CollectionManager.on_photo_saved.connect(_create_photo_thumbnail)
load_gallery()
func load_gallery() -> void:
for child in grid_container.get_children():
child.queue_free()
var saved_photos = CollectionManager.saved_photos
for file_path in saved_photos:
if FileAccess.file_exists(file_path):
_create_photo_thumbnail(file_path)
func _create_photo_thumbnail(file_path: String) -> void:
var image = Image.load_from_file(file_path)
if image:
var photo: Photo = photo_scene.instantiate()
var texture = ImageTexture.create_from_image(image)
photo.setup(texture)
grid_container.add_child(photo)

View File

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

View File

@@ -0,0 +1,50 @@
[gd_scene format=3 uid="uid://b6r787sik5yil"]
[ext_resource type="Script" uid="uid://k4iqrlkbjppl" path="res://core/photo_mode/ui/photo_gallery.gd" id="1_bp5uf"]
[ext_resource type="PackedScene" uid="uid://cvus62qkop3qi" path="res://core/photo_mode/ui/photo.tscn" id="2_45wok"]
[node name="PhotoGallery" type="Control" unique_id=354419843]
layout_mode = 3
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_bp5uf")
photo_scene = ExtResource("2_45wok")
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=640179265]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -350.0
offset_top = -350.0
offset_right = 350.0
offset_bottom = 350.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer" unique_id=125147979]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/MarginContainer" unique_id=348576028]
layout_mode = 2
draw_focus_border = true
[node name="PhotoGrid" type="GridContainer" parent="PanelContainer/MarginContainer/ScrollContainer" unique_id=1187865988]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 6
size_flags_vertical = 2
theme_override_constants/h_separation = 12
theme_override_constants/v_separation = 12
columns = 3

31
core/radio/radio.gd Normal file
View File

@@ -0,0 +1,31 @@
extends AudioStreamPlayer
class_name Radio
@export var playlist: Array[AudioStream] = []
var current_track_index: int = 0
func _ready():
finished.connect(next_track)
func play_track(index: int = current_track_index):
if playlist.is_empty():
return
current_track_index = posmod(index, playlist.size())
stream = playlist[current_track_index]
play()
stream_paused = false
func next_track():
play_track(current_track_index + 1)
func prev_track():
play_track(current_track_index - 1)
func toggle_pause():
stream_paused = !stream_paused
func stop_radio():
stop()
stream_paused = false

1
core/radio/radio.gd.uid Normal file
View File

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

7
core/radio/radio.tscn Normal file
View File

@@ -0,0 +1,7 @@
[gd_scene format=3 uid="uid://cpeyt1dgrtglc"]
[ext_resource type="Script" uid="uid://bwrgh6xlvhqch" path="res://core/radio/radio.gd" id="1_2akjj"]
[node name="Radio" type="AudioStreamPlayer" unique_id=1234112225]
bus = &"Music"
script = ExtResource("1_2akjj")

View File

@@ -8,6 +8,12 @@ signal toggle_snow(value: bool)
@warning_ignore("unused_signal") @warning_ignore("unused_signal")
signal toggle_wind(value: bool) signal toggle_wind(value: bool)
@warning_ignore("unused_signal") @warning_ignore("unused_signal")
signal wind_change(value: float)
@warning_ignore("unused_signal")
signal trigger_random_weather(duration: float)
@warning_ignore("unused_signal")
signal weather_event_changed(description: String)
@warning_ignore("unused_signal")
signal toggle_fireflies(value: bool) signal toggle_fireflies(value: bool)
@warning_ignore("unused_signal") @warning_ignore("unused_signal")
signal toggle_storm(value: bool) signal toggle_storm(value: bool)
@@ -21,9 +27,19 @@ signal toggle_dust(value: bool)
signal toggle_blur(value: bool) signal toggle_blur(value: bool)
@warning_ignore("unused_signal") @warning_ignore("unused_signal")
signal toggle_shadows(value: bool) signal toggle_shadows(value: bool)
@warning_ignore("unused_signal")
signal toggle_fog(value: bool)
#railway signals
@warning_ignore("unused_signal")
signal update_rail_chunks()
#day cycle signals #day cycle signals
@warning_ignore("unused_signal") @warning_ignore("unused_signal")
signal toggle_pause_daytime(value: bool) signal toggle_pause_daytime(value: bool)
@warning_ignore("unused_signal") @warning_ignore("unused_signal")
signal day_time_changed(value: float, time_string: String) signal day_time_changed(value: float, time_string: String)
@warning_ignore("unused_signal")
signal time_option_item_changed(index: int)
@warning_ignore("unused_signal")
signal day_time_option_changed(index: int)

15
default_bus_layout.tres Normal file
View File

@@ -0,0 +1,15 @@
[gd_resource type="AudioBusLayout" format=3 uid="uid://cqvx62pwvvboj"]
[resource]
bus/1/name = &"Music"
bus/1/solo = false
bus/1/mute = false
bus/1/bypass_fx = false
bus/1/volume_db = 0.0
bus/1/send = &"Master"
bus/2/name = &"SFX"
bus/2/solo = false
bus/2/mute = false
bus/2/bypass_fx = false
bus/2/volume_db = 0.0
bus/2/send = &"Master"

92
docs/gyms/ai/gym_ai.tscn Normal file
View File

@@ -0,0 +1,92 @@
[gd_scene format=3 uid="uid://dqvrhiqgkd3w1"]
[ext_resource type="PackedScene" uid="uid://clx701xdwelgx" path="res://core/ai/agents/base/ai_base.tscn" id="1_a2xtd"]
[sub_resource type="NavigationMesh" id="NavigationMesh_optuv"]
vertices = PackedVector3Array(10.984741, 0.25012434, -1.0140381, 11.234741, 0.25012434, 0.23596191, 19.484741, 0.25012434, 0.23596191, 19.484741, 0.25012434, -19.514038, 9.234741, 0.25012434, -1.0140381, -19.515259, 0.25012434, -19.514038, -19.515259, 0.25012434, -0.014038086, 8.984741, 0.25012434, -0.014038086, 10.984741, 0.25012434, 1.2359619, 19.484741, 0.25012434, 19.235962, 9.234741, 0.25012434, 1.2359619, -19.515259, 0.25012434, 19.235962)
polygons = [PackedInt32Array(1, 0, 2), PackedInt32Array(2, 0, 3), PackedInt32Array(6, 5, 4), PackedInt32Array(4, 5, 3), PackedInt32Array(3, 0, 4), PackedInt32Array(4, 7, 6), PackedInt32Array(1, 2, 8), PackedInt32Array(8, 2, 9), PackedInt32Array(6, 10, 11), PackedInt32Array(11, 10, 9), PackedInt32Array(10, 8, 9), PackedInt32Array(6, 7, 10)]
[sub_resource type="PlaneMesh" id="PlaneMesh_optuv"]
[sub_resource type="BoxShape3D" id="BoxShape3D_xtjak"]
size = Vector3(1.9942131, 0.09710693, 1.984024)
[sub_resource type="BoxMesh" id="BoxMesh_lmjyn"]
[sub_resource type="BoxShape3D" id="BoxShape3D_lmjyn"]
[sub_resource type="Environment" id="Environment_lmjyn"]
[node name="GymAI" type="Node3D" unique_id=868339787]
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=774907858]
navigation_mesh = SubResource("NavigationMesh_optuv")
[node name="Floor" type="MeshInstance3D" parent="NavigationRegion3D" unique_id=1976415311]
transform = Transform3D(20, 0, 0, 0, 20, 0, 0, 0, 20, 0, -0.83473957, 0)
mesh = SubResource("PlaneMesh_optuv")
[node name="StaticBody3D" type="StaticBody3D" parent="NavigationRegion3D/Floor" unique_id=2007477719]
[node name="CollisionShape3D" type="CollisionShape3D" parent="NavigationRegion3D/Floor/StaticBody3D" unique_id=1446628130]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0036563873, -0.009703338, -0.00868988)
shape = SubResource("BoxShape3D_xtjak")
[node name="Obstacle" type="MeshInstance3D" parent="NavigationRegion3D" unique_id=710701082]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.148018, 0.06508446, 0)
mesh = SubResource("BoxMesh_lmjyn")
[node name="StaticBody3D" type="StaticBody3D" parent="NavigationRegion3D/Obstacle" unique_id=67152580]
[node name="CollisionShape3D" type="CollisionShape3D" parent="NavigationRegion3D/Obstacle/StaticBody3D" unique_id=1663742898]
shape = SubResource("BoxShape3D_lmjyn")
[node name="AIBase" parent="." unique_id=1228675528 instance=ExtResource("1_a2xtd")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.48246834, 0.21202153, 0.11262059)
[node name="Camera3D" type="Camera3D" parent="." unique_id=1148396057]
transform = Transform3D(-2.0613477e-08, 0.8818225, -0.47158146, 3.854569e-08, 0.47158146, 0.8818225, 1, -3.5527137e-15, -4.371139e-08, -18.21907, 28.71355, 0)
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1772424900]
environment = SubResource("Environment_lmjyn")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=269230449]
transform = Transform3D(1, 0, 0, 0, 0.17952333, 0.98375374, 0, -0.98375374, 0.17952333, 0, 18.920979, 0)
[node name="Control" type="Control" parent="." unique_id=1072240389]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="PanelContainer" type="PanelContainer" parent="Control" unique_id=1647272132]
layout_mode = 0
offset_left = 50.0
offset_top = 50.0
offset_right = 400.0
offset_bottom = 827.0
[node name="Label" type="Label" parent="Control/PanelContainer" unique_id=272421245]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
size_flags_vertical = 1
text = "System Logic:
- The NPC uses a finite state machine to switch between behaviors.
- State logic runs during physics updates, while movement is handled by the agent through NavigationAgent3D.
AI States:
- IdleState: The agent waits in place for the duration defined by wait_time.
- PatrolState: The agent selects a valid random destination and moves toward it.
Core Components:
- NavigationAgent3D: Calculates the path and reports when the destination has been reached.
- PatrolRadiusShape: Defines the random movement area used when choosing the next patrol point.
- StateMachine: Initializes the available states and handles transitions between them.
Parameters:
- Speed: Movement speed of the agent.
- Wait Time: Delay in seconds on the IdleState node.
- Area Radius: Radius of the SphereShape3D used by PatrolRadiusShape."
autowrap_mode = 2

View File

@@ -0,0 +1,7 @@
extends Node3D
@onready var gallery: Control = $%Gallery
func _on_button_gallery_pressed() -> void:
gallery.visible = !gallery.visible

View File

@@ -0,0 +1 @@
uid://7oglx4br38d5

View File

@@ -0,0 +1,138 @@
[gd_scene format=3 uid="uid://bwc8slnbu7dmn"]
[ext_resource type="Script" uid="uid://7oglx4br38d5" path="res://docs/gyms/photo_mode/gym_photo_mode.gd" id="1_cl1h5"]
[ext_resource type="PackedScene" uid="uid://vni5kjalum6d" path="res://core/photo_mode/runtime/photo_mode_controller.tscn" id="2_q5kgc"]
[ext_resource type="PackedScene" uid="uid://bmkxt6btcx8qr" path="res://core/photo_mode/runtime/collectible.tscn" id="3_734hf"]
[ext_resource type="Texture2D" uid="uid://c3grftlmap4q5" path="res://tgcc/chunk/prop/tree/leaf1_alpha.png" id="4_00joj"]
[ext_resource type="Script" uid="uid://bj34o0org55ei" path="res://core/photo_mode/data/collectible_resource.gd" id="5_crfij"]
[ext_resource type="PackedScene" uid="uid://bvw086glfpcba" path="res://core/photo_mode/ui/collectible_gallery.tscn" id="6_6aab5"]
[ext_resource type="PackedScene" uid="uid://b6r787sik5yil" path="res://core/photo_mode/ui/photo_gallery.tscn" id="7_tb1ii"]
[sub_resource type="Environment" id="Environment_48mla"]
[sub_resource type="PlaneMesh" id="PlaneMesh_hh1ka"]
[sub_resource type="BoxShape3D" id="BoxShape3D_ynvi2"]
size = Vector3(1.9942131, 0.09710693, 1.984024)
[sub_resource type="Resource" id="Resource_ynvi2"]
script = ExtResource("5_crfij")
id = &"gatto"
title = "Gatto"
image = ExtResource("4_00joj")
metadata/_custom_type_script = "uid://bj34o0org55ei"
[sub_resource type="BoxMesh" id="BoxMesh_hh1ka"]
[node name="GymPhotoMode" type="Node3D" unique_id=1314600330]
script = ExtResource("1_cl1h5")
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=213183287]
environment = SubResource("Environment_48mla")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1274975837]
transform = Transform3D(1, 0, 0, 0, 0.17952333, 0.98375374, 0, -0.98375374, 0.17952333, 0, 18.920979, 0)
[node name="Floor" type="MeshInstance3D" parent="." unique_id=958865480]
transform = Transform3D(20, 0, 0, 0, 20, 0, 0, 0, 20, 0, -0.83473957, 0)
mesh = SubResource("PlaneMesh_hh1ka")
[node name="StaticBody3D" type="StaticBody3D" parent="Floor" unique_id=2096983240]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor/StaticBody3D" unique_id=188185045]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0036563873, -0.009703338, -0.00868988)
shape = SubResource("BoxShape3D_ynvi2")
[node name="PhotoModeController" parent="." unique_id=695158870 node_paths=PackedStringArray("rotation_target") instance=ExtResource("2_q5kgc")]
transform = Transform3D(1, 0, 0, 0, 0.49999994, 0.8660254, 0, -0.8660254, 0.49999994, 0, 30, 20)
movement_bounds = AABB(-5, -5, -5, 10, 10, 10)
rotation_target = NodePath("../Node3D")
[node name="Node3D" type="Node3D" parent="." unique_id=570786250]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10, 0)
[node name="Node3D2" type="Node3D" parent="." unique_id=15037296]
[node name="Collectible" parent="Node3D2" unique_id=1229019813 instance=ExtResource("3_734hf")]
collectible_data = SubResource("Resource_ynvi2")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Node3D2" unique_id=553040412]
mesh = SubResource("BoxMesh_hh1ka")
[node name="Control" type="Control" parent="." unique_id=1802496396]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="ButtonGallery" type="Button" parent="Control" unique_id=539195532]
layout_mode = 0
offset_left = 50.0
offset_top = 50.0
offset_right = 163.0
offset_bottom = 81.0
text = "Menu"
[node name="Gallery" type="HSplitContainer" parent="Control" unique_id=1388833682]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 420.0
grow_horizontal = 2
grow_vertical = 2
[node name="CollectibleGallery" parent="Control/Gallery" unique_id=354419843 instance=ExtResource("6_6aab5")]
layout_mode = 2
size_flags_horizontal = 3
[node name="PhotoGallery" parent="Control/Gallery" unique_id=263414560 instance=ExtResource("7_tb1ii")]
layout_mode = 2
size_flags_horizontal = 3
[node name="PanelContainer" type="PanelContainer" parent="Control" unique_id=2015953740]
layout_mode = 0
offset_left = 50.0
offset_top = 150.0
offset_right = 400.0
offset_bottom = 927.0
[node name="ScrollContainer" type="ScrollContainer" parent="Control/PanelContainer" unique_id=1474258594]
layout_mode = 2
[node name="Label" type="Label" parent="Control/PanelContainer/ScrollContainer" unique_id=1982569320]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
size_flags_horizontal = 3
text = "System Logic:
- The system allows players to toggle a free camera mode to photograph and collect museum items.
- Game Pause: Activating the mode pauses the SceneTree, freezing the world while allowing the camera to remain functional.
- Taking a photo saves the captured image to disk and checks which collectibles are visible and unobstructed before unlocking them.
- CollectionManager acts as a global singleton that tracks unlocked collectibles, saved photos, and the data used by the UI.
Photo Mode Actions:
- Toggle Mode(P): Switches between normal gameplay and the photo camera, capturing or releasing the mouse, and toggles the global pause state.
- Rotate(Mouse): The camera orbits around the configured target while photo mode is active.
- Pan(Input Actions): The camera can be moved horizontally and vertically within the configured bounds.
- Capture(F): Saves the current frame and performs frustum and raycast checks against collectibles in view.
Core Components:
- PhotoModeController: Manages camera inputs, movement logic, and the photo-taking process.
- Collectible: An Area3D node placed on objects to make them identifiable by the camera.
- CollectionManager: Handles the logic for unlocking items and provides data to the UI.
- CollectibleGallery: Displays all collectibles and visually highlights the unlocked ones.
- PhotoGallery: Displays the photos saved by the player during the session.
Parameters:
- Pan & Rotation Speed: Defines how fast the camera moves and rotates.
- Movement Bounds: An AABB that restricts the camera's panning area.
- Collectible Data: Resource files containing the ID, title, and image for each item.
- Library: A central list of all CollectibleResources registered in the game."
autowrap_mode = 2
[connection signal="pressed" from="Control/ButtonGallery" to="." method="_on_button_gallery_pressed"]

View File

@@ -0,0 +1,35 @@
extends Node3D
@onready var radio: Radio = $%Radio
@onready var button_pause_resume: Button = $%Button_Pause_Resume
@onready var settings_menu: SettingsMenu = $%SettingsMenu
func _on_button_play_pressed() -> void:
if !radio.playing:
radio.play_track()
func _on_button_pause_pressed() -> void:
radio.toggle_pause()
if !button_pause_resume:
button_pause_resume = $%Button_Pause_Resume
if radio.stream_paused:
button_pause_resume.text = "Resume"
else:
button_pause_resume.text = "Pause"
func _on_button_stop_pressed() -> void:
radio.stop_radio()
func _on_button_next_pressed() -> void:
radio.next_track()
func _on_button_previous_pressed() -> void:
radio.prev_track()
func _on_button_settings_menu_pressed() -> void:
settings_menu.visible = !settings_menu.visible

View File

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

View File

@@ -0,0 +1,104 @@
[gd_scene format=3 uid="uid://bdkdhn1oallke"]
[ext_resource type="PackedScene" uid="uid://cpeyt1dgrtglc" path="res://core/radio/radio.tscn" id="1_8yomj"]
[ext_resource type="Script" uid="uid://ew4b78u5tkiu" path="res://docs/gyms/radio/gym_radio.gd" id="1_h5r8c"]
[ext_resource type="AudioStream" uid="uid://70cc8she43re" path="res://docs/gyms/radio/lofi_01.ogg" id="3_ltpvc"]
[ext_resource type="PackedScene" uid="uid://caqf471x0kttc" path="res://core/audio/settings/settings_menu.tscn" id="4_8pw2j"]
[node name="GymRadio" type="Node3D" unique_id=204859062]
script = ExtResource("1_h5r8c")
[node name="Radio" parent="." unique_id=1234112225 instance=ExtResource("1_8yomj")]
unique_name_in_owner = true
playlist = Array[AudioStream]([ExtResource("3_ltpvc")])
[node name="Control" type="Control" parent="." unique_id=1232082176]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="SettingsMenu" parent="Control" unique_id=1639777294 instance=ExtResource("4_8pw2j")]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
[node name="VBoxContainer2" type="VBoxContainer" parent="Control" unique_id=2092627240]
layout_mode = 1
anchors_preset = 9
anchor_bottom = 1.0
offset_right = 229.0
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Control/VBoxContainer2" unique_id=2097365775]
layout_mode = 2
[node name="Button_Play" type="Button" parent="Control/VBoxContainer2/VBoxContainer" unique_id=1422696340]
layout_mode = 2
text = "Play"
[node name="Button_Pause_Resume" type="Button" parent="Control/VBoxContainer2/VBoxContainer" unique_id=2016690081]
unique_name_in_owner = true
layout_mode = 2
text = "Pause"
[node name="Button_Stop" type="Button" parent="Control/VBoxContainer2/VBoxContainer" unique_id=61861870]
layout_mode = 2
text = "Stop"
[node name="Button_Next" type="Button" parent="Control/VBoxContainer2/VBoxContainer" unique_id=957606385]
layout_mode = 2
text = "Next"
[node name="Button_Previous" type="Button" parent="Control/VBoxContainer2/VBoxContainer" unique_id=929072518]
layout_mode = 2
text = "Previous"
[node name="Button_SettingsMenu" type="Button" parent="Control/VBoxContainer2/VBoxContainer" unique_id=431867154]
layout_mode = 2
text = "SettingsMenu"
[node name="PanelContainer" type="PanelContainer" parent="Control/VBoxContainer2" unique_id=1558442050]
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="Control/VBoxContainer2/PanelContainer" unique_id=7789304]
layout_mode = 2
[node name="Label" type="Label" parent="Control/VBoxContainer2/PanelContainer/ScrollContainer" unique_id=2043293564]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
text = "System Logic:
- The system is an extension of AudioStreamPlayer designed to manage sequential audio playback.
- It automatically triggers the next track in the sequence when the current one finishes via the finished signal.
Core Functionality:
- Playlist Management: Stores a list of AudioStream resources and tracks the current song index.
- Track Navigation: Allows cycling forward and backward through the playlist with index wrapping (loops back to start/end).
- Playback Control: Supports jumping to specific tracks, toggling the pause state, and stopping the stream.
- Audio Settings: If the Music bus exists, the Settings menu can be used to test and adjust playback volume.
Key Methods:
- play_track: Loads a specific stream from the playlist and begins playback.
- next_track / prev_track: Increments or decrements the index to navigate the playlist.
- toggle_pause: Resumes or pauses the current stream without resetting its position.
Adjustable Parameters:
- Playlist: An Array in the inspector where you can assign one or more AudioStream resources."
autowrap_mode = 2
[connection signal="pressed" from="Control/VBoxContainer2/VBoxContainer/Button_Play" to="." method="_on_button_play_pressed"]
[connection signal="pressed" from="Control/VBoxContainer2/VBoxContainer/Button_Pause_Resume" to="." method="_on_button_pause_pressed"]
[connection signal="pressed" from="Control/VBoxContainer2/VBoxContainer/Button_Stop" to="." method="_on_button_stop_pressed"]
[connection signal="pressed" from="Control/VBoxContainer2/VBoxContainer/Button_Next" to="." method="_on_button_next_pressed"]
[connection signal="pressed" from="Control/VBoxContainer2/VBoxContainer/Button_Previous" to="." method="_on_button_previous_pressed"]
[connection signal="pressed" from="Control/VBoxContainer2/VBoxContainer/Button_SettingsMenu" to="." method="_on_button_settings_menu_pressed"]

BIN
docs/gyms/radio/lofi_01.ogg Normal file

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://70cc8she43re"
path="res://.godot/imported/lofi_01.ogg-0aacd597266806080412021811923091.oggvorbisstr"
[deps]
source_file="res://docs/gyms/radio/lofi_01.ogg"
dest_files=["res://.godot/imported/lofi_01.ogg-0aacd597266806080412021811923091.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

View File

@@ -7,15 +7,14 @@
[ext_resource type="PackedScene" uid="uid://ujv2f1l4d2ps" path="res://core/biome_generator/biome_generator.tscn" id="4_yrmqs"] [ext_resource type="PackedScene" uid="uid://ujv2f1l4d2ps" path="res://core/biome_generator/biome_generator.tscn" id="4_yrmqs"]
[ext_resource type="Script" uid="uid://wv6kcqkibium" path="res://core/biome_generator/biome.gd" id="5_2wjys"] [ext_resource type="Script" uid="uid://wv6kcqkibium" path="res://core/biome_generator/biome.gd" id="5_2wjys"]
[ext_resource type="PackedScene" uid="uid://cv5xmnow451kl" path="res://core/camera.tscn" id="5_8tojn"] [ext_resource type="PackedScene" uid="uid://cv5xmnow451kl" path="res://core/camera.tscn" id="5_8tojn"]
[ext_resource type="PackedScene" uid="uid://botm0dqtr5whb" path="res://docs/museums/daynight/scenes/chunk_c_e_1/chunk_c_e_1.tscn" id="6_21usy"] [ext_resource type="PackedScene" uid="uid://crlk31ecl480n" path="res://tgcc/chunk/countryside/scene/chunk_country_corner_03.tscn" id="6_21usy"]
[ext_resource type="Script" uid="uid://dboerd4a6dwj7" path="res://docs/museums/biome_generator/rails.gd" id="6_pypsn"] [ext_resource type="Script" uid="uid://dboerd4a6dwj7" path="res://core/biome_generator/rails.gd" id="6_pypsn"]
[ext_resource type="PackedScene" uid="uid://dvk3bytqn3m5s" path="res://tgcc/train/main_train.tscn" id="6_wjpfq"] [ext_resource type="PackedScene" uid="uid://dvk3bytqn3m5s" path="res://tgcc/train/main_train.tscn" id="6_wjpfq"]
[ext_resource type="PackedScene" uid="uid://givg3dyrxsk5" path="res://docs/museums/daynight/scenes/chunk_c_f_1/chunk_c_f_1.tscn" id="7_qe84w"] [ext_resource type="PackedScene" uid="uid://brpp7fe5noq8v" path="res://tgcc/chunk/countryside/scene/chunk_country_cross_3_01.tscn" id="7_qe84w"]
[ext_resource type="PackedScene" uid="uid://0kgjaqijaqku" path="res://docs/museums/biome_generator/rails.tscn" id="8_mb5yv"] [ext_resource type="PackedScene" uid="uid://0kgjaqijaqku" path="res://docs/museums/biome_generator/rails.tscn" id="8_mb5yv"]
[ext_resource type="PackedScene" uid="uid://bi12g5nj421jv" path="res://docs/museums/daynight/scenes/chunk_f_e_1/chunk_f_e_1.tscn" id="8_qe84w"]
[ext_resource type="PackedScene" uid="uid://dn1btv0e0ses7" path="res://core/fireworks.tscn" id="8_xtogr"] [ext_resource type="PackedScene" uid="uid://dn1btv0e0ses7" path="res://core/fireworks.tscn" id="8_xtogr"]
[ext_resource type="PackedScene" uid="uid://cpebna545ihfh" path="res://docs/museums/daynight/scenes/chunk_f_l_2/chunk_f_l_2.tscn" id="9_3qrd0"] [ext_resource type="PackedScene" uid="uid://cu2chsjh8wuvp" path="res://tgcc/chunk/countryside/scene/chunk_country_straight_02.tscn" id="9_sw7bt"]
[ext_resource type="PackedScene" uid="uid://xebpudhewwwj" path="res://docs/museums/daynight/scenes/chunk_c_l_2/chunk_c_l_2.tscn" id="9_sw7bt"] [ext_resource type="PackedScene" uid="uid://c73yk6858dmwn" path="res://tgcc/chunk/countryside/scene/chunk_country_cross_4_01.tscn" id="10_c7mkj"]
[ext_resource type="PackedScene" uid="uid://cmuvmmp7xam4o" path="res://core/daynight/environment_management.tscn" id="16_c7mkj"] [ext_resource type="PackedScene" uid="uid://cmuvmmp7xam4o" path="res://core/daynight/environment_management.tscn" id="16_c7mkj"]
[ext_resource type="AudioStream" uid="uid://dvabvoqyya0g5" path="res://core/daynight/sounds/thunder_3.mp3" id="17_sw7bt"] [ext_resource type="AudioStream" uid="uid://dvabvoqyya0g5" path="res://core/daynight/sounds/thunder_3.mp3" id="17_sw7bt"]
[ext_resource type="AudioStream" uid="uid://creenugno7hm" path="res://core/daynight/sounds/thunder_4.mp3" id="18_2l6dd"] [ext_resource type="AudioStream" uid="uid://creenugno7hm" path="res://core/daynight/sounds/thunder_4.mp3" id="18_2l6dd"]
@@ -102,13 +101,7 @@ compositor_effects = Array[CompositorEffect]([SubResource("CompositorEffect_q52t
[sub_resource type="Resource" id="Resource_3qrd0"] [sub_resource type="Resource" id="Resource_3qrd0"]
script = ExtResource("5_2wjys") script = ExtResource("5_2wjys")
name = "countryside" name = "countryside"
available_chunks = Array[PackedScene]([ExtResource("6_21usy"), ExtResource("7_qe84w"), ExtResource("9_sw7bt")]) available_chunks = Array[PackedScene]([ExtResource("6_21usy"), ExtResource("7_qe84w"), ExtResource("9_sw7bt"), ExtResource("10_c7mkj")])
metadata/_custom_type_script = "uid://wv6kcqkibium"
[sub_resource type="Resource" id="Resource_2qks6"]
script = ExtResource("5_2wjys")
name = "Forest"
available_chunks = Array[PackedScene]([ExtResource("8_qe84w"), ExtResource("9_3qrd0")])
metadata/_custom_type_script = "uid://wv6kcqkibium" metadata/_custom_type_script = "uid://wv6kcqkibium"
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_pypsn"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_pypsn"]
@@ -140,9 +133,10 @@ compositor = SubResource("Compositor_mb5yv")
[node name="biome_generator" parent="." unique_id=1861369341 node_paths=PackedStringArray("rail_path") instance=ExtResource("4_yrmqs")] [node name="biome_generator" parent="." unique_id=1861369341 node_paths=PackedStringArray("rail_path") instance=ExtResource("4_yrmqs")]
rail_path = NodePath("../rails") rail_path = NodePath("../rails")
biome_list = Array[ExtResource("5_2wjys")]([SubResource("Resource_3qrd0"), SubResource("Resource_2qks6")]) biome_list = Array[ExtResource("5_2wjys")]([SubResource("Resource_3qrd0")])
[node name="Terrain" type="MeshInstance3D" parent="." unique_id=219178561] [node name="Terrain" type="MeshInstance3D" parent="." unique_id=219178561]
visible = false
material_override = SubResource("StandardMaterial3D_pypsn") material_override = SubResource("StandardMaterial3D_pypsn")
mesh = SubResource("BoxMesh_xtogr") mesh = SubResource("BoxMesh_xtogr")
@@ -185,10 +179,10 @@ script = ExtResource("23_27ile")
[node name="Snow" type="CheckButton" parent="Control" unique_id=454394427] [node name="Snow" type="CheckButton" parent="Control" unique_id=454394427]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 13.0 offset_top = 173.0
offset_right = 109.0 offset_right = 98.0
offset_bottom = 44.0 offset_bottom = 204.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
@@ -199,10 +193,10 @@ text = "Snow"
[node name="Rain" type="CheckButton" parent="Control" unique_id=837805524] [node name="Rain" type="CheckButton" parent="Control" unique_id=837805524]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 41.0 offset_top = 201.0
offset_right = 109.0 offset_right = 96.0
offset_bottom = 72.0 offset_bottom = 232.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
@@ -213,10 +207,10 @@ text = "Rain"
[node name="Wind" type="CheckButton" parent="Control" unique_id=1013004982] [node name="Wind" type="CheckButton" parent="Control" unique_id=1013004982]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 91.0 offset_top = 231.0
offset_right = 109.0 offset_right = 96.0
offset_bottom = 122.0 offset_bottom = 262.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
@@ -225,12 +219,21 @@ theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
text = "Wind" text = "Wind"
[node name="WindSlider" type="HSlider" parent="Control" unique_id=28050098]
layout_mode = 0
offset_left = 103.0
offset_top = 238.0
offset_right = 187.0
offset_bottom = 254.0
max_value = 1.0
step = 0.01
[node name="Fireflies" type="CheckButton" parent="Control" unique_id=1046228444] [node name="Fireflies" type="CheckButton" parent="Control" unique_id=1046228444]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 115.0 offset_top = 259.0
offset_right = 130.0 offset_right = 117.0
offset_bottom = 146.0 offset_bottom = 290.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
@@ -241,10 +244,10 @@ text = "Fireflies"
[node name="Storm" type="CheckButton" parent="Control" unique_id=1792625744] [node name="Storm" type="CheckButton" parent="Control" unique_id=1792625744]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 98.0
offset_top = 66.0 offset_top = 201.0
offset_right = 117.0 offset_right = 194.0
offset_bottom = 97.0 offset_bottom = 232.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
@@ -255,73 +258,88 @@ text = "Storm"
[node name="DayTimeLabel" type="Label" parent="Control" unique_id=1124935856] [node name="DayTimeLabel" type="Label" parent="Control" unique_id=1124935856]
layout_mode = 0 layout_mode = 0
offset_left = 26.0 offset_left = 13.0
offset_top = 150.0 offset_top = 15.0
offset_right = 360.0 offset_right = 347.0
offset_bottom = 173.0 offset_bottom = 38.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
text = "Time 00:00" text = "Time 00:00"
[node name="DayTimeSlider" type="HSlider" parent="Control" unique_id=1865778345] [node name="DayTimeSlider" type="HSlider" parent="Control" unique_id=1865778345]
layout_mode = 0 layout_mode = 0
offset_left = 26.0 offset_left = 13.0
offset_top = 174.0 offset_top = 40.0
offset_right = 205.0 offset_right = 138.0
offset_bottom = 190.0 offset_bottom = 56.0
max_value = 1.0 max_value = 1.0
step = 0.01 step = 0.01
[node name="DayTimeOptions" type="OptionButton" parent="Control" unique_id=928646468]
layout_mode = 0
offset_left = 13.0
offset_top = 59.0
offset_right = 124.0
offset_bottom = 90.0
selected = 1
item_count = 4
popup/item_0/text = "Sunrise"
popup/item_0/id = 1
popup/item_1/text = "Day"
popup/item_1/id = 2
popup/item_2/text = "Sunset"
popup/item_2/id = 3
popup/item_3/text = "Night"
popup/item_3/id = 4
[node name="Dust" type="CheckButton" parent="Control" unique_id=760281365] [node name="Dust" type="CheckButton" parent="Control" unique_id=760281365]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 250.0 offset_top = 91.0
offset_right = 150.0 offset_right = 137.0
offset_bottom = 281.0 offset_bottom = 122.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(1, 1, 1, 1) theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
button_pressed = true
text = "Dust" text = "Dust"
[node name="Blur" type="CheckButton" parent="Control" unique_id=1083708100] [node name="Blur" type="CheckButton" parent="Control" unique_id=1083708100]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 278.0 offset_top = 118.0
offset_right = 150.0 offset_right = 137.0
offset_bottom = 309.0 offset_bottom = 149.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(1, 1, 1, 1) theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
button_pressed = true
text = "Blur" text = "Blur"
[node name="Pause" type="CheckButton" parent="Control" unique_id=1587043871] [node name="Pause" type="CheckButton" parent="Control" unique_id=1587043871]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 143.0
offset_top = 189.0 offset_top = 31.0
offset_right = 158.0 offset_right = 238.0
offset_bottom = 220.0 offset_bottom = 62.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_color = Color(1, 1, 1, 1) theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
text = "Pause Time" text = "Pause"
[node name="Shadows" type="CheckButton" parent="Control" unique_id=283354318] [node name="Shadows" type="CheckButton" parent="Control" unique_id=283354318]
layout_mode = 0 layout_mode = 0
offset_left = 21.0 offset_left = 8.0
offset_top = 302.0 offset_top = 145.0
offset_right = 150.0 offset_right = 137.0
offset_bottom = 333.0 offset_bottom = 176.0
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_focus_color = Color(1, 1, 1, 1) theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
@@ -331,26 +349,40 @@ theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
button_pressed = true button_pressed = true
text = "Shadows" text = "Shadows"
[node name="Guide" type="Label" parent="Control" unique_id=2121474959] [node name="RandomWeather" type="Button" parent="Control" unique_id=1047710802]
layout_mode = 0 layout_mode = 0
offset_left = 26.0 offset_left = 12.0
offset_top = 347.0 offset_top = 289.0
offset_right = 236.0 offset_right = 157.0
offset_bottom = 474.0 offset_bottom = 320.0
text = "R-> Reset isometric camera theme_override_colors/font_color = Color(1, 1, 1, 1)
C-> Toggle cinematic mode theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
Z-> soft zoom theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
Manual controls (1-5) theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
T -> go to next stop theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
F -> fireworks" theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
text = "Random Weather (10\")"
[node name="WeatherEventLabel" type="Label" parent="Control" unique_id=641424797]
layout_mode = 0
offset_left = 1656.0
offset_top = 11.0
offset_right = 1908.0
offset_bottom = 34.0
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
text = "Weather: Clear"
[connection signal="toggled" from="Control/Snow" to="Control" method="_on_snow_toggled"] [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/Rain" to="Control" method="_on_rain_toggled"]
[connection signal="toggled" from="Control/Wind" to="Control" method="_on_wind_toggled"] [connection signal="toggled" from="Control/Wind" to="Control" method="_on_wind_toggled"]
[connection signal="value_changed" from="Control/WindSlider" to="Control" method="_on_wind_slider_value_changed"]
[connection signal="toggled" from="Control/Fireflies" to="Control" method="_on_fireflies_toggled"] [connection signal="toggled" from="Control/Fireflies" to="Control" method="_on_fireflies_toggled"]
[connection signal="toggled" from="Control/Storm" to="Control" method="_on_storm_toggled"] [connection signal="toggled" from="Control/Storm" to="Control" method="_on_storm_toggled"]
[connection signal="value_changed" from="Control/DayTimeSlider" to="Control" method="_on_day_time_slider_value_changed"] [connection signal="value_changed" from="Control/DayTimeSlider" to="Control" method="_on_day_time_slider_value_changed"]
[connection signal="item_selected" from="Control/DayTimeOptions" to="Control" method="_on_option_button_item_selected"]
[connection signal="toggled" from="Control/Dust" to="Control" method="_on_dust_toggled"] [connection signal="toggled" from="Control/Dust" to="Control" method="_on_dust_toggled"]
[connection signal="toggled" from="Control/Blur" to="Control" method="_on_blur_toggled"] [connection signal="toggled" from="Control/Blur" to="Control" method="_on_blur_toggled"]
[connection signal="toggled" from="Control/Pause" to="Control" method="_on_pause_toggled"] [connection signal="toggled" from="Control/Pause" to="Control" method="_on_pause_toggled"]
[connection signal="toggled" from="Control/Shadows" to="Control" method="_on_shadows_toggled"] [connection signal="toggled" from="Control/Shadows" to="Control" method="_on_shadows_toggled"]
[connection signal="pressed" from="Control/RandomWeather" to="Control" method="_on_random_weather_pressed"]

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,22 @@
extends Control extends Control
@onready var day_night: EnvironmentManagerRoot = get_node_or_null("../DayNight") as EnvironmentManagerRoot @onready var day_night: EnvironmentManagerRoot = $"../DayNight"
@onready var weather_event_label: Label = $WeatherEventLabel
@onready var day_time_label: Label = $DayTimeLabel @onready var day_time_label: Label = $DayTimeLabel
@onready var day_time_slider: HSlider = $DayTimeSlider @onready var day_time_slider: HSlider = $DayTimeSlider
@onready var wind_slider: HSlider = $WindSlider
@onready var day_time_option: OptionButton = $DayTimeOptions
var default_random_event_duration: float = 10.0
func _ready() -> void: func _ready() -> void:
UIEvents.day_time_changed.connect(_on_day_time_changed) UIEvents.day_time_changed.connect(_on_day_time_changed)
UIEvents.day_time_option_changed.connect(_on_day_time_option_changed)
UIEvents.weather_event_changed.connect(_on_weather_event_changed)
if day_night != null and day_night.environment_config != null and wind_slider != null:
wind_slider.set_value_no_signal(day_night.environment_config.wind_strength)
_sync_day_time_ui() _sync_day_time_ui()
_on_weather_event_changed("Weather: Clear")
func _on_rain_toggled(toggled_on: bool) -> void: func _on_rain_toggled(toggled_on: bool) -> void:
UIEvents.toggle_rain.emit(toggled_on) UIEvents.toggle_rain.emit(toggled_on)
@@ -18,6 +27,15 @@ func _on_snow_toggled(toggled_on: bool) -> void:
func _on_wind_toggled(toggled_on: bool) -> void: func _on_wind_toggled(toggled_on: bool) -> void:
UIEvents.toggle_wind.emit(toggled_on) UIEvents.toggle_wind.emit(toggled_on)
func _on_wind_slider_value_changed(value: float) -> void:
UIEvents.wind_change.emit(value)
func _on_random_weather_pressed() -> void:
var duration: float = default_random_event_duration
if day_night != null and day_night.environment_config != null:
duration = day_night.environment_config.random_event_duration
UIEvents.trigger_random_weather.emit(duration)
func _on_fireflies_toggled(toggled_on: bool) -> void: func _on_fireflies_toggled(toggled_on: bool) -> void:
UIEvents.toggle_fireflies.emit(toggled_on) UIEvents.toggle_fireflies.emit(toggled_on)
@@ -38,6 +56,10 @@ func _on_day_time_changed(value: float, time_string: String) -> void:
label_text = "Time %s | nt %.3f | dt %.3f" % [time_string, value, day_night.day_time] label_text = "Time %s | nt %.3f | dt %.3f" % [time_string, value, day_night.day_time]
day_time_label.text = label_text day_time_label.text = label_text
func _on_weather_event_changed(description: String) -> void:
if weather_event_label:
weather_event_label.text = description
func _on_fog_overlay_toggled(toggled_on: bool) -> void: func _on_fog_overlay_toggled(toggled_on: bool) -> void:
UIEvents.toggle_fog_overlay.emit(toggled_on) UIEvents.toggle_fog_overlay.emit(toggled_on)
@@ -68,3 +90,20 @@ func _sync_day_time_ui() -> void:
day_night_controller.current_time, day_night_controller.current_time,
day_night_controller.get_time_string() day_night_controller.get_time_string()
) )
_on_day_time_option_changed(
day_night_controller.get_time_option_index(day_night_controller.current_time)
)
func _on_day_time_option_changed(index: int) -> void:
if day_time_option == null:
return
day_time_option.select(index)
func _on_option_button_item_selected(index: int) -> void:
UIEvents.time_option_item_changed.emit(index)
func _on_fog_toggled(toggled_on: bool) -> void:
UIEvents.toggle_fog.emit(toggled_on)
func _on_update_rails_pressed() -> void:
UIEvents.update_rail_chunks.emit()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More