Files
Trinittu/dynamic-sky/scripts/vfx_controller.gd
Overside srl 0ca80ae65b add rain sound
2026-02-24 18:55:34 +01:00

297 lines
8.0 KiB
GDScript

@tool
class_name VFXController
extends Node3D
signal shooting_star_spawned(position: Vector3, direction: Vector3)
signal meteor_shower_started()
signal meteor_shower_ended()
@export_group("Shooting Stars")
@export var shooting_stars_enabled: bool = true
@export var shooting_star_frequency: float = 0.05
@export var min_spawn_interval: float = 5.0
@export var max_spawn_interval: float = 30.0
@export_group("Shooting Star Appearance")
@export var trail_length: float = 2.0
@export var trail_width: float = 0.05
@export var star_brightness: float = 1.5
@export var star_color: Color = Color(1.0, 1.0, 0.9)
@export var trail_fade_time: float = 0.5
@export_group("Meteor Shower")
@export var meteor_shower_active: bool = false
@export var meteor_shower_intensity: float = 1.0
@export var meteors_per_second: float = 3.0
@export var meteor_shower_duration: float = 60.0
@export_group("Meteor Appearance")
@export var meteor_trail_length: float = 4.0
@export var meteor_brightness: float = 2.0
@export var meteor_color: Color = Color(1.0, 0.8, 0.5)
@export_group("Spawn Area")
@export var spawn_radius: float = 500.0
@export var spawn_height_min: float = 100.0
@export var spawn_height_max: float = 200.0
@export var spawn_direction_variance: float = 30.0
@export_group("Audio")
@export var meteor_shower_audio_event: String = ""
var _spawn_timer: float = 0.0
var _next_spawn_time: float = 0.0
var _meteor_spawn_accumulator: float = 0.0
var _meteor_shower_elapsed: float = 0.0
var _active_stars: Array[Dictionary] = []
var _star_material: ShaderMaterial
var _is_night: bool = false
func _ready() -> void:
_setup_star_mesh()
_schedule_next_shooting_star()
func _process(delta: float) -> void:
if not _is_night:
return
if shooting_stars_enabled:
_process_shooting_stars(delta)
if meteor_shower_active:
_process_meteor_shower(delta)
_update_active_stars(delta)
func _setup_star_mesh() -> void:
_star_material = ShaderMaterial.new()
_star_material.shader = _get_star_shader()
_star_material.set_shader_parameter("color", star_color)
_star_material.set_shader_parameter("brightness", star_brightness)
func _get_star_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type spatial;
render_mode unshaded, depth_draw_never, cull_disabled, blend_add;
uniform vec4 color : source_color = vec4(1.0, 1.0, 0.9, 1.0);
uniform float brightness = 1.5;
uniform float fade = 1.0;
uniform float trail_progress = 0.0;
void vertex() {
VERTEX.y *= mix(0.1, 1.0, trail_progress);
}
void fragment() {
float gradient = UV.y;
float alpha = gradient * fade;
ALBEDO = color.rgb * brightness;
ALPHA = alpha * color.a;
}
"""
return shader
func _schedule_next_shooting_star() -> void:
_next_spawn_time = randf_range(min_spawn_interval, max_spawn_interval)
_next_spawn_time /= maxf(0.01, shooting_star_frequency)
_spawn_timer = 0.0
func _process_shooting_stars(delta: float) -> void:
_spawn_timer += delta
if _spawn_timer >= _next_spawn_time:
_spawn_shooting_star()
_schedule_next_shooting_star()
func _process_meteor_shower(delta: float) -> void:
_meteor_shower_elapsed += delta
if _meteor_shower_elapsed >= meteor_shower_duration:
stop_meteor_shower()
return
_meteor_spawn_accumulator += meteors_per_second * meteor_shower_intensity * delta
while _meteor_spawn_accumulator >= 1.0:
_meteor_spawn_accumulator -= 1.0
_spawn_meteor()
func _spawn_shooting_star() -> void:
var spawn_pos := _get_random_sky_position()
var direction := _get_random_fall_direction()
var star_data := {
"position": spawn_pos,
"direction": direction,
"speed": randf_range(50.0, 100.0),
"lifetime": 0.0,
"max_lifetime": trail_fade_time + randf_range(0.2, 0.5),
"trail_length": trail_length * randf_range(0.8, 1.2),
"brightness": star_brightness * randf_range(0.8, 1.2),
"color": star_color,
"mesh": _create_star_trail(spawn_pos, direction),
"is_meteor": false
}
_active_stars.append(star_data)
shooting_star_spawned.emit(spawn_pos, direction)
func _spawn_meteor() -> void:
var spawn_pos := _get_random_sky_position()
var direction := _get_random_fall_direction()
var star_data := {
"position": spawn_pos,
"direction": direction,
"speed": randf_range(80.0, 150.0),
"lifetime": 0.0,
"max_lifetime": trail_fade_time + randf_range(0.3, 0.7),
"trail_length": meteor_trail_length * randf_range(0.8, 1.2),
"brightness": meteor_brightness * randf_range(0.8, 1.2),
"color": meteor_color,
"mesh": _create_star_trail(spawn_pos, direction, true),
"is_meteor": true
}
_active_stars.append(star_data)
func _get_random_sky_position() -> Vector3:
var angle := randf() * TAU
var radius := randf_range(spawn_radius * 0.3, spawn_radius)
var height := randf_range(spawn_height_min, spawn_height_max)
return Vector3(
cos(angle) * radius,
height,
sin(angle) * radius
)
func _get_random_fall_direction() -> Vector3:
var base_dir := Vector3(-0.5, -0.8, -0.3).normalized()
var variance := deg_to_rad(spawn_direction_variance)
var curr_rotation := Basis(Vector3.UP, randf() * TAU)
curr_rotation = curr_rotation.rotated(Vector3.RIGHT, randf_range(-variance, variance))
return (curr_rotation * base_dir).normalized()
func _create_star_trail(position: Vector3, direction: Vector3, is_meteor: bool = false) -> MeshInstance3D:
var mesh_instance := MeshInstance3D.new()
var length := meteor_trail_length if is_meteor else trail_length
var width := trail_width * (1.5 if is_meteor else 1.0)
var quad := QuadMesh.new()
quad.size = Vector2(width, length)
mesh_instance.mesh = quad
var material := _star_material.duplicate() as ShaderMaterial
material.set_shader_parameter("color", meteor_color if is_meteor else star_color)
material.set_shader_parameter("brightness", meteor_brightness if is_meteor else star_brightness)
mesh_instance.material_override = material
mesh_instance.position = position
add_child(mesh_instance)
mesh_instance.look_at(position + direction, Vector3.UP)
mesh_instance.rotate_object_local(Vector3.RIGHT, PI / 2.0)
return mesh_instance
func _update_active_stars(delta: float) -> void:
var stars_to_remove: Array[int] = []
for i in _active_stars.size():
var star: Dictionary = _active_stars[i]
star["lifetime"] += delta
var progress: float = star["lifetime"] / star["max_lifetime"]
if progress >= 1.0:
stars_to_remove.append(i)
continue
var mesh: MeshInstance3D = star["mesh"]
if is_instance_valid(mesh):
star["position"] += star["direction"] * star["speed"] * delta
mesh.position = star["position"]
var fade := 1.0 - smoothstep(0.5, 1.0, progress)
var material := mesh.material_override as ShaderMaterial
if material:
material.set_shader_parameter("fade", fade)
material.set_shader_parameter("trail_progress", minf(progress * 3.0, 1.0))
for i in range(stars_to_remove.size() - 1, -1, -1):
var idx: int = stars_to_remove[i]
var star: Dictionary = _active_stars[idx]
var mesh: MeshInstance3D = star["mesh"]
if is_instance_valid(mesh):
mesh.queue_free()
_active_stars.remove_at(idx)
func set_night_mode(is_night: bool) -> void:
_is_night = is_night
if not is_night:
_clear_all_stars()
func _clear_all_stars() -> void:
for star in _active_stars:
var mesh: MeshInstance3D = star["mesh"]
if is_instance_valid(mesh):
mesh.queue_free()
_active_stars.clear()
func start_meteor_shower(duration: float = -1.0) -> void:
if duration > 0:
meteor_shower_duration = duration
meteor_shower_active = true
_meteor_shower_elapsed = 0.0
_meteor_spawn_accumulator = 0.0
meteor_shower_started.emit()
if meteor_shower_audio_event != "":
pass
func stop_meteor_shower() -> void:
meteor_shower_active = false
_meteor_shower_elapsed = 0.0
meteor_shower_ended.emit()
func trigger_shooting_star() -> void:
if _is_night:
_spawn_shooting_star()
func set_shooting_star_appearance(color: Color, brightness: float, length: float) -> void:
star_color = color
star_brightness = brightness
trail_length = length
func set_meteor_appearance(color: Color, brightness: float, length: float) -> void:
meteor_color = color
meteor_brightness = brightness
meteor_trail_length = length