class_name ScreenEffects extends CanvasLayer const DEFAULT_TRANSITION: float = 0.25 const DEFAULT_WEATHER_TRANSITION: float = 1.0 const DEFAULT_TIME_OF_DAY_TRANSITION: float = 6.0 const LONGEST_DEFAULT_TRANSITION: float = DEFAULT_TIME_OF_DAY_TRANSITION const AVERAGE_LIGHTNING_TIMEOUT: float = 10.0 const LIGHTNING_MAX_TRANSITION: float = 1.0 const NORMAL_OVERCAST: float = 0.25 const NORMAL_WIND: float = 0.25 const NORMAL_BRIGHTNESS: float = 0.25 const NORMAL_TWIST: float = 0.0625 const NORMAL_PSYCHEDELIC: float = 0.0625 const SIGNIFICANT_OVERCAST: float = 0.75 const SIGNIFICANT_WIND: float = 0.75 const RAIN_FOG_COLOR := Color(0.15, 0.22, 0.24) const SNOW_FOG_COLOR := Color(0.55, 0.59, 0.92) const RAIN_VOLUME_DB: float = -15.0 const THUNDER_VOLUME_DB: float = 0.0 const SNOW_VOLUME_DB: float = -5.0 const NON_SOLID_SHADERS := { "res://vfx/liquid.gdshader": true, "res://vfx/underlay.gdshader": true } const MAX_FOG_DENSITY: float = 0.0625 const MAX_FOG_SKY_AFFECT: float = 0.98 enum _EffectType {NONE, RAIN, SNOW, TINT, FADE, FLASH} enum _WeatherType { NONE, SNOW_LEVEL_1, SNOW_LEVEL_2, RAIN_LEVEL_1, RAIN_LEVEL_2, RAIN_LEVEL_3, RAIN_LEVEL_4, RAIN_LEVEL_5 } @onready var _tint_sprite := $'Tint' as Sprite2D @onready var _fade_sprite := $'Fade' as Sprite2D @onready var _flash_sprite := $'Flash' as Sprite2D @onready var _weather_parent := $'Weather' as Node2D @onready var _rain_level_5_particles := $'Weather/RainLevel5' as GPUParticles2D @onready var _rain_level_4_particles := $'Weather/RainLevel4' as GPUParticles2D @onready var _rain_level_3_particles := $'Weather/RainLevel3' as GPUParticles2D @onready var _rain_level_2_particles := $'Weather/RainLevel2' as GPUParticles2D @onready var _rain_level_1_particles := $'Weather/RainLevel1' as GPUParticles2D @onready var _snow_level_2_particles := $'Weather/SnowLevel2' as GPUParticles2D @onready var _snow_level_1_particles := $'Weather/SnowLevel1' as GPUParticles2D @onready var _rain_level_5_audio := ( $'Weather/RainLevel5/AudioStreamPlayer' as AudioStreamPlayer ) @onready var _rain_level_4_audio := ( $'Weather/RainLevel4/AudioStreamPlayer' as AudioStreamPlayer ) @onready var _rain_level_3_audio := ( $'Weather/RainLevel3/AudioStreamPlayer' as AudioStreamPlayer ) @onready var _rain_level_2_audio := ( $'Weather/RainLevel2/AudioStreamPlayer' as AudioStreamPlayer ) @onready var _rain_level_1_audio := ( $'Weather/RainLevel1/AudioStreamPlayer' as AudioStreamPlayer ) @onready var _snow_level_2_audio := ( $'Weather/SnowLevel2/AudioStreamPlayer' as AudioStreamPlayer ) @onready var _thunder_audio := $'Weather/Thunder' as AudioStreamPlayer @onready var _rain_level_5_material := ( _rain_level_5_particles.process_material as ParticleProcessMaterial ) @onready var _rain_level_4_material := ( _rain_level_4_particles.process_material as ParticleProcessMaterial ) @onready var _rain_level_3_material := ( _rain_level_3_particles.process_material as ParticleProcessMaterial ) @onready var _rain_level_2_material := ( _rain_level_2_particles.process_material as ParticleProcessMaterial ) @onready var _rain_level_1_material := ( _rain_level_1_particles.process_material as ParticleProcessMaterial ) @onready var _snow_level_2_material := ( _snow_level_2_particles.process_material as ParticleProcessMaterial ) @onready var _snow_level_1_material := ( _snow_level_1_particles.process_material as ParticleProcessMaterial ) @onready var _tint_gradient := ( _tint_sprite.texture as GradientTexture1D ).gradient @onready var _fade_gradient := ( _fade_sprite.texture as GradientTexture1D ).gradient @onready var _flash_gradient := ( _flash_sprite.texture as GradientTexture1D ).gradient @onready var _environment := ( preload("res://vfx/Environment.tres") as Environment ) @onready var _sky_material := ( _environment.sky.sky_material as ShaderMaterial ) @onready var _solid_materials := {} @onready var _light := $'DirectionalLight3D' as DirectionalLight3D @onready var _tint_lock := NonReentrantCoroutineMutex.new() @onready var _fade_lock := NonReentrantCoroutineMutex.new() @onready var _flash_lock := NonReentrantCoroutineMutex.new() @onready var _time_of_day_lock := NonReentrantCoroutineMutex.new() @onready var _overcast_lock := NonReentrantCoroutineMutex.new() @onready var _brightness_lock := NonReentrantCoroutineMutex.new() @onready var _wind_lock := NonReentrantCoroutineMutex.new() @onready var _fog_lock := NonReentrantCoroutineMutex.new() @onready var _light_source_lock := NonReentrantCoroutineMutex.new() @onready var _twist_lock := NonReentrantCoroutineMutex.new() @onready var _psychedelic_lock := NonReentrantCoroutineMutex.new() @onready var _lightning_lock := NonReentrantCoroutineMutex.new() var _tint_color: Color: get: return _tint_gradient.colors[0] set(value): _tint_gradient.colors[0] = value var _fade_color: Color: get: return _fade_gradient.colors[0] set(value): _fade_gradient.colors[0] = value var _flash_color: Color: get: return _flash_gradient.colors[0] set(value): _flash_gradient.colors[0] = value var _current_weather := _WeatherType.NONE func _register_node_material_if_solid_object_with_shader(node: Node) -> void: if node is MeshInstance3D: for i in node.get_surface_override_material_count(): var material := node.get_active_material(i) as Material if material is ShaderMaterial and not ( material.shader.resource_path in NON_SOLID_SHADERS ): _solid_materials[material.get_rid()] = material func _register_all_relevant_materials_starting_from_node(node: Node) -> void: for child in node.get_children(): _register_all_relevant_materials_starting_from_node(child) _register_node_material_if_solid_object_with_shader(node) func _ready() -> void: _reposition_children() get_viewport().size_changed.connect(_reposition_children) _tint_color = Color.TRANSPARENT _fade_color = Color.TRANSPARENT _flash_color = Color.TRANSPARENT get_tree().node_added.connect( _register_node_material_if_solid_object_with_shader ) _register_all_relevant_materials_starting_from_node(get_tree().root) await brightness(NORMAL_BRIGHTNESS, 0.0) await stop_weather(0.0) func _reposition_children() -> void: var view := get_viewport().get_visible_rect() _tint_sprite.position = view.position _fade_sprite.position = view.position _flash_sprite.position = view.position _weather_parent.position = view.position + Vector2(view.size.x/2.0, 0.0) _tint_sprite.scale = view.size _fade_sprite.scale = view.size _flash_sprite.scale = view.size _rain_level_5_material.emission_shape_scale.x = view.size.x _rain_level_4_material.emission_shape_scale.x = view.size.x _rain_level_3_material.emission_shape_scale.x = view.size.x _rain_level_2_material.emission_shape_scale.x = view.size.x _rain_level_1_material.emission_shape_scale.x = view.size.x _snow_level_2_material.emission_shape_scale.x = view.size.x _snow_level_1_material.emission_shape_scale.x = view.size.x func _weather_get_particles(type: _WeatherType) -> GPUParticles2D: match type: _WeatherType.NONE: return null _WeatherType.SNOW_LEVEL_1: return _snow_level_1_particles _WeatherType.SNOW_LEVEL_2: return _snow_level_2_particles _WeatherType.RAIN_LEVEL_1: return _rain_level_1_particles _WeatherType.RAIN_LEVEL_2: return _rain_level_2_particles _WeatherType.RAIN_LEVEL_3: return _rain_level_3_particles _WeatherType.RAIN_LEVEL_4: return _rain_level_4_particles _WeatherType.RAIN_LEVEL_5: return _rain_level_5_particles _: return null func _weather_get_audio(type: _WeatherType) -> AudioStreamPlayer: match type: _WeatherType.NONE: return null _WeatherType.SNOW_LEVEL_1: return null _WeatherType.SNOW_LEVEL_2: return _snow_level_2_audio _WeatherType.RAIN_LEVEL_1: return _rain_level_1_audio _WeatherType.RAIN_LEVEL_2: return _rain_level_2_audio _WeatherType.RAIN_LEVEL_3: return _rain_level_3_audio _WeatherType.RAIN_LEVEL_4: return _rain_level_4_audio _WeatherType.RAIN_LEVEL_5: return _rain_level_5_audio _: return null func _weather_has_lightning(type: _WeatherType) -> bool: return type == _WeatherType.RAIN_LEVEL_5 func _change_weather(type: _WeatherType) -> void: for other in _WeatherType: other = _WeatherType.get(other) if other != type: var other_particles := _weather_get_particles(other) if other_particles: other_particles.emitting = false var other_audio := _weather_get_audio(other) if other_audio and other_audio.playing: FX.audio_fade_out(other_audio) var particles := _weather_get_particles(type) if particles: particles.emitting = true var audio := _weather_get_audio(type) if audio: FX.audio_fade_in(audio, AudioFader.DEFAULT_FADE, ( SNOW_VOLUME_DB if type == _WeatherType.SNOW_LEVEL_2 else RAIN_VOLUME_DB )) if _weather_has_lightning(type): FX.audio_fade_in( _thunder_audio, AudioFader.DEFAULT_FADE, THUNDER_VOLUME_DB ) elif _thunder_audio.playing: FX.audio_fade_out(_thunder_audio) _current_weather = type func _process(delta: float) -> void: if _weather_has_lightning(_current_weather): _do_lightning(delta) func _do_lightning(delta: float) -> void: var roll: float = randf() var limit: float = delta/AVERAGE_LIGHTNING_TIMEOUT if roll < limit: lightning(1.0 - roll/limit) func tint( color: Color = Color.WHITE, transition: float = DEFAULT_TRANSITION ) -> void: await _tint_lock.acquire() var current_color := _tint_color if color.a == 0.0: color = Color(current_color) color.a = 1.0 color = color.lerp(Color.WHITE, 1.0 - color.a) color.a = 1.0 var rate := (color - current_color)/transition while transition > 0.0: var delta: float = await Wait.tick() _tint_color = current_color current_color += rate*delta transition -= delta _tint_color = color _tint_lock.relinquish() func clear_tint(transition: float = DEFAULT_TRANSITION) -> void: await tint(Color.WHITE, transition) func fade( color: Color = Color.TRANSPARENT, transition: float = DEFAULT_TRANSITION ) -> void: await _fade_lock.acquire() var current_color := _fade_color if color.a == 0.0: color = Color(current_color) color.a = 0.0 var rate := (color - current_color)/transition while transition > 0.0: var delta: float = await Wait.tick() _fade_color = current_color current_color += rate*delta transition -= delta _fade_color = color _fade_lock.relinquish() func clear_fade(transition: float = DEFAULT_TRANSITION) -> void: await fade(Color.TRANSPARENT, transition) func flash( color: Color = Color.TRANSPARENT, transition: float = DEFAULT_TRANSITION ) -> void: await _flash_lock.acquire() var old_color := _flash_color if old_color.a == 0.0: old_color = Color(color) old_color.a = 0.0 _flash_color = color var rate := (old_color - _flash_color)/transition while transition > 0.0: var delta: float = await Wait.tick() _flash_color += rate*delta transition -= delta _flash_color = old_color _flash_lock.relinquish() func rain( magnitude: int = 3, transition: float = DEFAULT_TRANSITION, do_not_adjust_overcast: bool = false, do_not_adjust_brightness: bool = false, do_not_adjust_wind: bool = false ) -> void: var do_overcast := func(): if not do_not_adjust_overcast: await overcast( 0.5 + float(magnitude)*0.1, transition, bool(do_not_adjust_brightness) ) var do_wind := func(): if not do_not_adjust_wind: await wind(0.5 + float(magnitude)*0.1, transition) var do_shader := func(): await _animate_solid_shader_universal_param( &'wet', float(magnitude)/5.0, transition ) var do_weather := func(): match magnitude: 1: _change_weather(_WeatherType.RAIN_LEVEL_1) 2: _change_weather(_WeatherType.RAIN_LEVEL_2) 3: _change_weather(_WeatherType.RAIN_LEVEL_3) 4: _change_weather(_WeatherType.RAIN_LEVEL_4) 5: _change_weather(_WeatherType.RAIN_LEVEL_5) _: push_error("Invalid rain magnitude %d" % magnitude) var do_fog := func(): await _animate_fog( RAIN_FOG_COLOR, float(magnitude)*MAX_FOG_DENSITY/5.0, transition ) await Promise.new([ do_overcast, do_wind, do_shader, do_weather, do_fog ]).join() func snow( magnitude: int = 1, transition: float = DEFAULT_TRANSITION, do_not_adjust_overcast: bool = false, do_not_adjust_brightness: bool = false, do_not_adjust_wind: bool = false ) -> void: var do_overcast := func(): if not do_not_adjust_overcast: await overcast( 0.25*float(magnitude + 1), transition, bool(do_not_adjust_brightness) ) var do_wind := func(): if not do_not_adjust_wind: await wind(0.5*float(magnitude + 1), transition) var do_shader := func(): await _animate_solid_shader_universal_param(&'wet', 0.0, transition) var do_weather := func(): match magnitude: 1: _change_weather(_WeatherType.SNOW_LEVEL_1) 2: _change_weather(_WeatherType.SNOW_LEVEL_2) _: push_error("Invalid snow magnitude %d" % magnitude) var do_fog := func(): await _animate_fog( SNOW_FOG_COLOR, float(magnitude)*MAX_FOG_DENSITY/3.0, transition ) await Promise.new([ do_overcast, do_wind, do_shader, do_weather, do_fog ]).join() func stop_weather( transition: float = DEFAULT_TRANSITION, do_not_adjust_overcast: bool = false, do_not_adjust_brightness: bool = false, do_not_adjust_wind: bool = false ) -> void: var do_weather := func(): _change_weather(_WeatherType.NONE) var do_shader := func(): await _animate_solid_shader_universal_param(&'wet', 0.0, transition) var do_overcast := func(): if not do_not_adjust_overcast: await overcast( NORMAL_OVERCAST, transition, bool(do_not_adjust_brightness) ) var do_wind := func(): if not do_not_adjust_wind: await wind(NORMAL_WIND, transition) var do_fog := func(): await _animate_fog(Color.BLACK, 0.0, transition) await Promise.new([ do_overcast, do_wind, do_shader, do_weather, do_fog ]).join() func _animate_shader_param( material: ShaderMaterial, param: StringName, magnitude: float, transition: float ) -> void: magnitude = clampf(magnitude, 0.0, 1.0) var current_v = material.get_shader_parameter(param) var current: float = 0.0 if current_v and current_v is float: current = current_v var rate: float = (magnitude - current)/transition while (magnitude - current)*signf(rate) > 0.0: material.set_shader_parameter(param, current) current += rate*(await Wait.tick()) material.set_shader_parameter(param, magnitude) func _animate_sky_shader_param( param: StringName, magnitude: float, transition: float ) -> void: await _animate_shader_param(_sky_material, param, magnitude, transition) func _animate_solid_shader_universal_param( param: StringName, magnitude: float, transition: float ) -> void: for rid in _solid_materials: _animate_shader_param( _solid_materials[rid], param, magnitude, transition ) var countdown: float = transition while countdown > 0.0: countdown -= await Wait.tick() func _animate_fog( color: Color, magnitude: float, transition: float ) -> void: await _fog_lock.acquire() magnitude = clampf(magnitude, 0.0, 1.0) var current: float = _environment.fog_density var current_color := _environment.fog_light_color var rate: float = (magnitude - current)/transition var color_rate := (color - current_color)/transition while (magnitude - current)*signf(rate) > 0.0: _environment.fog_density = current _environment.fog_sky_affect = ( current*MAX_FOG_SKY_AFFECT/MAX_FOG_DENSITY ) _environment.fog_light_color = current_color var delta: float = await Wait.tick() current += rate*delta current_color += color_rate*delta _environment.fog_density = magnitude _environment.fog_sky_affect = magnitude*MAX_FOG_SKY_AFFECT/MAX_FOG_DENSITY _environment.fog_light_color = color _fog_lock.relinquish() func wind( magnitude: float = SIGNIFICANT_WIND, transition: float = DEFAULT_WEATHER_TRANSITION ) -> void: await _wind_lock.acquire() await _animate_shader_param( _sky_material, &'windy', magnitude, transition ) _wind_lock.relinquish() func overcast( magnitude: float = SIGNIFICANT_OVERCAST, transition: float = DEFAULT_WEATHER_TRANSITION, do_not_adjust_brightness: bool = false ) -> void: await _overcast_lock.acquire() var do_brightness := func(): if not do_not_adjust_brightness: await brightness( lerpf( NORMAL_BRIGHTNESS, NORMAL_BRIGHTNESS/2.0, (magnitude - NORMAL_OVERCAST)*4.0/3.0 ), transition ) var do_shader := func(): await _animate_shader_param( _sky_material, &'overcast', magnitude, transition ) await Promise.new([ do_brightness, do_shader ]).join() _overcast_lock.relinquish() func stop_wind(transition: float = DEFAULT_WEATHER_TRANSITION) -> void: await wind(NORMAL_WIND, transition) func clear_overcast( transition: float = DEFAULT_WEATHER_TRANSITION, do_not_adjust_brightness: bool = false ) -> void: await overcast(NORMAL_OVERCAST, transition, bool(do_not_adjust_brightness)) func morning(transition: float = DEFAULT_TIME_OF_DAY_TRANSITION) -> void: await _time_of_day_lock.acquire() await _animate_sky_shader_param(&'night', 0.5, transition) _time_of_day_lock.relinquish() func day(transition: float = DEFAULT_TIME_OF_DAY_TRANSITION) -> void: await _time_of_day_lock.acquire() await _animate_sky_shader_param(&'night', 0.0, transition) _time_of_day_lock.relinquish() func afternoon(transition: float = DEFAULT_TIME_OF_DAY_TRANSITION) -> void: await _time_of_day_lock.acquire() await _animate_sky_shader_param(&'night', 0.25, transition) _time_of_day_lock.relinquish() func evening(transition: float = DEFAULT_TIME_OF_DAY_TRANSITION) -> void: await _time_of_day_lock.acquire() await _animate_sky_shader_param(&'night', 0.75, transition) _time_of_day_lock.relinquish() func night(transition: float = DEFAULT_TIME_OF_DAY_TRANSITION) -> void: await _time_of_day_lock.acquire() await _animate_sky_shader_param(&'night', 1.0, transition) _time_of_day_lock.relinquish() func light_source( point_at_infinity: Vector3, transition: float = DEFAULT_TIME_OF_DAY_TRANSITION ) -> void: await _light_source_lock.acquire() var up := Vector3.UP if up.cross(point_at_infinity).is_zero_approx(): up = Vector3.RIGHT if up.cross(point_at_infinity).is_zero_approx(): up = Vector3.BACK var countdown: float = transition while countdown > 0.0: _light.basis = _light.basis.slerp( Basis.looking_at(-point_at_infinity, up), 1.0 - countdown/transition ) var delta: float = await Wait.tick() countdown -= delta _light.look_at(-point_at_infinity, up) _light_source_lock.relinquish() func _light_source_brightness(magnitude: float, transition: float) -> void: magnitude = clampf(magnitude, 0.0, 1.0) var current: float = _light.light_energy var rate: float = (magnitude - current)/transition while (magnitude - current)*signf(rate) > 0.0: _light.light_energy = current current += rate*(await Wait.tick()) _light.light_energy = magnitude func brightness( magnitude: float = NORMAL_BRIGHTNESS, transition: float = DEFAULT_TIME_OF_DAY_TRANSITION ) -> void: await _brightness_lock.acquire() var do_shader := func(): await _animate_sky_shader_param(&'bright', magnitude, transition) var do_light_source := func(): await _light_source_brightness( lerpf(NORMAL_BRIGHTNESS/1.5, 4.0*NORMAL_BRIGHTNESS/3.0, magnitude), transition ) await Promise.new([ do_shader, do_light_source ]).join() _brightness_lock.relinquish() func twist( magnitude: float = NORMAL_TWIST, transition: float = DEFAULT_WEATHER_TRANSITION ) -> void: await _twist_lock.acquire() await _animate_sky_shader_param(&'twist', magnitude, transition) _twist_lock.relinquish() func psychedelic( magnitude: float = NORMAL_PSYCHEDELIC, transition: float = DEFAULT_TIME_OF_DAY_TRANSITION ) -> void: await _psychedelic_lock.acquire() await _animate_sky_shader_param(&'twist', magnitude, transition) _psychedelic_lock.relinquish() func lightning(intensity: float = 1.0) -> void: await _lightning_lock.acquire() var transition: float = intensity*LIGHTNING_MAX_TRANSITION var do_shader := func(): var old_night: float = ( _sky_material.get_shader_parameter(&'night') ) var scaled_intensity: float = lerpf(0.875, 1.0, intensity) _sky_material.set_shader_parameter( &'night', lerpf(old_night, 0.0, scaled_intensity) ) await _animate_sky_shader_param(&'night', old_night, transition) var do_fog := func(): var old_fog: float = _environment.fog_density _environment.fog_density = 0.0 await _animate_fog( _environment.fog_light_color, old_fog, transition ) var do_brightness := func(): var old_brightness: float = ( _sky_material.get_shader_parameter(&'bright') ) await brightness( lerpf(old_brightness, 1.0, lerpf(0.875, 1.0, intensity)), 0.0 ) await brightness(old_brightness, transition) await Promise.new([ do_shader, do_fog, do_brightness ]).join() _lightning_lock.relinquish() func hide_weather_particles() -> void: _rain_level_1_particles.visible = false _rain_level_2_particles.visible = false _rain_level_3_particles.visible = false _rain_level_4_particles.visible = false _rain_level_5_particles.visible = false _snow_level_1_particles.visible = false _snow_level_2_particles.visible = false func unhide_weather_particles() -> void: _rain_level_1_particles.visible = true _rain_level_2_particles.visible = true _rain_level_3_particles.visible = true _rain_level_4_particles.visible = true _rain_level_5_particles.visible = true _snow_level_1_particles.visible = true _snow_level_2_particles.visible = true func current_tint() -> Color: return _tint_color func current_fade() -> Color: return _fade_color