@tool class_name DayNightController extends Node signal time_changed(time_of_day: float) signal hour_changed(hour: int) signal day_changed(day: int) signal sunrise_started() signal sunset_started() signal night_started() signal noon_reached() const HOURS_PER_DAY := 24.0 const SUNRISE_START := 0.2 # ~5 AM const SUNRISE_END := 0.3 # ~7 AM const SUNSET_START := 0.75 # ~6 PM const SUNSET_END := 0.85 # ~8 PM const NOON := 0.5 @export_group("Time Settings") @export var time_of_day: float = 0.25: set(value): var old_hour := get_current_hour() time_of_day = wrapf(value, 0.0, 1.0) var new_hour := get_current_hour() if old_hour != new_hour: hour_changed.emit(new_hour) time_changed.emit(time_of_day) _check_time_events(old_hour, new_hour) @export var day_length_minutes: float = 30.0: set(value): day_length_minutes = clampf(value, 1.0, 1440.0) @export var paused: bool = false @export var time_scale: float = 1.0 @export var current_day: int = 1 @export_group("Sun Arc") @export var sun_arc_tilt: float = 23.5 @export var sun_arc_offset: float = 0.0 @export_group("Moon Arc") @export var moon_arc_tilt: float = 15.0 @export var moon_arc_offset: float = 180.0 @export var moon_phase: float = 0.0 @export_group("Sun Curves") @export var sun_color_curve: Gradient @export var sun_intensity_curve: Curve @export_group("Moon Curves") @export var moon_color_curve: Gradient @export var moon_intensity_curve: Curve @export_group("Ambient Curves") @export var ambient_color_curve: Gradient @export var ambient_intensity_curve: Curve @export_group("Fog Curves") @export var fog_color_curve: Gradient @export var fog_density_curve: Curve @export_group("Sky Gradient Curves") @export var sky_top_color_curve: Gradient @export var sky_horizon_color_curve: Gradient @export var sky_bottom_color_curve: Gradient @export_group("Stars") @export var stars_visibility_curve: Curve var _last_hour: int = -1 var _sunrise_emitted := false var _sunset_emitted := false var _night_emitted := false var _noon_emitted := false func _ready() -> void: _initialize_default_curves() _last_hour = get_current_hour() func _process(delta: float) -> void: if paused: return var time_increment := delta / (day_length_minutes * 60.0) * time_scale var old_time := time_of_day time_of_day += time_increment if time_of_day < old_time: current_day += 1 day_changed.emit(current_day) _reset_time_events() func _initialize_default_curves() -> void: if sun_color_curve == null: sun_color_curve = Gradient.new() sun_color_curve.set_color(0, Color(1.0, 0.6, 0.4)) # Dawn sun_color_curve.add_point(0.25, Color(1.0, 0.95, 0.85)) # Morning sun_color_curve.add_point(0.5, Color(1.0, 1.0, 0.95)) # Noon sun_color_curve.add_point(0.75, Color(1.0, 0.7, 0.4)) # Sunset sun_color_curve.set_color(1, Color(1.0, 0.5, 0.3)) # Dusk if sun_intensity_curve == null: sun_intensity_curve = Curve.new() sun_intensity_curve.add_point(Vector2(0.0, 0.0)) sun_intensity_curve.add_point(Vector2(0.2, 0.0)) sun_intensity_curve.add_point(Vector2(0.3, 0.8)) sun_intensity_curve.add_point(Vector2(0.5, 1.0)) sun_intensity_curve.add_point(Vector2(0.7, 0.8)) sun_intensity_curve.add_point(Vector2(0.85, 0.0)) sun_intensity_curve.add_point(Vector2(1.0, 0.0)) if moon_color_curve == null: moon_color_curve = Gradient.new() moon_color_curve.set_color(0, Color(0.7, 0.8, 1.0)) moon_color_curve.set_color(1, Color(0.7, 0.8, 1.0)) if moon_intensity_curve == null: moon_intensity_curve = Curve.new() moon_intensity_curve.add_point(Vector2(0.0, 0.4)) moon_intensity_curve.add_point(Vector2(0.15, 0.3)) moon_intensity_curve.add_point(Vector2(0.22, 0.0)) moon_intensity_curve.add_point(Vector2(0.85, 0.0)) moon_intensity_curve.add_point(Vector2(0.92, 0.3)) moon_intensity_curve.add_point(Vector2(1.0, 0.4)) if ambient_color_curve == null: ambient_color_curve = Gradient.new() ambient_color_curve.set_color(0, Color(0.15, 0.18, 0.3)) # Night ambient_color_curve.add_point(0.25, Color(0.5, 0.55, 0.65)) # Morning ambient_color_curve.add_point(0.5, Color(0.6, 0.65, 0.75)) # Noon ambient_color_curve.add_point(0.75, Color(0.55, 0.5, 0.55)) # Evening ambient_color_curve.set_color(1, Color(0.15, 0.18, 0.3)) # Night if ambient_intensity_curve == null: ambient_intensity_curve = Curve.new() ambient_intensity_curve.add_point(Vector2(0.0, 0.05)) ambient_intensity_curve.add_point(Vector2(0.2, 0.1)) ambient_intensity_curve.add_point(Vector2(0.3, 0.5)) ambient_intensity_curve.add_point(Vector2(0.5, 0.6)) ambient_intensity_curve.add_point(Vector2(0.7, 0.5)) ambient_intensity_curve.add_point(Vector2(0.85, 0.1)) ambient_intensity_curve.add_point(Vector2(1.0, 0.05)) if fog_color_curve == null: fog_color_curve = Gradient.new() fog_color_curve.set_color(0, Color(0.1, 0.12, 0.2)) fog_color_curve.add_point(0.25, Color(0.6, 0.65, 0.75)) fog_color_curve.add_point(0.5, Color(0.7, 0.75, 0.85)) fog_color_curve.add_point(0.75, Color(0.7, 0.55, 0.5)) fog_color_curve.set_color(1, Color(0.1, 0.12, 0.2)) if fog_density_curve == null: fog_density_curve = Curve.new() fog_density_curve.add_point(Vector2(0.0, 0.02)) fog_density_curve.add_point(Vector2(0.25, 0.015)) fog_density_curve.add_point(Vector2(0.5, 0.01)) fog_density_curve.add_point(Vector2(0.75, 0.015)) fog_density_curve.add_point(Vector2(1.0, 0.02)) if sky_top_color_curve == null: sky_top_color_curve = Gradient.new() sky_top_color_curve.set_color(0, Color(0.01, 0.01, 0.04)) # Midnight sky_top_color_curve.add_point(0.2, Color(0.03, 0.05, 0.1)) # Pre-dawn sky_top_color_curve.add_point(0.3, Color(0.18, 0.35, 0.8)) # Morning sky_top_color_curve.add_point(0.5, Color(0.15, 0.35, 0.82)) # Noon sky_top_color_curve.add_point(0.75, Color(0.12, 0.15, 0.4)) # Sunset sky_top_color_curve.add_point(0.85, Color(0.02, 0.02, 0.06)) # Dusk end sky_top_color_curve.set_color(1, Color(0.01, 0.01, 0.04)) # Midnight if sky_horizon_color_curve == null: sky_horizon_color_curve = Gradient.new() sky_horizon_color_curve.set_color(0, Color(0.03, 0.04, 0.08)) # Midnight sky_horizon_color_curve.add_point(0.2, Color(0.3, 0.22, 0.28)) # Pre-dawn sky_horizon_color_curve.add_point(0.25, Color(0.85, 0.55, 0.45))# Dawn sky_horizon_color_curve.add_point(0.35, Color(0.45, 0.6, 0.85))# Morning sky_horizon_color_curve.add_point(0.5, Color(0.5, 0.65, 0.88)) # Noon sky_horizon_color_curve.add_point(0.75, Color(0.95, 0.45, 0.25))# Sunset sky_horizon_color_curve.add_point(0.85, Color(0.06, 0.06, 0.1))# Dusk end sky_horizon_color_curve.set_color(1, Color(0.03, 0.04, 0.08)) # Midnight if sky_bottom_color_curve == null: sky_bottom_color_curve = Gradient.new() sky_bottom_color_curve.set_color(0, Color(0.02, 0.02, 0.05)) # Midnight sky_bottom_color_curve.add_point(0.2, Color(0.12, 0.1, 0.15)) # Pre-dawn sky_bottom_color_curve.add_point(0.3, Color(0.35, 0.45, 0.55)) # Morning sky_bottom_color_curve.add_point(0.5, Color(0.35, 0.45, 0.55)) # Noon sky_bottom_color_curve.add_point(0.75, Color(0.3, 0.2, 0.2)) # Sunset sky_bottom_color_curve.add_point(0.85, Color(0.03, 0.03, 0.06))# Dusk end sky_bottom_color_curve.set_color(1, Color(0.02, 0.02, 0.05)) # Midnight if stars_visibility_curve == null: stars_visibility_curve = Curve.new() stars_visibility_curve.add_point(Vector2(0.0, 1.0)) stars_visibility_curve.add_point(Vector2(0.2, 0.8)) stars_visibility_curve.add_point(Vector2(0.3, 0.0)) stars_visibility_curve.add_point(Vector2(0.7, 0.0)) stars_visibility_curve.add_point(Vector2(0.85, 0.8)) stars_visibility_curve.add_point(Vector2(1.0, 1.0)) func _check_time_events(old_hour: int, new_hour: int) -> void: if time_of_day >= SUNRISE_START and time_of_day < SUNRISE_END and not _sunrise_emitted: _sunrise_emitted = true sunrise_started.emit() if time_of_day >= SUNSET_START and time_of_day < SUNSET_END and not _sunset_emitted: _sunset_emitted = true sunset_started.emit() if time_of_day >= SUNSET_END and not _night_emitted: _night_emitted = true night_started.emit() if time_of_day >= NOON - 0.01 and time_of_day < NOON + 0.01 and not _noon_emitted: _noon_emitted = true noon_reached.emit() func _reset_time_events() -> void: _sunrise_emitted = false _sunset_emitted = false _night_emitted = false _noon_emitted = false func get_current_hour() -> int: return int(time_of_day * HOURS_PER_DAY) func get_current_hour_float() -> float: return time_of_day * HOURS_PER_DAY func set_time_from_hours(hours: float) -> void: time_of_day = fmod(hours, HOURS_PER_DAY) / HOURS_PER_DAY func get_sun_direction() -> Vector3: var angle := (time_of_day - 0.25) * TAU var tilt_rad := deg_to_rad(sun_arc_tilt) var offset_rad := deg_to_rad(sun_arc_offset) var direction := Vector3( cos(angle + offset_rad), sin(angle), sin(angle) * sin(tilt_rad) ).normalized() return direction func get_moon_direction() -> Vector3: var angle := (time_of_day + 0.25) * TAU var tilt_rad := deg_to_rad(moon_arc_tilt) var offset_rad := deg_to_rad(moon_arc_offset) var direction := Vector3( cos(angle + offset_rad), sin(angle), sin(angle) * sin(tilt_rad) ).normalized() return direction func get_sun_color() -> Color: if sun_color_curve: return sun_color_curve.sample(time_of_day) return Color.WHITE func get_sun_intensity() -> float: if sun_intensity_curve: return sun_intensity_curve.sample(time_of_day) return 1.0 func get_moon_color() -> Color: if moon_color_curve: return moon_color_curve.sample(time_of_day) return Color(0.7, 0.8, 1.0) func get_moon_intensity() -> float: var base_intensity := 1.0 if moon_intensity_curve: base_intensity = moon_intensity_curve.sample(time_of_day) var phase_factor := 0.5 + 0.5 * cos(moon_phase * TAU) return base_intensity * phase_factor func get_ambient_color() -> Color: if ambient_color_curve: return ambient_color_curve.sample(time_of_day) return Color(0.5, 0.55, 0.65) func get_ambient_intensity() -> float: if ambient_intensity_curve: return ambient_intensity_curve.sample(time_of_day) return 0.5 func get_fog_color() -> Color: if fog_color_curve: return fog_color_curve.sample(time_of_day) return Color(0.7, 0.75, 0.85) func get_fog_density() -> float: if fog_density_curve: return fog_density_curve.sample(time_of_day) return 0.01 func get_stars_visibility() -> float: if stars_visibility_curve: return stars_visibility_curve.sample(time_of_day) return 0.0 func get_sky_top_color() -> Color: if sky_top_color_curve: return sky_top_color_curve.sample(time_of_day) return Color(0.2, 0.4, 0.8) func get_sky_horizon_color() -> Color: if sky_horizon_color_curve: return sky_horizon_color_curve.sample(time_of_day) return Color(0.6, 0.7, 0.9) func get_sky_bottom_color() -> Color: if sky_bottom_color_curve: return sky_bottom_color_curve.sample(time_of_day) return Color(0.4, 0.5, 0.6) func is_daytime() -> bool: return time_of_day >= SUNRISE_END and time_of_day < SUNSET_START func is_nighttime() -> bool: return time_of_day < SUNRISE_START or time_of_day >= SUNSET_END func is_dawn() -> bool: return time_of_day >= SUNRISE_START and time_of_day < SUNRISE_END func is_dusk() -> bool: return time_of_day >= SUNSET_START and time_of_day < SUNSET_END func get_time_period() -> String: if is_dawn(): return "dawn" elif is_dusk(): return "dusk" elif is_daytime(): return "day" else: return "night"