Files
Trinittu/dynamic-sky/scripts/weather_vfx.gd
2026-02-24 10:14:27 +01:00

288 lines
8.4 KiB
GDScript

## Manages rain and snow particle effects driven by the weather controller.
@tool
class_name WeatherVFX
extends Node3D
@export_group("Rain")
@export var rain_amount: int = 4000
@export var rain_area: Vector3 = Vector3(80.0, 40.0, 80.0)
@export var rain_speed: float = 20.0
@export var rain_color: Color = Color(0.7, 0.75, 0.85, 0.4)
@export_group("Snow")
@export var snow_amount: int = 2000
@export var snow_area: Vector3 = Vector3(80.0, 12.0, 80.0)
@export var snow_speed: float = 4.0
@export var snow_color: Color = Color(0.95, 0.95, 1.0, 0.8)
@export_group("Lightning")
@export var lightning_color: Color = Color(0.9, 0.9, 1.0)
@export var lightning_flash_duration: float = 0.15
@export_group("Thunder")
@export var thunder_enabled: bool = true
@export var thunder_min_delay: float = 1.0
@export var thunder_max_delay: float = 5.0
@export var thunder_volume_db: float = -2.0
var _rain_particles: GPUParticles3D
var _snow_particles: GPUParticles3D
var _rain_material: ShaderMaterial
var _snow_material: ShaderMaterial
var _target_rain: float = 0.0
var _target_snow: float = 0.0
var _current_rain: float = 0.0
var _current_snow: float = 0.0
var _lightning_active: bool = false
var _lightning_frequency: float = 0.0
var _lightning_timer: float = 0.0
var _lightning_next: float = 0.0
var _lightning_flash_timer: float = 0.0
var _lightning_light: DirectionalLight3D
var _thunder_player: AudioStreamPlayer
var _thunder_pending: bool = false
var _thunder_delay_timer: float = 0.0
func _ready() -> void:
_create_rain_system()
_create_snow_system()
_create_lightning_light()
_create_thunder_player()
func _process(delta: float) -> void:
_current_rain = move_toward(_current_rain, _target_rain, delta * 0.5)
_current_snow = move_toward(_current_snow, _target_snow, delta * 0.3)
_rain_particles.emitting = _current_rain > 0.01
_rain_particles.amount = maxi(1, int(rain_amount * _current_rain))
if _rain_material:
_rain_material.set_shader_parameter("alpha_mult", _current_rain)
_snow_particles.emitting = _current_snow > 0.01
_snow_particles.amount = maxi(1, int(snow_amount * _current_snow))
if _snow_material:
_snow_material.set_shader_parameter("alpha_mult", _current_snow)
_process_lightning(delta)
_process_thunder(delta)
_follow_camera()
func _process_lightning(delta: float) -> void:
if _lightning_flash_timer > 0.0:
_lightning_flash_timer -= delta
if _lightning_light:
var flash := _lightning_flash_timer / lightning_flash_duration
_lightning_light.light_energy = flash * 3.0
_lightning_light.visible = true
if _lightning_flash_timer <= 0.0 and _lightning_light:
_lightning_light.visible = false
if not _lightning_active or _lightning_frequency <= 0.0:
return
_lightning_timer += delta
if _lightning_timer >= _lightning_next:
_lightning_timer = 0.0
_lightning_next = randf_range(1.0 / maxf(_lightning_frequency, 0.01) * 0.5, 1.0 / maxf(_lightning_frequency, 0.01) * 2.0)
_trigger_lightning()
func _trigger_lightning() -> void:
_lightning_flash_timer = lightning_flash_duration
if _lightning_light:
_lightning_light.light_color = lightning_color
_lightning_light.light_energy = 3.0
_lightning_light.visible = true
_lightning_light.rotation_degrees.x = randf_range(-80.0, -50.0)
_lightning_light.rotation_degrees.y = randf_range(-180.0, 180.0)
if thunder_enabled:
_thunder_pending = true
_thunder_delay_timer = randf_range(thunder_min_delay, thunder_max_delay)
func _process_thunder(delta: float) -> void:
if not _thunder_pending:
return
_thunder_delay_timer -= delta
if _thunder_delay_timer <= 0.0:
_thunder_pending = false
_play_thunder()
func _play_thunder() -> void:
if _thunder_player == null:
return
_thunder_player.play()
func _create_thunder_player() -> void:
_thunder_player = AudioStreamPlayer.new()
_thunder_player.name = "ThunderPlayer"
_thunder_player.volume_db = thunder_volume_db
_thunder_player.bus = "Master"
_thunder_player.stream = load("res://resources/thunder.mp3")
add_child(_thunder_player)
func _create_lightning_light() -> void:
_lightning_light = DirectionalLight3D.new()
_lightning_light.name = "LightningFlash"
_lightning_light.light_color = lightning_color
_lightning_light.light_energy = 0.0
_lightning_light.shadow_enabled = false
_lightning_light.visible = false
add_child(_lightning_light)
func _follow_camera() -> void:
var vp := get_viewport()
if vp == null:
return
var cam := vp.get_camera_3d()
if cam:
global_position = cam.global_position
func set_rain_intensity(intensity: float) -> void:
_target_rain = clampf(intensity, 0.0, 1.0)
func set_snow_intensity(intensity: float) -> void:
_target_snow = clampf(intensity, 0.0, 1.0)
func set_lightning(active: bool, frequency: float = 0.1) -> void:
_lightning_active = active
_lightning_frequency = frequency
if not active and _lightning_light:
_lightning_light.visible = false
func _create_rain_system() -> void:
_rain_particles = GPUParticles3D.new()
_rain_particles.name = "RainParticles"
_rain_particles.amount = rain_amount
_rain_particles.lifetime = rain_area.y / rain_speed
_rain_particles.emitting = false
_rain_particles.visibility_aabb = AABB(-rain_area / 2.0, rain_area)
var mat := ParticleProcessMaterial.new()
mat.direction = Vector3(0.0, -1.0, 0.0)
mat.spread = 5.0
mat.initial_velocity_min = rain_speed * 0.8
mat.initial_velocity_max = rain_speed * 1.2
mat.gravity = Vector3(0.0, -9.8, 0.0)
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
mat.emission_box_extents = Vector3(rain_area.x / 2.0, 0.5, rain_area.z / 2.0)
_rain_particles.process_material = mat
_rain_particles.transform.origin.y = rain_area.y / 2.0
_rain_material = ShaderMaterial.new()
_rain_material.shader = _get_rain_shader()
_rain_material.set_shader_parameter("rain_color", rain_color)
_rain_material.set_shader_parameter("alpha_mult", 0.0)
var mesh := QuadMesh.new()
mesh.size = Vector2(0.02, 0.6)
mesh.material = _rain_material
_rain_particles.draw_pass_1 = mesh
add_child(_rain_particles)
func _create_snow_system() -> void:
_snow_particles = GPUParticles3D.new()
_snow_particles.name = "SnowParticles"
_snow_particles.amount = snow_amount
_snow_particles.lifetime = snow_area.y / snow_speed * 2.0
_snow_particles.emitting = false
_snow_particles.visibility_aabb = AABB(-snow_area / 2.0, snow_area)
var mat := ParticleProcessMaterial.new()
mat.direction = Vector3(0.0, -1.0, 0.0)
mat.spread = 15.0
mat.initial_velocity_min = snow_speed * 0.5
mat.initial_velocity_max = snow_speed * 1.0
mat.gravity = Vector3(0.0, -3, 0.0)
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
mat.emission_box_extents = Vector3(snow_area.x / 2.0, 1.0, snow_area.z / 2.0)
mat.turbulence_enabled = true
mat.turbulence_noise_strength = 1.5
mat.turbulence_noise_speed_random = 0.5
mat.turbulence_noise_scale = 4.0
_snow_particles.process_material = mat
_snow_particles.transform.origin.y = snow_area.y / 2.0
_snow_material = ShaderMaterial.new()
_snow_material.shader = _get_snow_shader()
_snow_material.set_shader_parameter("snow_color", snow_color)
_snow_material.set_shader_parameter("alpha_mult", 0.0)
var mesh := QuadMesh.new()
mesh.size = Vector2(0.12, 0.12)
mesh.material = _snow_material
_snow_particles.draw_pass_1 = mesh
add_child(_snow_particles)
func _get_rain_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type spatial;
render_mode unshaded, cull_disabled, depth_draw_opaque, blend_mix;
uniform vec4 rain_color : source_color = vec4(0.7, 0.75, 0.85, 0.4);
uniform float alpha_mult = 1.0;
void vertex() {
MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
MODEL_MATRIX[3]
);
}
void fragment() {
float fade = smoothstep(0.0, 0.3, UV.y) * smoothstep(1.0, 0.7, UV.y);
ALBEDO = rain_color.rgb;
ALPHA = rain_color.a * fade * alpha_mult;
}
"""
return shader
func _get_snow_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type spatial;
render_mode unshaded, cull_disabled, depth_draw_opaque, blend_mix;
uniform vec4 snow_color : source_color = vec4(0.95, 0.95, 1.0, 0.8);
uniform float alpha_mult = 1.0;
void vertex() {
MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
MODEL_MATRIX[3]
);
}
void fragment() {
float dist = length(UV - vec2(0.5));
float circle = smoothstep(0.5, 0.3, dist);
ALBEDO = snow_color.rgb;
ALPHA = snow_color.a * circle * alpha_mult;
}
"""
return shader