stick-the-quick/autoload/FX/ScreenEffects.gd

666 lines
21 KiB
GDScript

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