diff --git a/fx/FX.gd b/fx/FX.gd new file mode 100644 index 0000000..732f5e1 --- /dev/null +++ b/fx/FX.gd @@ -0,0 +1,439 @@ +extends CanvasLayer +## Exposes high-level controls for overarching visual and audio effects. + +## Default weather intensity transition time. +const DEFAULT_WEATHER_FADE: float = 10.0 +## Default screen fade transition time. +const DEFAULT_FADE: float = 1.0 +## Default tint transition time. +const DEFAULT_TINT: float = 1.0 +## Default flash duration. +const DEFAULT_FLASH: float = 1.0 +## Max turbulence influence at peak snow magnitude. +const SNOW_MAX_MAX_TURBULENCE_INFLUENCE: float = 0.375 +## Max turbulence influence at zero snow magnitude. +const SNOW_MIN_MAX_TURBULENCE_INFLUENCE: float = 0.125 +## Minimum necessary rain magnitude for lightning. +const LIGHTNING_RAIN_THRESHOLD: float = 0.8 +## Probability of a lightning strike at any given frame at peak rain magnitude. +const LIGHTNING_MAX_PROBABILITY_PER_FRAME: float = 0.005 +## Maximum duration of random lighning. +const LIGHTNING_MAX_FADE: float = 1.0 + +## Active configuration. +@export var config: FXConfig + +## Audio fader for playing music. +@onready var bgm_fader := $BGM as AudioFader +## Audio fader for playing general-purpose continuous ambience. +@onready var bgs_fader := $BGS as AudioFader +## Ambience interpolator for playing continuous ambience related to rain. +@onready var rain_bgs_interpolator := $RainBGS as AmbienceInterpolator +## Ambience interpolator for playing continuous ambience related to snow. +@onready var snow_bgs_interpolator := $SnowBGS as AmbienceInterpolator +## Having a single universal environment allows high-level manipulation. +@onready var environment := $WorldEnvironment.environment as Environment +## Tween channel for changing fog color. +@onready var fog_color_tween_channel := TweenChannel.make_replacing() +## Tween channel for changing fog density. +@onready var fog_density_tween_channel := TweenChannel.make_replacing() +## Tween channel for changing fog sky affect. +@onready var fog_sky_affect_tween_channel := TweenChannel.make_replacing() +## Sky resource for use with environment. +@onready var sky := $WorldEnvironment.environment.sky as Sky +## Tween channels for shader globals. +@onready var shader_global_tween_channels := {} +## Having a single universal light source allows high-level manipulation. +@onready var light := $DirectionalLight3D as DirectionalLight3D +## Tween channel for moving light source. +@onready var light_transform_tween_channel := TweenChannel.make_replacing() +## Tween channel for changing light source energy. +@onready var light_energy_tween_channel := TweenChannel.make_replacing() +## Particle system for rendering rain. +@onready var rain_particles := $RainParticles as WeatherParticles +## Tween channel for changing rain particle amount ratio. +@onready var rain_amount_tween_channel := TweenChannel.make_replacing() +## Particle system for rendering snow. +@onready var snow_particles := $SnowParticles as WeatherParticles +## Tween channel for changing snow particle amount ratio. +@onready var snow_amount_tween_channel := TweenChannel.make_replacing() +## Tween channel for changing snow turbulence. +@onready var snow_turbulence_tween_channel := TweenChannel.make_replacing() +## Multiplicative solid-color overlay primarily for ambient effects. +@onready var tint_rect := $Tint as ColorRect +## Tween channel for changing tint color. +@onready var tint_tween_channel := TweenChannel.make_replacing() +## Alpha-blended solid-color overlay primarily for screen transitions. +@onready var fade_rect := $Fade as ColorRect +## Tween channel for changing fade color. +@onready var fade_tween_channel := TweenChannel.make_replacing() +## Alpha-blended solid-color overlay primarily for dramatic effect. +@onready var flash_rect := $Flash as ColorRect +## Tween channel for changing flash color. +@onready var flash_tween_channel := TweenChannel.make_replacing() + +## Sets the environment to use canvas layer 1 (and below) as the background. +func enable_canvas_background() -> void: + environment.background_mode = Environment.BG_CANVAS + environment.background_canvas_max_layer = 1 + +## Sets the environment to use the sky as the background. +func disable_canvas_background() -> void: + environment.background_mode = Environment.BG_SKY + environment.sky = sky + +## Applies the background mode associated with the active config. +func restore_background_mode() -> void: + if config.canvas: + enable_canvas_background() + else: + disable_canvas_background() + +## Fades out any current music and plays the given music instead. +func play_bgm(stream: AudioStream, fade: float = 0.0) -> void: + if fade > 0.0: + await bgm_fader.fade_in(stream, fade) + else: + await bgm_fader.play(stream) + +## Fades out any current music. +func stop_bgm(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: + await bgm_fader.fade_out(fade) + +## Plays the music specified by the active config. +func restore_bgm(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: + await play_bgm(config.bgm, fade) + +## Fades out any current general-purpose ambience and plays the given instead. +func play_bgs( + stream: AudioStream, + fade: float = AudioFader.DEFAULT_AUDIO_FADE +) -> void: + await bgs_fader.crossfade(stream, fade) + +## Fades out any current general-purpose ambience. +func stop_bgs(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: + await bgs_fader.fade_out(fade) + +## Plays the ambience specified by the active config. +func restore_bgs(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: + await play_bgs(config.bgs, fade) + +## Sets fog color. Alpha is used for density and sky affect. +func set_fog(color: Color, fade: float = DEFAULT_WEATHER_FADE) -> void: + var opaque := color + opaque.a = 1.0 + var set_fog_color := func() -> void: + var tween := await fog_color_tween_channel.create_tween(self) + tween.tween_property(environment, ^'fog_light_color', opaque, fade) + await fog_color_tween_channel.sync(tween) + var set_fog_density := func() -> void: + var tween := await fog_density_tween_channel.create_tween(self) + tween.tween_property(environment, ^'fog_density', color.a, fade) + await fog_density_tween_channel.sync(tween) + var set_fog_sky_affect := func() -> void: + var tween := ( + await fog_sky_affect_tween_channel.create_tween(self) + ) + tween.tween_property(environment, ^'fog_sky_affect', color.a, fade) + await fog_sky_affect_tween_channel.sync(tween) + await Task.group([ + Task.new(set_fog_color), + Task.new(set_fog_density), + Task.new(set_fog_sky_affect) + ]).sync() + +## Sets fog density and sky affect without changing color. +func set_fog_strength( + strength: float, + fade: float = DEFAULT_WEATHER_FADE +) -> void: + var set_fog_density := func() -> void: + var tween := await fog_density_tween_channel.create_tween(self) + tween.tween_property(environment, ^'fog_density', strength, fade) + await fog_density_tween_channel.sync(tween) + var set_fog_sky_affect := func() -> void: + var tween := ( + await fog_sky_affect_tween_channel.create_tween(self) + ) + tween.tween_property(environment, ^'fog_sky_affect', strength, fade) + await fog_sky_affect_tween_channel.sync(tween) + await Task.group([ + Task.new(set_fog_density), + Task.new(set_fog_sky_affect) + ]).sync() + +## Applies the fog settings specified by the active config. +func restore_fog(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_fog(config.fog, fade) + +## Sets global shader uniform. +func set_shader_global( + what: StringName, + value: float, + fade: float = DEFAULT_WEATHER_FADE +) -> void: + if not shader_global_tween_channels.has(what): + shader_global_tween_channels[what] = TweenChannel.make_replacing() + var tween_channel := shader_global_tween_channels[what] as TweenChannel + var tween := await tween_channel.create_tween(self) + tween.tween_method( + RenderingServer.global_shader_parameter_set.bind(what), + RenderingServer.global_shader_parameter_get(what), + value, fade + ) + await tween_channel.sync(tween) + +## Sets closeness to midnight. +func set_night(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_shader_global(&'night', value, fade) + +## Applies the night value specified by the active config. +func restore_night(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_night(config.night, fade) + +## Sets sky cloudiness. +func set_overcast(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_shader_global(&'overcast', value, fade) + +## Applies the overcast value specified by the active config. +func restore_overcast(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_overcast(config.overcast, fade) + +## Sets environment wetness. +func set_wet(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_shader_global(&'wet', value, fade) + +## Applies the wet value specified by the active config. +func restore_wet(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_wet(config.wet, fade) + +## Sets wind speed. +func set_wind(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_shader_global(&'wind', value, fade) + +## Applies the wind value specified by the active config. +func restore_wind(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_wind(config.wind, fade) + +## Sets light source's position (i.e. the opposite of its facing direction). +func set_light_source( + value: Vector3, + fade: float = DEFAULT_WEATHER_FADE +) -> void: + var from := light.basis + var to := Basis.looking_at(-value) + var setter := func(weight: float) -> void: + light.basis = from.slerp(to, weight) + var tween := await light_transform_tween_channel.create_tween(self) + tween.tween_method(setter, 0.0, 1.0, fade) + await light_transform_tween_channel.sync(tween) + +## Applies the light source position specified by the active config. +func restore_light_source(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_light_source(config.light_source, fade) + +## Sets light source's energy. +func set_light_energy( + value: float, + fade: float = DEFAULT_WEATHER_FADE +) -> void: + var tween := await light_energy_tween_channel.create_tween(self) + tween.tween_property(light, ^'light_energy', value, fade) + await light_energy_tween_channel.sync(tween) + +## Applies the light source energy specified by the active config. +func restore_light_energy(fade: float = DEFAULT_WEATHER_FADE) -> void: + await set_light_energy(config.light_energy, fade) + +## Sets the weather to rainfall at the given magnitude. +func set_rain(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: + await _set_weather(value, 0.0, fade) + +## Sets the weather to snowfall at the given magnitude. +func set_snow(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: + await _set_weather(0.0, value, fade) + +## Clears any active weather. +func stop_weather(fade: float = DEFAULT_WEATHER_FADE) -> void: + await _set_weather(0.0, 0.0, fade) + +## Applies the weather specified by the active config. +func restore_weather(fade: float = DEFAULT_WEATHER_FADE) -> void: + match config.weather_type: + FXConfig.WeatherType.NONE: + await stop_weather(fade) + FXConfig.WeatherType.RAIN: + await set_rain(config.weather_magnitude, fade) + FXConfig.WeatherType.SNOW: + await set_snow(config.weather_magnitude, fade) + +## Sets screen tint. +func tint(color: Color, fade: float = DEFAULT_TINT) -> void: + var tween := await tint_tween_channel.create_tween(self) + tween.tween_property(tint_rect, ^'color', color, fade) + await tint_tween_channel.sync(tween) + +## Clears screen tint. +func clear_tint(fade: float = DEFAULT_TINT) -> void: + await tint(Color.WHITE, fade) + +## Applies the screen tint specified by the active config. +func restore_tint(fade: float = DEFAULT_TINT) -> void: + await tint(config.tint, fade) + +## Fades the screen out to the given color. +func fade_out(color: Color = Color.BLACK, fade: float = DEFAULT_FADE) -> void: + var tween := await fade_tween_channel.create_tween(self) + tween.tween_property(fade_rect, ^'color', color, fade) + await fade_tween_channel.sync(tween) + +## Fades the screen in from any current screen fade state. +func fade_in(fade: float = DEFAULT_FADE) -> void: + var transparent_equiv := fade_rect.color + transparent_equiv.a = 0.0 + await fade_out(transparent_equiv, fade) + +## Flashes the screen the given color. +func flash(color: Color, fade: float = DEFAULT_FLASH) -> void: + var transparent_equiv := color + transparent_equiv.a = 0.0 + var tween := await flash_tween_channel.create_tween(self) + flash_rect.color = color + tween.tween_property(flash_rect, ^'color', transparent_equiv, fade) + await flash_tween_channel.sync(tween) + +## Simulates lightning by rapidly shifting fog, brightness, and time of day. +func lightning(value: float = 1.0, fade: float = DEFAULT_FLASH) -> void: + var night_task := func() -> void: + RenderingServer.global_shader_parameter_set( + &'night', lerpf(config.night, 0.0, value) + ) + await restore_night(fade) + var fog_task := func() -> void: + environment.fog_density = lerpf(config.fog.a, 0.0, value) + environment.fog_sky_affect = environment.fog_density + await set_fog_strength(config.fog.a, fade) + var light_task := func() -> void: + light.light_energy = lerpf(config.light_energy, 1.0, value) + await set_light_energy(config.light_energy, fade) + await Task.group([ + Task.new(night_task), + Task.new(fog_task), + Task.new(light_task) + ]).sync() + +## Applies given as active config. Restores all associated effects. +## +## If config to apply is null or not given, restores all effects +## associated with the current active config. +func apply_conifg( + new_config: FXConfig = null, + fade: float = DEFAULT_WEATHER_FADE +) -> void: + if new_config: + config = new_config + restore_background_mode() + var bgm_task := func() -> void: + await restore_bgm(fade) + var bgs_task := func() -> void: + await restore_bgs(fade) + var fog_task := func() -> void: + await restore_fog(fade) + var night_task := func() -> void: + await restore_night(fade) + var overcast_task := func() -> void: + await restore_overcast(fade) + var wet_task := func() -> void: + await restore_wet(fade) + var wind_task := func() -> void: + await restore_wind(fade) + var light_source_task := func() -> void: + await restore_light_source(fade) + var light_energy_task := func() -> void: + await restore_light_energy(fade) + var weather_task := func() -> void: + await restore_weather(fade) + var tint_task := func() -> void: + await restore_tint(fade) + await Task.group([ + Task.new(bgm_task), + Task.new(bgs_task), + Task.new(fog_task), + Task.new(night_task), + Task.new(overcast_task), + Task.new(wet_task), + Task.new(wind_task), + Task.new(light_source_task), + Task.new(light_energy_task), + Task.new(weather_task), + Task.new(tint_task) + ]).sync() + +## Changes the weather to an arbitrary and possibly chimeric configuration. +func _set_weather( + rain: float, + snow: float, + fade: float = DEFAULT_WEATHER_FADE +) -> void: + var rain_audio_task := func() -> void: + await rain_bgs_interpolator.interpolate(rain, fade) + var rain_amount_task := func() -> void: + var tween := await rain_amount_tween_channel.create_tween(self) + tween.tween_property(rain_particles, ^'amount_ratio', rain, fade) + await rain_amount_tween_channel.sync(tween) + var snow_audio_task := func() -> void: + await snow_bgs_interpolator.interpolate(snow, fade) + var snow_amount_task := func() -> void: + var tween := await snow_amount_tween_channel.create_tween(self) + tween.tween_property(snow_particles, ^'amount_ratio', snow, fade) + await snow_amount_tween_channel.sync(tween) + var max_turbulence: float = lerpf( + SNOW_MIN_MAX_TURBULENCE_INFLUENCE, + SNOW_MAX_MAX_TURBULENCE_INFLUENCE, + snow + ) + var min_turbulence: float = max_turbulence/2.0 + var snow_max_turbulence_task := func() -> void: + var tween := await snow_turbulence_tween_channel.create_tween(self) + tween.tween_property( + snow_particles.process_material, + ^'turbulence_influence_max', + max_turbulence, + fade + ) + await snow_turbulence_tween_channel.sync(tween) + var snow_min_turbulence_task := func() -> void: + var tween := await snow_turbulence_tween_channel.create_tween(self) + tween.tween_property( + snow_particles.process_material, + ^'turbulence_influence_min', + min_turbulence, + fade + ) + await snow_turbulence_tween_channel.sync(tween) + await Task.group([ + Task.new(rain_audio_task), + Task.new(rain_amount_task), + Task.new(snow_audio_task), + Task.new(snow_amount_task), + Task.new(snow_max_turbulence_task), + Task.new(snow_min_turbulence_task) + ]).sync() + +## Lightning handling for _process. +func _do_lightning() -> void: + if rain_particles.amount_ratio > LIGHTNING_RAIN_THRESHOLD: + var roll: float = randf() + var max_power: float = ( + (rain_particles.amount_ratio - LIGHTNING_RAIN_THRESHOLD)/ + (1.0 - LIGHTNING_RAIN_THRESHOLD) + ) + var check: float = max_power*LIGHTNING_MAX_PROBABILITY_PER_FRAME + if roll <= check: + var power: float = max_power*roll/check + # Intentionally not awaited. + lightning(power, power*LIGHTNING_MAX_FADE) + +func _process(_delta: float) -> void: + _do_lightning() diff --git a/fx/FX.tscn b/fx/FX.tscn new file mode 100644 index 0000000..0604d0b --- /dev/null +++ b/fx/FX.tscn @@ -0,0 +1,167 @@ +[gd_scene load_steps=26 format=3 uid="uid://bapbn52tw4uxt"] + +[ext_resource type="Script" path="res://fx/FX.gd" id="1_o5hjr"] +[ext_resource type="Script" path="res://audio/AudioFader.gd" id="2_lpome"] +[ext_resource type="Script" path="res://audio/ambience/AmbienceInterpolator.gd" id="3_kcje8"] +[ext_resource type="Shader" path="res://shaders/sky.gdshader" id="3_qkds1"] +[ext_resource type="AudioStream" uid="uid://bs25p8gni0x5t" path="res://audio/ambience/rain_level_1.ogg" id="4_m88w1"] +[ext_resource type="ArrayMesh" uid="uid://5s6wywij7e8l" path="res://fx/Raindrop.res" id="4_n0mfc"] +[ext_resource type="ArrayMesh" uid="uid://l6okcjkm4n72" path="res://fx/Snowflake.res" id="4_x3nfi"] +[ext_resource type="Script" path="res://fx/WeatherParticles.gd" id="5_4a72t"] +[ext_resource type="AudioStream" uid="uid://cvkmicogpj178" path="res://audio/ambience/rain_level_2.ogg" id="5_qq61e"] +[ext_resource type="AudioStream" uid="uid://gmicbgcj2e2k" path="res://audio/ambience/rain_level_3.ogg" id="6_boctk"] +[ext_resource type="AudioStream" uid="uid://dskrc0ofdbnbl" path="res://audio/ambience/rain_level_4.ogg" id="7_oixwt"] +[ext_resource type="AudioStream" uid="uid://t2y240536hb2" path="res://audio/ambience/rain_level_5.ogg" id="8_my1rc"] +[ext_resource type="AudioStream" uid="uid://clxqwer2sr55e" path="res://audio/ambience/thunder.ogg" id="9_fbpi5"] +[ext_resource type="AudioStream" uid="uid://fs3rqrp3cn3b" path="res://audio/ambience/howling_wind.ogg" id="10_dbbpf"] + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_jy0wt"] + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_guaq2"] +seamless = true +noise = SubResource("FastNoiseLite_jy0wt") + +[sub_resource type="Gradient" id="Gradient_uyl8u"] +offsets = PackedFloat32Array(0.764151, 1) + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_reikm"] +frequency = 0.75 + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ahwt5"] +width = 1024 +color_ramp = SubResource("Gradient_uyl8u") +noise = SubResource("FastNoiseLite_reikm") + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_khdbh"] +shader = ExtResource("3_qkds1") +shader_parameter/cloud_map = SubResource("NoiseTexture2D_guaq2") +shader_parameter/star_map = SubResource("NoiseTexture2D_ahwt5") + +[sub_resource type="Sky" id="Sky_y43u3"] +sky_material = SubResource("ShaderMaterial_khdbh") + +[sub_resource type="Environment" id="Environment_5ktyi"] +background_mode = 2 +background_canvas_max_layer = 1 +sky = SubResource("Sky_y43u3") +fog_enabled = true +fog_light_color = Color(0.517647, 0.552941, 0.607843, 1) +fog_density = 0.0 +fog_sky_affect = 0.0 + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_bqidl"] +particle_flag_align_y = true +emission_shape_offset = Vector3(0, 24, 0) +emission_shape = 6 +emission_ring_axis = Vector3(0, 1, 0) +emission_ring_height = 0.0 +emission_ring_radius = 32.0 +emission_ring_inner_radius = 16.0 +direction = Vector3(0, -1, 0) +spread = 3.0 +initial_velocity_min = 48.0 +initial_velocity_max = 48.0 +angular_velocity_min = 360.0 +angular_velocity_max = 720.0 +damping_min = 4.0 +damping_max = 4.0 +turbulence_noise_speed = Vector3(0, 0, 1) +turbulence_influence_min = 0.063 +turbulence_influence_max = 0.063 +collision_mode = 2 + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_x0w6v"] +particle_flag_rotate_y = true +emission_shape_offset = Vector3(0, 24, 0) +emission_shape = 6 +emission_ring_axis = Vector3(0, 1, 0) +emission_ring_height = 0.0 +emission_ring_radius = 32.0 +emission_ring_inner_radius = 16.0 +direction = Vector3(0, -1, 0) +initial_velocity_min = 3.0 +initial_velocity_max = 3.0 +angular_velocity_min = 360.0 +angular_velocity_max = 720.0 +damping_min = 4.0 +damping_max = 4.0 +turbulence_enabled = true +turbulence_noise_speed = Vector3(0, 0, 1) +turbulence_influence_min = 0.188 +turbulence_influence_max = 0.375 +collision_mode = 2 + +[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_4csoj"] +blend_mode = 3 + +[node name="FX" type="CanvasLayer"] +layer = 2 +script = ExtResource("1_o5hjr") + +[node name="BGM" type="Node" parent="."] +script = ExtResource("2_lpome") +bus = &"Music" + +[node name="BGS" type="Node" parent="."] +script = ExtResource("2_lpome") +bus = &"Sound" + +[node name="RainBGS" type="Node" parent="."] +script = ExtResource("3_kcje8") +bus = &"Sound" +streams = [ExtResource("4_m88w1"), ExtResource("5_qq61e"), ExtResource("6_boctk"), ExtResource("7_oixwt"), [ExtResource("8_my1rc"), ExtResource("9_fbpi5")]] + +[node name="SnowBGS" type="Node" parent="."] +script = ExtResource("3_kcje8") +bus = &"Sound" +streams = [ExtResource("10_dbbpf")] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_5ktyi") + +[node name="RainParticles" type="GPUParticles3D" parent="."] +amount = 32768 +amount_ratio = 0.0 +lifetime = 0.75 +visibility_aabb = AABB(-64, -64, -64, 128, 128, 128) +process_material = SubResource("ParticleProcessMaterial_bqidl") +draw_pass_1 = ExtResource("4_n0mfc") +script = ExtResource("5_4a72t") +seconds_ahead = 1.0 + +[node name="SnowParticles" type="GPUParticles3D" parent="."] +amount = 4096 +amount_ratio = 0.0 +lifetime = 6.0 +visibility_aabb = AABB(-64, -64, -64, 128, 128, 128) +process_material = SubResource("ParticleProcessMaterial_x0w6v") +draw_pass_1 = ExtResource("4_x3nfi") +script = ExtResource("5_4a72t") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +light_energy = 0.25 +shadow_enabled = true + +[node name="Tint" type="ColorRect" parent="."] +material = SubResource("CanvasItemMaterial_4csoj") +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Fade" type="ColorRect" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(1, 1, 1, 0) + +[node name="Flash" type="ColorRect" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(1, 1, 1, 0) diff --git a/fx/FXConfig.gd b/fx/FXConfig.gd new file mode 100644 index 0000000..9b3e03b --- /dev/null +++ b/fx/FXConfig.gd @@ -0,0 +1,36 @@ +class_name FXConfig extends Resource +## Stores settings to apply to FX. + +## Variety of weather condition. +enum WeatherType { + NONE, ## No particular weather condition. + RAIN, ## Rainfall. + SNOW ## Snowfall. +} + +## Whether to render a canvas layer in the background instead of sky. +@export var canvas := false +## Music that should play. +@export var bgm: AudioStream = null +## Ambience that should play. +@export var bgs: AudioStream = null +## Desired fog color. +@export var fog := Color.TRANSPARENT +## How close the sky should look to midnight. +@export var night: float = 0.0 +## How cloudy the sky should be. +@export var overcast: float = 0.25 +## How wet the environment should be. +@export var wet: float = 0.0 +## Desired wind speed. +@export var wind: float = 0.25 +## Direction the light should come from. (Opposite of actual facing direction.) +@export var light_source := Vector3.UP +## Light intensity. +@export var light_energy: float = 0.25 +## Weather that should occur. +@export var weather_type := WeatherType.NONE +## Amount of weather to occur. +@export var weather_magnitude: float = 0.0 +## Screen tint, e.g. for exceptionally hot or cold environments. +@export var tint := Color.WHITE diff --git a/fx/Raindrop.glb b/fx/Raindrop.glb new file mode 100644 index 0000000..6655d84 --- /dev/null +++ b/fx/Raindrop.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8317ea802c829c3cb862930c803d523fe161af949b72261f983525015c69c5 +size 4736 diff --git a/fx/Raindrop.glb.import b/fx/Raindrop.glb.import new file mode 100644 index 0000000..fc67f9a --- /dev/null +++ b/fx/Raindrop.glb.import @@ -0,0 +1,47 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bjodbd5e8vd5s" +path="res://.godot/imported/Raindrop.glb-5c9e24da8779228af34394222cad9207.scn" + +[deps] + +source_file="res://fx/Raindrop.glb" +dest_files=["res://.godot/imported/Raindrop.glb-5c9e24da8779228af34394222cad9207.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +import_script/path="" +_subresources={ +"meshes": { +"Raindrop_Cylinder": { +"generate/lightmap_uv": 0, +"generate/lods": 0, +"generate/shadow_meshes": 0, +"lods/normal_merge_angle": 60.0, +"lods/normal_split_angle": 25.0, +"save_to_file/enabled": true, +"save_to_file/make_streamable": "", +"save_to_file/path": "res://fx/Raindrop.res" +} +} +} +gltf/naming_version=1 +gltf/embedded_image_handling=1 diff --git a/fx/Raindrop.res b/fx/Raindrop.res new file mode 100644 index 0000000..7a8320b Binary files /dev/null and b/fx/Raindrop.res differ diff --git a/fx/Snowflake.glb b/fx/Snowflake.glb new file mode 100644 index 0000000..0c73f85 --- /dev/null +++ b/fx/Snowflake.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f7fb498549465c2aa0574835226a285c4370db986bb967a226cbf5e66a19c0b +size 130776 diff --git a/fx/Snowflake.glb.import b/fx/Snowflake.glb.import new file mode 100644 index 0000000..23df66e --- /dev/null +++ b/fx/Snowflake.glb.import @@ -0,0 +1,47 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bwgf3d4bhrbmf" +path="res://.godot/imported/Snowflake.glb-4fc7c590a9a455186ce9965fe6d9835b.scn" + +[deps] + +source_file="res://fx/Snowflake.glb" +dest_files=["res://.godot/imported/Snowflake.glb-4fc7c590a9a455186ce9965fe6d9835b.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +import_script/path="" +_subresources={ +"meshes": { +"Snowflake_Circle_001": { +"generate/lightmap_uv": 0, +"generate/lods": 0, +"generate/shadow_meshes": 0, +"lods/normal_merge_angle": 60.0, +"lods/normal_split_angle": 25.0, +"save_to_file/enabled": true, +"save_to_file/make_streamable": "", +"save_to_file/path": "res://fx/Snowflake.res" +} +} +} +gltf/naming_version=1 +gltf/embedded_image_handling=1 diff --git a/fx/Snowflake.res b/fx/Snowflake.res new file mode 100644 index 0000000..fcfb08f Binary files /dev/null and b/fx/Snowflake.res differ diff --git a/fx/WeatherParticles.gd b/fx/WeatherParticles.gd new file mode 100644 index 0000000..48acf42 --- /dev/null +++ b/fx/WeatherParticles.gd @@ -0,0 +1,25 @@ +class_name WeatherParticles extends GPUParticles3D +## Particle system that follows the camera. + +## How far directly in front of the camera to remain regardless of velocity. +@export var front_flat_distance: float = 1.0 +## How many seconds ahead of the camera to remain when it moves. +@export var seconds_ahead: float = 4.0 + +## Last known position of the camera, for tracking velocity. +var _last_camera_posn := Vector3.ZERO + +func _process(delta: float) -> void: + var camera := get_viewport().get_camera_3d() + if camera: + # Track camera velocity. + var camera_velocity := ( + camera.global_position - _last_camera_posn + )/delta + _last_camera_posn = camera.global_position + # Reposition particle system. + global_position = ( + camera.global_position + + camera.basis.z*front_flat_distance + + camera_velocity*seconds_ahead + )