Files
tgcc/core/daynight/weather_controller.gd
2026-06-30 22:48:47 +02:00

1055 lines
41 KiB
GDScript

class_name WeatherController
extends Node
signal wind_parameters_changed(speed: float, strength: float, fade: float, direction: Vector2)
const SNOW_ACCUMULATION_DELAY: float = 4.0
const SNOW_ACCUMULATION_DELAY_THRESHOLD: float = 0.01
var thunder_audio_player: AudioStreamPlayer
var thunder_sounds: Array[AudioStream]
var rain_sounds: Array[AudioStream]
var rain_audio_player: AudioStreamPlayer
var particles_wind: GPUParticles3D
var particles_snow: GPUParticles3D
var particles_fireflies: GPUParticles3D
var particles_rain: GPUParticles3D
var godray: PackedScene
var environment_dust: ColorRect
var blur: ColorRect
var environment_shadows: MeshInstance3D
var fog: Node3D
var sun: DirectionalLight3D
var environment: WorldEnvironment
var environment_config: EnvironmentConfig
var camera: Camera3D
var camera_pivot: Node3D
var day_time: float = 0.0
var lightning_sprite: Sprite3D
var lightning_light = DirectionalLight3D.new();
var timer_next_lightning: float = 0.0
var is_raining: bool = false
var rain_intensity: float = 0.0
var rain_tween: Tween
var rain_audio_tween: Tween
var puddle_tween: Tween
var puddle_amount: float = 0.0
var clouds_tween: Tween
var snow_tween: Tween
var snow_weather_tween: Tween
var snow_particles_tween: Tween
var is_snowing: bool = false
var is_snow_accumulated: bool = false
var actual_snow_amount: float = 0.0
var snow_weather_amount: float = 0.0
var is_storm: bool = false
var cold_tween: Tween
var wind_tween: Tween
var is_windy: bool = false
var thereare_fireflies: bool = true
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,
fireflies: GPUParticles3D, rain: GPUParticles3D, ray: PackedScene,
dust: ColorRect, blurr: ColorRect, thefog: Node3D, envshadows: MeshInstance3D,
thesun: DirectionalLight3D, theenvironment: WorldEnvironment, environmentconfig: EnvironmentConfig,
thecamera: Camera3D, thecamerapivot: Node3D, thundersounds: Array[AudioStream], rainsounds: Array[AudioStream]) -> void:
particles_wind = wind
particles_snow = snow
particles_fireflies = fireflies
particles_rain = rain
godray = ray
sun = thesun
environment_dust = dust
blur = blurr
fog = thefog
environment_shadows = envshadows
environment = theenvironment
environment_config = environmentconfig
max_wind_amount = environment_config.wind_amount if environment_config != null else 0
camera = thecamera
camera_pivot = thecamerapivot
thunder_sounds = thundersounds
rain_sounds = rainsounds
init_rain()
init_fireflies()
init_wind()
init_snow()
init_lightning()
create_sound_players()
toggle_dust(environment_config.enable_dust)
toggle_blur(environment_config.enable_blur)
toggle_fog(environment_config.enable_fog)
#set camera pivot if available
if camera_pivot:
camera_pivot.get_node("pivot").camera_changed.connect(_set_camera)
func _ready() -> void:
#used for wind change state (wind_decoration)
add_to_group("weather_controller")
#connect events
UIEvents.toggle_rain.connect(toggle_rain)
UIEvents.toggle_snow.connect(toggle_snow)
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_storm.connect(toggle_storm)
UIEvents.toggle_dust.connect(toggle_dust)
UIEvents.toggle_blur.connect(toggle_blur)
UIEvents.toggle_fog.connect(toggle_fog)
UIEvents.toggle_shadows.connect(toggle_shadows)
_emit_weather_event_label()
func _process(delta: float) -> void:
_update_random_event_label(delta)
_follow_camera()
_update_fireflies_visibility()
var base_tint: Color
var base_sky_top: Color
var base_sky_horizon: Color
var base_fog_color: Color
var base_fog_density: float
var base_bloom: float
var base_exposure: float
var base_grad_top: Color
var base_grad_bot: Color
var base_grad_intensity: float
var base_water_color: Color
var base_rotation_sun: Vector3
if is_raining:
var target_rain = environment_config.storm_rain_intensity_multiplier if is_storm else 1.0
rain_intensity = move_toward(rain_intensity, target_rain, delta * 0.5)
if is_storm:
timer_next_lightning -= delta
if timer_next_lightning <= 0.0:
start_lightning()
init_lightning_timer()
else:
rain_intensity = move_toward(rain_intensity, 0.0, delta * 0.5)
RenderingServer.global_shader_parameter_set("global_rain_intensity", rain_intensity)
if day_time <= 2.0:
var t = day_time - 1.0
base_tint = environment_config.morning_color.lerp(environment_config.afternoon_color, t)
base_sky_top = environment_config.sky_top_morning.lerp(environment_config.sky_top_afternoon, t)
base_sky_horizon = environment_config.sky_horizon_morning.lerp(environment_config.sky_horizon_afternoon, t)
base_fog_color = environment_config.fog_color_morning.lerp(environment_config.fog_color_afternoon, t)
base_fog_density = lerp(environment_config.fog_density_morning, environment_config.fog_density_afternoon, t)
base_bloom = lerp(environment_config.glow_morning, environment_config.glow_afternoon, t)
base_exposure = lerp(environment_config.exposure_morning, environment_config.exposure_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_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)
elif day_time <= 3.0:
var t = day_time - 2.0
base_tint = environment_config.afternoon_color.lerp(environment_config.night_color, t)
base_sky_top = environment_config.sky_top_afternoon.lerp(environment_config.sky_top_night, t)
base_sky_horizon = environment_config.sky_horizon_afternoon.lerp(environment_config.sky_horizon_night, t)
base_fog_color = environment_config.fog_color_afternoon.lerp(environment_config.fog_color_night, t)
base_fog_density = lerp(environment_config.fog_density_afternoon, environment_config.fog_density_night, t)
base_bloom = lerp(environment_config.glow_afternoon, environment_config.glow_night, t)
base_exposure = lerp(environment_config.exposure_afternoon, environment_config.exposure_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_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)
else:
var t = day_time - 3.0
base_tint = environment_config.night_color.lerp(environment_config.morning_color, t)
base_sky_top = environment_config.sky_top_night.lerp(environment_config.sky_top_morning, t)
base_sky_horizon = environment_config.sky_horizon_night.lerp(environment_config.sky_horizon_morning, t)
base_fog_color = environment_config.fog_color_night.lerp(environment_config.fog_color_morning, t)
base_fog_density = lerp(environment_config.fog_density_night, environment_config.fog_density_morning, t)
base_bloom = lerp(environment_config.glow_night, environment_config.glow_morning, t)
base_exposure = lerp(environment_config.exposure_night, environment_config.exposure_morning, t)
base_grad_top = environment_config.grad_top_night.lerp(environment_config.grad_top_morning, t)
base_grad_bot = environment_config.grad_bot_night.lerp(environment_config.grad_bot_morning, t)
base_grad_intensity = lerp(environment_config.grad_intensity_night, environment_config.grad_intensity_morning, t)
base_water_color = environment_config.water_color_night.lerp(environment_config.water_color_morning, t)
base_rotation_sun = environment_config.sun_rotation_night.lerp(environment_config.sun_rotation_morning, t)
var weather_color = environment_config.rain_mode_color
if is_storm:
var storm_blend = clamp((rain_intensity - 1.0) / (environment_config.storm_rain_intensity_multiplier - 1.0), 0.0, 1.0)
weather_color = weather_color.lerp(environment_config.storm_mode_color, storm_blend)
var final_tint = base_tint.lerp(base_tint * weather_color, clamp(rain_intensity, 0.0, 1.0))
var final_sky_top = base_sky_top.lerp(base_sky_top * 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_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))
var rain_weather_amount: float = clamp(rain_intensity, 0.0, 1.0)
var storm_weather_amount: float = 0.0
if is_storm:
if environment_config.storm_rain_intensity_multiplier > 1.0:
storm_weather_amount = clamp((rain_intensity - 1.0) / (environment_config.storm_rain_intensity_multiplier - 1.0), 0.0, 1.0)
else:
storm_weather_amount = rain_weather_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, snow_weather_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, snow_weather_amount)
final_tint = final_tint.lerp(final_tint * _get_rain_day_color(), rain_weather_amount)
final_tint = final_tint.lerp(final_tint * _get_storm_day_color(), storm_weather_amount)
#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_bot = base_grad_bot.lerp(base_grad_bot * weather_color, clamp(rain_intensity, 0.0, 1.0))
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_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)
#Snow exposure compensation
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, snow_weather_amount)
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
var final_exposure = base_exposure * snow_light_attenuation
final_exposure = lerp(final_exposure, _get_rain_day_exposure(), rain_weather_amount)
final_exposure = lerp(final_exposure, _get_storm_day_exposure(), storm_weather_amount)
var reflected_color = Color(0.4, 0.5, 0.6, 1.0)
var drops_color = final_sky_horizon.lerp(reflected_color, 0.15)
drops_color = drops_color.lerp(drops_color * 0.4, night_val)
if environment_config.material_drops:
environment_config.material_drops.albedo_color = drops_color
if sun:
sun.light_color = final_tint
sun.rotation_degrees = base_rotation_sun
var target_energy = lerp(1.0, environment_config.night_light_energy, night_val)
# Sunlight scales based on weather/night
sun.light_energy = lerp(target_energy, target_energy * 0.4, rain_intensity)
if environment and environment.environment:
environment.environment.ambient_light_color = final_tint
# Apply fog
environment.environment.fog_enabled = true
environment.environment.fog_light_color = final_fog_color
environment.environment.fog_density = final_fog_density
# Apply glow
environment.environment.glow_enabled = true
environment.environment.glow_intensity = final_bloom
environment.environment.tonemap_exposure = final_exposure
var sky_mat = environment.environment.sky.sky_material as ShaderMaterial
if sky_mat:
sky_mat.set_shader_parameter("sky_top_color", final_sky_top)
sky_mat.set_shader_parameter("sky_horizon_color", final_sky_horizon)
sky_mat.set_shader_parameter("sun_color", final_tint)
sky_mat.set_shader_parameter("night_intensity", night_val)
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:
environment_config.material_clouds.set_shader_parameter("cloud_density", current_cloud_density)
var current_sharpness = lerp(0.14, 0.0, cloud_density_amount)
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:
environment_config.material_fog.set_shader_parameter("fog_color", final_fog_color)
environment_config.material_fog.set_shader_parameter("fog_density", clamp(final_fog_density * 25.0, 0.0, 1.0))
environment_config.material_fog.set_shader_parameter("night_intensity", night_val)
environment_config.material_fog.set_shader_parameter("sun_color", final_tint)
if fog:
for child in fog.get_children():
var fog_mesh := child as MeshInstance3D
if fog_mesh == null:
continue
var fog_material := fog_mesh.get_surface_override_material(0) as ShaderMaterial
if fog_material == null:
continue
fog_material.set_shader_parameter("fog_color", final_fog_color)
fog_material.set_shader_parameter("fog_density", clamp(final_fog_density * 25.0, 0.0, 1.0))
fog_material.set_shader_parameter("night_intensity", night_val)
fog_material.set_shader_parameter("sun_color", final_tint)
func create_sound_players():
rain_audio_player = AudioStreamPlayer.new()
rain_audio_player.name = "RainAudioPlayer"
rain_audio_player.volume_db = environment_config.rain_audio_volume_db
rain_audio_player.finished.connect(_on_rain_audio_player_finished)
add_child(rain_audio_player)
thunder_audio_player = AudioStreamPlayer.new()
thunder_audio_player.name = "ThunderAudioPlayer"
thunder_audio_player.volume_db = environment_config.thunder_audio_volume_db
add_child(thunder_audio_player)
func _get_rain_day_color() -> Color:
if day_time <= 2.0:
return environment_config.rain_morning_color.lerp(environment_config.rain_afternoon_color, day_time - 1.0)
elif day_time <= 3.0:
return environment_config.rain_afternoon_color.lerp(environment_config.rain_night_color, day_time - 2.0)
return environment_config.rain_night_color.lerp(environment_config.rain_morning_color, day_time - 3.0)
func _get_storm_day_color() -> Color:
if day_time <= 2.0:
return environment_config.storm_morning_color.lerp(environment_config.storm_afternoon_color, day_time - 1.0)
elif day_time <= 3.0:
return environment_config.storm_afternoon_color.lerp(environment_config.storm_night_color, day_time - 2.0)
return environment_config.storm_night_color.lerp(environment_config.storm_morning_color, day_time - 3.0)
func _get_rain_day_exposure() -> float:
if day_time <= 2.0:
return lerp(environment_config.rain_exposure_morning, environment_config.rain_exposure_afternoon, day_time - 1.0)
elif day_time <= 3.0:
return lerp(environment_config.rain_exposure_afternoon, environment_config.rain_exposure_night, day_time - 2.0)
return lerp(environment_config.rain_exposure_night, environment_config.rain_exposure_morning, day_time - 3.0)
func _get_storm_day_exposure() -> float:
if day_time <= 2.0:
return lerp(environment_config.storm_exposure_morning, environment_config.storm_exposure_afternoon, day_time - 1.0)
elif day_time <= 3.0:
return lerp(environment_config.storm_exposure_afternoon, environment_config.storm_exposure_night, day_time - 2.0)
return lerp(environment_config.storm_exposure_night, environment_config.storm_exposure_morning, day_time - 3.0)
#region camera
func _set_camera(curr_camera: Camera3D):
camera = curr_camera
func _get_weather_anchor_position() -> Variant:
if camera_pivot and is_instance_valid(camera_pivot):
return camera_pivot.global_position
if camera and is_instance_valid(camera):
return camera.global_position
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:
var cam_pos = _get_weather_anchor_position()
if cam_pos == null:
return
if particles_rain:
particles_rain.global_position = Vector3(cam_pos.x, particles_rain.position.y, cam_pos.z)
if particles_snow:
particles_snow.global_position = Vector3(cam_pos.x, particles_snow.position.y, cam_pos.z)
if particles_wind:
particles_wind.global_position = Vector3(cam_pos.x, cam_pos.y, cam_pos.z)
if particles_fireflies:
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
#region Fireflies
func toggle_fireflies(value: bool):
thereare_fireflies = value
_update_fireflies_visibility()
#disable fireflies and set default values and materials
func init_fireflies():
if particles_fireflies:
particles_fireflies.visible = true
particles_fireflies.emitting = false
particles_fireflies.amount = environment_config.fireflies_amount
var proc_mat = particles_fireflies.process_material as ParticleProcessMaterial
if proc_mat:
proc_mat.emission_box_extents = Vector3(environment_config.fireflies_spawn_ray, environment_config.fireflies_spawn_height, environment_config.fireflies_spawn_ray)
_update_fireflies_visibility()
func _should_show_fireflies() -> bool:
var is_night: bool = day_time >= 2.5
return thereare_fireflies and is_night and not is_raining and not is_snowing and not is_storm
func _update_fireflies_visibility() -> void:
if particles_fireflies:
particles_fireflies.emitting = _should_show_fireflies()
#endregion
#region Wind
func toggle_wind(value: bool):
is_windy = value
if particles_wind:
particles_wind.emitting = true
_apply_wind_state()
_emit_weather_event_label()
#disable wind and set default values and materials
func init_wind():
_update_wind_amount_from_strength(environment_config.wind_strength)
if particles_wind:
particles_wind.visible = true
particles_wind.emitting = true
particles_wind.amount = environment_config.wind_amount
var proc_mat_wind = particles_wind.process_material as ParticleProcessMaterial
if proc_mat_wind:
proc_mat_wind.emission_box_extents = Vector3(environment_config.wind_spawn_ray, environment_config.wind_spawn_height, environment_config.wind_spawn_ray)
if environment_config:
_apply_cloud_config()
_apply_wind_config()
_apply_dust_config()
func _apply_wind_config() -> void:
RenderingServer.global_shader_parameter_set("global_wind_direction", environment_config.wind_direction)
RenderingServer.global_shader_parameter_set("global_wind_scale", environment_config.wind_scale)
if particles_wind:
var proc_mat_wind = particles_wind.process_material as ParticleProcessMaterial
if proc_mat_wind:
var wind_direction_3d := Vector3(environment_config.wind_direction.x, 0.0, environment_config.wind_direction.y)
if wind_direction_3d.length_squared() > 0.0:
var wind_axis := wind_direction_3d.normalized()
var wind_cross := wind_axis.cross(Vector3.UP)
if wind_cross.length_squared() == 0.0:
wind_cross = Vector3.FORWARD
else:
wind_cross = wind_cross.normalized()
var wind_up := wind_cross.cross(wind_axis).normalized()
proc_mat_wind.direction = Vector3.UP
particles_wind.global_transform = Transform3D(Basis(wind_cross, wind_axis, wind_up), particles_wind.global_position)
_apply_wind_state(true)
func _get_target_wind_strength() -> float:
if is_windy:
return environment_config.wind_boost_strength
return environment_config.wind_strength
func _apply_wind_state(immediate: bool = false) -> void:
if environment_config == null:
return
if wind_tween and wind_tween.is_valid():
wind_tween.kill()
if particles_wind:
particles_wind.emitting = true
var active_wind_speed := environment_config.wind_speed
var active_wind_strength := _get_target_wind_strength()
var active_wind_fade := 1.0
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(active_wind_speed)
_set_current_wind_fade(active_wind_fade)
wind_tween = create_tween()
wind_tween.tween_method(_set_current_wind_strength, current_wind_strength, active_wind_strength, environment_config.wind_fade_in_out_time)
wind_tween.tween_callback(_finish_wind_transition)
func _set_current_wind_speed(value: float) -> void:
current_wind_speed = value
RenderingServer.global_shader_parameter_set("global_wind_speed", value)
_sync_wind_event_state()
func _set_current_wind_strength(value: float) -> void:
current_wind_strength = value
RenderingServer.global_shader_parameter_set("global_wind_strength", value)
_set_wind_particles_amount_from_strength(value)
_sync_wind_event_state()
func _set_current_wind_fade(value: float) -> void:
current_wind_fade = value
RenderingServer.global_shader_parameter_set("global_wind_fade", value)
_sync_wind_event_state()
func _sync_wind_event_state() -> void:
if environment_config == null:
return
#used to notify wind_decoration when wind change state
wind_parameters_changed.emit(current_wind_speed, current_wind_strength, current_wind_fade, environment_config.wind_direction)
func _finish_wind_transition() -> void:
_set_current_wind_speed(environment_config.wind_speed)
_set_current_wind_strength(_get_target_wind_strength())
_set_current_wind_fade(1.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 current_wind_fade > 0.0 and not is_windy:
_set_current_wind_strength(value)
if particles_wind:
particles_wind.amount = environment_config.wind_amount
_apply_wind_state()
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)
environment_config.wind_amount = _get_wind_particles_amount_from_strength(value)
func _get_wind_particles_amount_from_strength(value: float) -> int:
if max_wind_amount <= 0:
max_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)
return roundi(max_wind_amount * amount_ratio)
func _set_wind_particles_amount_from_strength(value: float) -> void:
if particles_wind:
particles_wind.amount = _get_wind_particles_amount_from_strength(value)
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:
var dir = environment_config.wind_direction
var cloud_scale = environment_config.cloud_scale
var cloud_speed = environment_config.cloud_speed
var envshadows_scale_rate = environment_config.cloud_scale_envshadows_rate
var envshadows_speed_rate = environment_config.cloud_speed_envshadows_rate
if environment and environment.environment and environment.environment.sky:
var sky_mat = environment.environment.sky.sky_material as ShaderMaterial
if sky_mat:
sky_mat.set_shader_parameter("cloud_direction", dir)
sky_mat.set_shader_parameter("cloud_scale", cloud_scale)
sky_mat.set_shader_parameter("cloud_speed", cloud_speed)
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("direction", dir)
env_shadow_mat.set_shader_parameter("cloud_scale", cloud_scale * envshadows_scale_rate)
env_shadow_mat.set_shader_parameter("cloud_speed", cloud_speed * envshadows_speed_rate)
if environment_config.material_clouds:
environment_config.material_clouds.set_shader_parameter("direction", dir)
environment_config.material_clouds.set_shader_parameter("cloud_scale", cloud_scale * envshadows_scale_rate)
environment_config.material_clouds.set_shader_parameter("cloud_speed", cloud_speed * envshadows_speed_rate)
#endregion
#region Lightning
func toggle_storm(value: bool):
is_storm = value
_emit_weather_event_label()
#set lightning
func init_lightning():
lightning_light.name = "Lightning Light"
lightning_light.transform = Transform3D(
Vector3(-0.6238797, -0.342596, 0.7024259), # asse X
Vector3(0.0, 0.89879405, 0.43837115), # asse Y
Vector3(-0.7815204, 0.27349085, -0.56073934), # asse Z
Vector3(0.0, 0.0, 30.411049) # origine
)
lightning_light.light_color = Color(0.8, 1.0, 1.0, 1.0)
lightning_light.light_energy = 0.0
lightning_light.shadow_enabled = true
add_child(lightning_light)
create_lightning_sprite()
init_lightning_timer()
func create_lightning_sprite():
lightning_sprite = Sprite3D.new()
lightning_sprite.name = "Lightning Sprite"
lightning_sprite.transform = Transform3D(
Basis(
Vector3(4, 0, 0),
Vector3(0, 7.32, 0),
Vector3(0, 0, 7.3)
),
Vector3(0, 17.324, 30.411049)
)
lightning_sprite.modulate = Color(1, 1, 1, 0)
lightning_sprite.billboard = BaseMaterial3D.BILLBOARD_ENABLED
lightning_sprite.alpha_cut = SpriteBase3D.ALPHA_CUT_DISCARD
#Add Texture
lightning_sprite.texture = load(environment_config.lightning_texture)
add_child(lightning_sprite)
#set next lightning timer
func init_lightning_timer():
timer_next_lightning = randf_range(environment_config.lightning_min_time, environment_config.lightning_max_time)
func start_lightning():
if lightning_light:
var tween_lighting = create_tween()
lightning_light.light_energy = 5.0
tween_lighting.tween_interval(0.05)
tween_lighting.tween_callback(func(): lightning_light.light_energy = 0.0)
tween_lighting.tween_interval(0.05)
tween_lighting.tween_callback(func(): lightning_light.light_energy = 2.0)
tween_lighting.tween_method(func(val): lightning_light.light_energy = val, 2.0, 0.0, 0.2)
var lightning_number = randi_range(environment_config.lightning_min, environment_config.lightning_max)
for i in lightning_number:
if lightning_sprite:
var random_angle = randf() * TAU
var distance = randf_range(10.0, 40.0)
lightning_sprite.position = Vector3(cos(random_angle) * distance, 10.0, sin(random_angle) * distance)
lightning_sprite.modulate = environment_config.lightning_color
lightning_sprite.modulate.a = 1.0
var s = randf_range(environment_config.lightning_scale_min, environment_config.lightning_scale_max)
lightning_sprite.scale = Vector3(s, s, s)
var tween_sprite = create_tween()
tween_sprite.tween_interval(0.2)
tween_sprite.tween_property(lightning_sprite, "modulate:a", 0.0, 0.4)
#Thunder sound; separated routine to do not stop the loop
_play_thunder_delayed(randf_range(0.5, 2.5))
if lightning_number > 1:
await get_tree().create_timer(randf_range(0.05, 0.15)).timeout
func _play_thunder_delayed(delay: float):
await get_tree().create_timer(delay).timeout
if thunder_sounds.is_empty() or not thunder_audio_player:
return
thunder_audio_player.stream = thunder_sounds[randi() % thunder_sounds.size()]
thunder_audio_player.pitch_scale = randf_range(0.9, 1.1)
thunder_audio_player.play()
#endregion
#region Rain
#disable rain
func init_rain():
if particles_rain:
particles_rain.visible = true
particles_rain.emitting = false
if rain_audio_player:
rain_audio_player.volume_db = environment_config.rain_audio_volume_db
rain_audio_player.stop()
RenderingServer.global_shader_parameter_set("global_rain_puddle_amount", 0.0)
func set_puddle_amount(value: float):
puddle_amount = value
RenderingServer.global_shader_parameter_set("global_rain_puddle_amount", value)
func toggle_rain(value: bool):
is_raining = value
if not particles_rain:
_emit_weather_event_label()
return
if is_raining and is_snowing:
toggle_snow(false)
if rain_tween and rain_tween.is_valid():
rain_tween.kill()
if puddle_tween and puddle_tween.is_valid():
puddle_tween.kill()
if is_raining:
particles_rain.amount_ratio = 0.0
particles_rain.emitting = true
if rain_audio_player:
if not rain_audio_player.playing:
rain_audio_player.volume_db = -80.0
_play_random_rain_sound()
_fade_rain_audio(environment_config.rain_audio_volume_db, false)
rain_tween = create_tween()
rain_tween.tween_property(particles_rain, "amount_ratio", 1.0, environment_config.rain_fade_time)
puddle_tween = create_tween()
puddle_tween.tween_method(set_puddle_amount, puddle_amount, 1.0, environment_config.puddle_form_time)
else:
if rain_audio_player:
_fade_rain_audio(-80.0, true)
rain_tween = create_tween()
rain_tween.tween_property(particles_rain, "amount_ratio", 0.0, environment_config.rain_fade_time)
rain_tween.tween_callback(func():
particles_rain.emitting = false
trigger_after_rain())
puddle_tween = create_tween()
puddle_tween.tween_method(set_puddle_amount, puddle_amount, 0.0, environment_config.puddle_dry_time)
_emit_weather_event_label()
func trigger_morning() -> void:
spawn_single_godray()
func trigger_after_rain() -> void:
if day_time < 2.5:
var count: int = randi_range(1, environment_config.godray_max_rain)
for i in count:
spawn_single_godray()
func spawn_single_godray() -> void:
var ray: Node3D = godray.instantiate()
get_tree().current_scene.add_child(ray)
var random_x: float = randf_range(-environment_config.godray_spawn_radius, environment_config.godray_spawn_radius)
var random_z: float = randf_range(-environment_config.godray_spawn_radius, environment_config.godray_spawn_radius)
var center := Vector3.ZERO
var anchor_pos = _get_weather_anchor_position()
if anchor_pos != null:
center = anchor_pos
ray.global_position = Vector3(center.x + random_x, environment_config.godray_spawn_height, center.z + random_z)
if ray.has_method("set_visual_rotation_degrees"):
ray.call("set_visual_rotation_degrees", environment_config.godray_rotation_degrees)
else:
ray.rotation_degrees = environment_config.godray_rotation_degrees
if ray.has_method("set_visual_scale"):
ray.call("set_visual_scale", environment_config.godray_scale)
else:
ray.scale = environment_config.godray_scale
if ray.has_method("orient_to_camera"):
ray.call("orient_to_camera")
func _play_random_rain_sound() -> void:
if rain_sounds.is_empty() or rain_audio_player == null:
return
rain_audio_player.stream = rain_sounds[randi() % rain_sounds.size()]
rain_audio_player.pitch_scale = 1.0
rain_audio_player.volume_db = minf(rain_audio_player.volume_db, environment_config.rain_audio_volume_db)
rain_audio_player.play()
func _on_rain_audio_player_finished() -> void:
if is_raining:
_play_random_rain_sound()
func _fade_rain_audio(target_volume_db: float, stop_after_fade: bool) -> void:
if rain_audio_player == null:
return
if rain_audio_tween and rain_audio_tween.is_valid():
rain_audio_tween.kill()
rain_audio_tween = create_tween()
rain_audio_tween.tween_property(
rain_audio_player,
"volume_db",
target_volume_db,
environment_config.rain_fade_time #same value of the rain particles
)
if stop_after_fade:
rain_audio_tween.tween_callback(func(): rain_audio_player.stop())
#endregion
#region Snow
func toggle_snow(value: bool):
is_snowing = value
if is_snowing and is_raining:
toggle_rain(false)
var snow_accumulation_delay: float = _get_snow_accumulation_delay() if is_snowing else 0.0
# Fade particelle neve
if particles_snow:
if snow_particles_tween and snow_particles_tween.is_valid():
snow_particles_tween.kill()
if is_snowing:
particles_snow.amount_ratio = 0.0
particles_snow.visible = true
particles_snow.emitting = true
snow_particles_tween = create_tween()
snow_particles_tween.tween_property(particles_snow, "amount_ratio", 1.0, environment_config.snow_fade_time)
start_snow_accumulation(snow_accumulation_delay)
else:
snow_particles_tween = create_tween()
snow_particles_tween.tween_property(particles_snow, "amount_ratio", 0.0, environment_config.snow_fade_time)
snow_particles_tween.tween_callback(func():
particles_snow.emitting = false)
start_snow_melt()
var target_snow_amount: float = 1.0 if is_snowing else 0.0
if snow_tween and snow_tween.is_valid():
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()
if is_snowing and snow_accumulation_delay > 0.0:
snow_tween.tween_interval(snow_accumulation_delay)
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
func init_snow(value: float = 0.0):
actual_snow_amount = value
snow_weather_amount = value
RenderingServer.global_shader_parameter_set("global_snow_amount", value)
if particles_snow:
particles_snow.emitting = false
if environment_config:
RenderingServer.global_shader_parameter_set("global_snow_threshold", environment_config.snow_threshold)
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_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_melt_time", -1.0)
func init_snow_amount(value: float):
actual_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(delay: float = 0.0) -> void:
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 + delay)
_flush_pending_weather_materials()
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_speed", environment_config.snow_melt_speed)
func _get_snow_accumulation_delay() -> float:
if actual_snow_amount > SNOW_ACCUMULATION_DELAY_THRESHOLD:
return 0.0
return SNOW_ACCUMULATION_DELAY
func _flush_pending_weather_materials() -> void:
var environment_manager := get_parent()
if environment_manager != null and environment_manager.has_method("flush_pending_environment_nodes"):
environment_manager.call("flush_pending_environment_nodes")
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
#region Post-Process
func toggle_dust(value: bool) -> void:
if environment_dust:
environment_dust.visible = value
func toggle_blur(value: bool) -> void:
if blur:
blur.visible = value
ApplyPostProcessBlurConfig()
func toggle_fog(value: bool) -> void:
if fog:
fog.visible = value
func toggle_shadows(value: bool) -> void:
if environment_shadows:
environment_shadows.visible = value
func ApplyPostProcessBlurConfig() -> void:
if blur == null:
return
var blur_material := blur.material as ShaderMaterial
if blur_material:
blur_material.set_shader_parameter("blur_amount", environment_config.blur_amount)
func _apply_dust_config() -> void:
var dust_color: Color = environment_config.dust_color
var dust_speed: float = environment_config.dust_speed
var dust_density: float = environment_config.dust_density
var dust_size_min: float = environment_config.dust_size_min
var dust_size_max: float = environment_config.dust_size_max
if environment_dust:
var dust_mat = environment_dust.material as ShaderMaterial
if dust_mat:
dust_mat.set_shader_parameter("dust_color", dust_color)
dust_mat.set_shader_parameter("dust_speed", dust_speed)
dust_mat.set_shader_parameter("dust_density", dust_density)
dust_mat.set_shader_parameter("dust_size_min", dust_size_min)
dust_mat.set_shader_parameter("dust_size_max", dust_size_max)
#endregion