247 lines
7.4 KiB
GDScript
247 lines
7.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, 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
|