## 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, 30.0, 80.0) @export var snow_speed: float = 3.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 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 func _ready() -> void: _create_rain_system() _create_snow_system() _create_lightning_light() 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) _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) 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.5 _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, -1.0, 0.0) mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX mat.emission_box_extents = Vector3(snow_area.x / 2.0, 0.5, snow_area.z / 2.0) mat.turbulence_enabled = true mat.turbulence_noise_strength = 2.0 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.08, 0.08) 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