@tool class_name DynamicSkyRoot extends Node3D signal time_of_day_changed(time: float) signal weather_changed(state: WeatherProfile.WeatherState) signal preset_changed(preset: SkyPreset) @export_group("Controllers") @export var preset_library: SkyboxPresetLibrary @export var day_night_controller: DayNightController @export var weather_controller: WeatherController @export var cloud_system: CloudSystem2D @export var post_process_controller: PostProcessController @export var vfx_controller: VFXController @export var shadow_proxy_system: ShadowProxySystem @export var weather_vfx: WeatherVFX @export_group("Environment") @export var world_environment: WorldEnvironment @export var sun_light: DirectionalLight3D @export var moon_light: DirectionalLight3D @export_group("Global Settings") @export var auto_create_controllers: bool = true @export var deterministic_seed: int = 0: set(value): deterministic_seed = value _apply_seed() @export_group("Debug") @export var show_debug_panel: bool = false @export var debug_time_override: float = -1.0 var _current_config: SkyConfig var preset_name: String = "" var _updating_preset_name: bool = false var _debug_panel: SkyDebugPanel var _debug_canvas_layer: CanvasLayer func _ready() -> void: if auto_create_controllers: _ensure_controllers_exist() _connect_signals() _apply_seed() _ensure_post_process_environment() _load_initial_preset() notify_property_list_changed() _sync_all_systems() func _process(delta: float) -> void: _sync_all_systems() if show_debug_panel: _update_debug_info() elif _debug_panel != null: _debug_panel.visible = false func _ensure_controllers_exist() -> void: if preset_library == null: preset_library = SkyboxPresetLibrary.new() preset_library.name = "PresetLibrary" add_child(preset_library) if day_night_controller == null: day_night_controller = DayNightController.new() day_night_controller.name = "DayNightController" add_child(day_night_controller) if weather_controller == null: weather_controller = WeatherController.new() weather_controller.name = "WeatherController" add_child(weather_controller) if cloud_system == null: cloud_system = CloudSystem2D.new() cloud_system.name = "CloudSystem" add_child(cloud_system) if post_process_controller == null: post_process_controller = PostProcessController.new() post_process_controller.name = "PostProcessController" add_child(post_process_controller) if vfx_controller == null: vfx_controller = VFXController.new() vfx_controller.name = "VFXController" add_child(vfx_controller) if shadow_proxy_system == null: shadow_proxy_system = ShadowProxySystem.new() shadow_proxy_system.name = "ShadowProxySystem" add_child(shadow_proxy_system) if weather_vfx == null: weather_vfx = WeatherVFX.new() weather_vfx.name = "WeatherVFX" add_child(weather_vfx) if world_environment == null: _create_default_environment() if sun_light == null: _create_sun_light() if moon_light == null: _create_moon_light() func _create_default_environment() -> void: world_environment = WorldEnvironment.new() world_environment.name = "WorldEnvironment" world_environment.environment = Environment.new() world_environment.environment.background_mode = Environment.BG_SKY world_environment.environment.sky = Sky.new() add_child(world_environment) if post_process_controller: post_process_controller.target_environment = world_environment.environment func _create_sun_light() -> void: sun_light = DirectionalLight3D.new() sun_light.name = "SunLight" sun_light.light_color = Color(1.0, 0.95, 0.85) sun_light.light_energy = 1.0 sun_light.shadow_enabled = true add_child(sun_light) func _create_moon_light() -> void: moon_light = DirectionalLight3D.new() moon_light.name = "MoonLight" moon_light.light_color = Color(0.7, 0.8, 1.0) moon_light.light_energy = 0.3 moon_light.shadow_enabled = false add_child(moon_light) func _connect_signals() -> void: if day_night_controller: day_night_controller.time_changed.connect(_on_time_changed) if weather_controller: weather_controller.weather_state_changed.connect(_on_weather_state_changed) if preset_library: preset_library.preset_changed.connect(_on_preset_changed) func _on_time_changed(time: float) -> void: time_of_day_changed.emit(time) if vfx_controller: vfx_controller.set_night_mode(day_night_controller.is_nighttime()) func _on_weather_state_changed(old_state: WeatherProfile.WeatherState, new_state: WeatherProfile.WeatherState) -> void: weather_changed.emit(new_state) func _on_preset_changed(preset: SkyPreset) -> void: preset_changed.emit(preset) if preset.config: _current_config = preset.config _sync_preset_name() _apply_preset_assets(preset) func _apply_seed() -> void: if deterministic_seed != 0: seed(deterministic_seed) func _sync_all_systems() -> void: if day_night_controller == null: return var time := day_night_controller.time_of_day if debug_time_override >= 0: time = debug_time_override _sync_sun_moon() _sync_clouds() _sync_post_process() _sync_shadows() _sync_weather_vfx() func _sync_sun_moon() -> void: if day_night_controller == null: return var sun_dir := day_night_controller.get_sun_direction() var moon_dir := day_night_controller.get_moon_direction() var sun_color: Color var sun_energy: float var moon_color: Color var moon_energy: float var ambient_color: Color var ambient_energy: float if _current_config: sun_color = _current_config.sun_color sun_energy = _current_config.sun_intensity moon_color = _current_config.moon_color moon_energy = _current_config.moon_intensity ambient_color = _current_config.ambient_color ambient_energy = _current_config.ambient_intensity else: sun_color = day_night_controller.get_sun_color() sun_energy = day_night_controller.get_sun_intensity() moon_color = day_night_controller.get_moon_color() moon_energy = day_night_controller.get_moon_intensity() ambient_color = day_night_controller.get_ambient_color() ambient_energy = day_night_controller.get_ambient_intensity() if weather_controller: var wp := weather_controller.get_current_profile() if wp: ambient_color *= wp.ambient_color_multiplier ambient_energy *= wp.ambient_intensity_multiplier if sun_light: sun_light.look_at(global_position - sun_dir, Vector3.UP) sun_light.light_color = sun_color sun_light.light_energy = sun_energy sun_light.visible = sun_energy > 0.01 if moon_light: moon_light.look_at(global_position - moon_dir, Vector3.UP) moon_light.light_color = moon_color moon_light.light_energy = moon_energy moon_light.visible = moon_energy > 0.01 if world_environment and world_environment.environment: world_environment.environment.ambient_light_color = ambient_color world_environment.environment.ambient_light_energy = ambient_energy func _sync_clouds() -> void: if cloud_system == null: return if day_night_controller: var sun_color: Color var sun_intensity: float var moon_color: Color var moon_intensity: float if _current_config: sun_color = _current_config.sun_color sun_intensity = _current_config.sun_intensity moon_color = _current_config.moon_color moon_intensity = _current_config.moon_intensity else: sun_color = day_night_controller.get_sun_color() sun_intensity = day_night_controller.get_sun_intensity() moon_color = day_night_controller.get_moon_color() moon_intensity = day_night_controller.get_moon_intensity() cloud_system.set_sun_lighting( day_night_controller.get_sun_direction(), sun_color, sun_intensity ) cloud_system.set_moon_lighting( day_night_controller.get_moon_direction(), moon_color, moon_intensity ) if weather_controller: cloud_system.global_coverage = weather_controller.get_cloud_coverage() elif _current_config: cloud_system.global_coverage = _current_config.cloud_coverage if weather_controller: cloud_system.set_global_wind( weather_controller.get_wind_direction(), weather_controller.get_wind_speed() ) func _sync_post_process() -> void: if post_process_controller == null or day_night_controller == null: return var sun_dir := day_night_controller.get_sun_direction() if _current_config: post_process_controller.set_sun_glow( sun_dir, _current_config.sun_color, _current_config.sun_glow_intensity, _current_config.sun_glow_size ) post_process_controller.set_fog_settings( _current_config.fog_color, _current_config.fog_density ) post_process_controller.set_dawn_dusk_blend(0.0) post_process_controller.set_stars_visibility( _current_config.stars_visibility, _current_config.stars_tint ) post_process_controller.set_sky_colors( _current_config.sky_top_color, _current_config.sky_horizon_color, _current_config.sky_bottom_color ) post_process_controller.gradient_intensity = _current_config.gradient_intensity post_process_controller.apply_config(_current_config) else: var sun_color := day_night_controller.get_sun_color() post_process_controller.set_sun_glow( sun_dir, sun_color, day_night_controller.get_sun_intensity() * 0.5, 0.2 ) var fog_color := day_night_controller.get_fog_color() var fog_density := day_night_controller.get_fog_density() if weather_controller: var wp := weather_controller.get_current_profile() if wp: fog_color *= wp.fog_color_multiplier fog_density *= wp.fog_density_multiplier post_process_controller.set_fog_settings(fog_color, fog_density) var dawn_dusk_blend := 0.0 if day_night_controller.is_dawn() or day_night_controller.is_dusk(): dawn_dusk_blend = 1.0 post_process_controller.set_dawn_dusk_blend(dawn_dusk_blend) post_process_controller.set_stars_visibility( day_night_controller.get_stars_visibility() ) post_process_controller.set_sky_colors( day_night_controller.get_sky_top_color(), day_night_controller.get_sky_horizon_color(), day_night_controller.get_sky_bottom_color() ) func _apply_preset_assets(preset: SkyPreset) -> void: if preset == null: return if post_process_controller: post_process_controller.skybox_texture = preset.skybox_texture if preset.post_process_profile: post_process_controller.profile = preset.post_process_profile if cloud_system and preset.cloud_layer_configs.size() > 0: cloud_system.layer_configs = preset.cloud_layer_configs cloud_system.rebuild_layers() func _resolve_preset_library() -> SkyboxPresetLibrary: if preset_library != null: return preset_library if has_node(NodePath("PresetLibrary")): return get_node(NodePath("PresetLibrary")) as SkyboxPresetLibrary return null func _get_property_list() -> Array: var properties := [] var names: PackedStringArray = [] var lib := _resolve_preset_library() if lib: names = lib.get_preset_names() var hint_string := "" if names.size() > 0: hint_string = ",".join(names) properties.append({ "name": "preset_name", "type": TYPE_STRING, "usage": PROPERTY_USAGE_EDITOR, "hint": PROPERTY_HINT_ENUM, "hint_string": hint_string, }) return properties func _set(property: StringName, value: Variant) -> bool: if property == "preset_name": if preset_name == value: return true preset_name = value if _updating_preset_name: return true var lib := _resolve_preset_library() if lib and String(preset_name) != "": lib.apply_preset(String(preset_name)) var p := lib.get_current_preset() if p: _on_preset_changed(p) _sync_all_systems() return true return false func _get(property: StringName) -> Variant: if property == "preset_name": return preset_name return null func _sync_preset_name() -> void: if preset_library == null: return var current := preset_library.get_current_preset() if current == null: return _updating_preset_name = true preset_name = current.preset_name _updating_preset_name = false func _ensure_post_process_environment() -> void: if post_process_controller and world_environment and world_environment.environment: if post_process_controller.target_environment == null: post_process_controller.target_environment = world_environment.environment post_process_controller._setup_environment() func _load_initial_preset() -> void: if preset_library == null: return var current := preset_library.get_current_preset() if current == null: return _sync_preset_name() if current.config: _current_config = current.config _apply_preset_assets(current) func _sync_shadows() -> void: if shadow_proxy_system == null: return if weather_controller: shadow_proxy_system.set_cloud_coverage(weather_controller.get_cloud_coverage()) if cloud_system: shadow_proxy_system.sync_with_cloud_system(cloud_system) func _sync_weather_vfx() -> void: if weather_vfx == null or weather_controller == null: return weather_vfx.set_rain_intensity(weather_controller.get_rain_intensity()) weather_vfx.set_snow_intensity(weather_controller.get_snow_intensity()) func _update_debug_info() -> void: if Engine.is_editor_hint(): return if _debug_panel == null: _create_debug_panel() _debug_panel.visible = true func _create_debug_panel() -> void: _debug_canvas_layer = CanvasLayer.new() _debug_canvas_layer.name = "DebugCanvasLayer" _debug_canvas_layer.layer = 100 add_child(_debug_canvas_layer) _debug_panel = SkyDebugPanel.new() _debug_panel.name = "SkyDebugPanel" _debug_panel.dynamic_sky = self _debug_panel.anchor_left = 0.0 _debug_panel.anchor_top = 0.0 _debug_panel.anchor_right = 0.0 _debug_panel.anchor_bottom = 0.0 _debug_panel.offset_left = 10.0 _debug_panel.offset_top = 10.0 _debug_canvas_layer.add_child(_debug_panel) func set_time_of_day(time: float) -> void: _current_config = null if day_night_controller: day_night_controller.time_of_day = time func set_weather(state: WeatherProfile.WeatherState, transition_time: float = -1.0) -> void: _current_config = null if weather_controller: weather_controller.set_weather(state, transition_time) func apply_preset(preset_name: String) -> void: if preset_library: preset_library.apply_preset(preset_name) func pause_time() -> void: if day_night_controller: day_night_controller.paused = true func resume_time() -> void: if day_night_controller: day_night_controller.paused = false func trigger_meteor_shower(duration: float = 60.0) -> void: if vfx_controller: vfx_controller.start_meteor_shower(duration) func get_time_of_day() -> float: if day_night_controller: return day_night_controller.time_of_day return 0.0 func get_current_weather() -> WeatherProfile.WeatherState: if weather_controller: return weather_controller.current_weather return WeatherProfile.WeatherState.CLEAR func is_daytime() -> bool: if day_night_controller: return day_night_controller.is_daytime() return true func is_nighttime() -> bool: if day_night_controller: return day_night_controller.is_nighttime() return false