Start dynamic sky

This commit is contained in:
2026-02-03 11:26:48 +01:00
parent dd1ba0fa9a
commit 5843e8a07a
49 changed files with 4448 additions and 0 deletions

283
dynamic-sky/README.md Normal file
View File

@@ -0,0 +1,283 @@
# Dynamic Sky System for Godot 4.6
A stylized dynamic sky system with day/night cycles, weather, clouds, and VFX.
## Setup Instructions
### Step 1: Open Project in Godot
1. Open your Godot 4.6 project
2. Wait for the editor to import all scripts (check the bottom progress bar)
3. If you see script errors, click **Project > Reload Current Project**
### Step 2: Add to Your Scene
1. Open the scene where you want the sky system
2. Drag `dynamic-sky/dynamic_sky.tscn` into your scene tree
3. Or right-click in the scene tree → **Instance Child Scene** → select `dynamic_sky.tscn`
### Step 3: Configure the Environment
The DynamicSky node auto-creates required children. To link an existing environment:
1. Select the **DynamicSky** node
2. In the Inspector, under **Environment**:
- Assign your existing `WorldEnvironment` node (or leave empty to auto-create)
- Assign existing `DirectionalLight3D` for sun/moon (or leave empty)
### Step 4: Generate Cloud Textures
Clouds need textures. Use the procedural generator:
```gdscript
# In a tool script or _ready():
var cloud_tex = ProceduralCloudTexture.create_default_cloud_texture(1)
$DynamicSky/CloudSystem.layer_configs[0].texture = cloud_tex
```
Or assign existing cloud textures to each `CloudLayerConfig` in the Inspector.
### Step 5: Configure Shadow Texture
```gdscript
var shadow_tex = ProceduralCloudTexture.create_shadow_texture()
$DynamicSky/ShadowProxySystem.shadow_texture = shadow_tex
```
## Testing the System
### Test 1: Basic Scene Test
1. Create a new 3D scene with a `Camera3D` and a `MeshInstance3D` (e.g., plane for ground)
2. Instance `dynamic_sky.tscn`
3. Run the scene (F5)
4. You should see a gradient sky with sun light
### Test 2: Time of Day
Add this script to test time changes:
```gdscript
extends Node
@onready var sky = $DynamicSky
func _ready():
# Start at morning
sky.set_time_of_day(0.25)
func _process(delta):
# Press keys to change time
if Input.is_action_just_pressed("ui_right"):
sky.set_time_of_day(sky.get_time_of_day() + 0.05)
if Input.is_action_just_pressed("ui_left"):
sky.set_time_of_day(sky.get_time_of_day() - 0.05)
# Print current time
if Input.is_action_just_pressed("ui_accept"):
print("Time: ", sky.get_time_of_day())
```
### Test 3: Weather System
```gdscript
func _input(event):
if event.is_action_pressed("ui_up"):
$DynamicSky.set_weather(WeatherProfile.WeatherState.RAIN)
if event.is_action_pressed("ui_down"):
$DynamicSky.set_weather(WeatherProfile.WeatherState.CLEAR)
```
### Test 4: Debug Panel
1. Add a `CanvasLayer` to your scene
2. Add a `Control` node as child
3. Attach the `sky_debug_panel.gd` script
4. In Inspector, assign the `DynamicSky` node to the `dynamic_sky` property
5. Run and use the UI controls
### Test 5: Meteor Shower
```gdscript
func _ready():
# Ensure it's night for visibility
$DynamicSky.set_time_of_day(0.0)
# Wait a moment then trigger
await get_tree().create_timer(1.0).timeout
$DynamicSky.trigger_meteor_shower(30.0)
```
### Test 6: Kill Volumes
1. Add an `Area3D` to your scene
2. Attach `weather_kill_volume.gd` script
3. Configure the shape and size in Inspector
4. Precipitation will be blocked inside this volume
## Complete Test Scene Example
Create a new scene with this structure:
```
TestScene (Node3D)
├── DynamicSky (instance of dynamic_sky.tscn)
├── Camera3D
├── Ground (MeshInstance3D with PlaneMesh)
├── CanvasLayer
│ └── DebugPanel (Control with sky_debug_panel.gd)
└── TestScript (Node with test script below)
```
Test script:
```gdscript
extends Node
@onready var sky = $"../DynamicSky"
func _ready():
# Generate cloud textures
_setup_clouds()
# Start at mid-morning
sky.set_time_of_day(0.35)
print("Dynamic Sky Test Ready!")
print("Arrow keys: Change time")
print("1-6: Change weather")
print("M: Meteor shower")
print("P: Pause/Resume time")
func _setup_clouds():
var cloud_system = sky.cloud_system
if cloud_system:
for i in cloud_system.layer_configs.size():
var tex = ProceduralCloudTexture.create_default_cloud_texture(i)
cloud_system.layer_configs[i].texture = tex
cloud_system.rebuild_layers()
var shadow_system = sky.shadow_proxy_system
if shadow_system:
shadow_system.shadow_texture = ProceduralCloudTexture.create_shadow_texture()
func _input(event):
if event is InputEventKey and event.pressed:
match event.keycode:
KEY_RIGHT:
sky.set_time_of_day(wrapf(sky.get_time_of_day() + 0.02, 0, 1))
KEY_LEFT:
sky.set_time_of_day(wrapf(sky.get_time_of_day() - 0.02, 0, 1))
KEY_1:
sky.set_weather(WeatherProfile.WeatherState.CLEAR)
KEY_2:
sky.set_weather(WeatherProfile.WeatherState.CLOUDY)
KEY_3:
sky.set_weather(WeatherProfile.WeatherState.OVERCAST)
KEY_4:
sky.set_weather(WeatherProfile.WeatherState.RAIN)
KEY_5:
sky.set_weather(WeatherProfile.WeatherState.STORM)
KEY_6:
sky.set_weather(WeatherProfile.WeatherState.SNOW)
KEY_M:
sky.trigger_meteor_shower(30.0)
KEY_P:
if sky.day_night_controller.paused:
sky.resume_time()
else:
sky.pause_time()
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Script errors on load | Reload project: Project → Reload Current Project |
| No sky visible | Ensure camera is looking up; check WorldEnvironment exists |
| Clouds not visible | Assign textures to CloudLayerConfig or generate procedurally |
| Shadows not visible | Assign shadow_texture to ShadowProxySystem |
| Stars not visible | Set time to night (0.0 or 0.9+); check stars_visibility curve |
| Time not advancing | Check `paused` is false on DayNightController |
| Weather not changing | Check WeatherController has profiles; look at transition progress |
## Features
### Preset Skybox Nodes
- 6 presets: Clear Day, Sunset, Night, Overcast, Storm, Dawn
- Runtime preset switching with smooth transitions
- Non-destructive overrides
### Day/Night Cycle
- Configurable day length (1-1440 minutes)
- Sun/moon arc movement
- Color/intensity curves for sun, moon, ambient, fog
- Star visibility at night
- Time events (sunrise, sunset, noon)
### Weather System
- States: Clear, Cloudy, Overcast, Rain, Storm, Snow
- Smooth transitions between states
- Per-state cloud, wind, and VFX settings
- Random or scripted weather timelines
### 2D Cloudscapes
- 4 independent layers (low, mid, high, wisps)
- Per-layer texture, tiling, opacity, color
- Flow animation with wind influence
- Evolution/morphing over time
- Reactive to sun/moon lighting
### Post-Processing
- Sky gradient shading
- Height and distance fog
- Sun glow halo
- Dawn/dusk enhancement
- Procedural stars
### VFX
- Shooting stars (randomized)
- Meteor showers (event-triggered)
- Configurable appearance
### Cloud Shadows
- Projected shadow overlay
- Syncs with cloud movement
- Blur and opacity controls
### Kill Volumes
- Block precipitation in indoor areas
- Box, sphere, cylinder shapes
- Fade-out margins
## Quick Start
1. Add `dynamic_sky.tscn` to your scene
2. Reference the DynamicSky node in your scripts
3. Use the API:
```gdscript
# Get reference
@onready var sky = $DynamicSky
# Set time (0.0-1.0, where 0.5 = noon)
sky.set_time_of_day(0.5)
# Change weather
sky.set_weather(WeatherProfile.WeatherState.RAIN)
# Apply preset
sky.apply_preset("Sunset")
# Trigger meteor shower
sky.trigger_meteor_shower(60.0)
```
## Debug Panel
Add `SkyDebugPanel` to your UI and assign the DynamicSky reference to see:
- Current time and weather
- Cloud coverage
- Controls for testing
## Resources
- `SkyConfig`: Global sky parameters
- `SkyPreset`: Complete preset configuration
- `WeatherProfile`: Per-weather-state settings
- `CloudLayerConfig`: Per-layer cloud settings
- `PostProcessProfile`: Post-processing parameters
## Performance
- Disable unused cloud layers
- Reduce cloud texture resolution
- Lower VFX update rates
- Disable shadow blur on low-end hardware

180
dynamic-sky/SPEC.md Normal file
View File

@@ -0,0 +1,180 @@
# Stylized Dynamic Sky Specification (Godot 4.6)
## Overview
This document specifies a stylized dynamic sky system for Godot 4.6. It defines the required features, behavior, data structures, and integration points without prescribing implementation code.
## Goals
- Provide a visually stylized, dynamic sky suitable for stylized or semi-realistic games.
- Offer modular components that can be combined or replaced.
- Enable deterministic playback for cutscenes and synchronized multiplayer.
- Support art-direction via presets and editor-friendly controls.
## Non-Goals
- Physically accurate atmospheric scattering.
- Real-world meteorology simulation.
- Automated terrain-aware shadow projections.
## System Architecture
- **DynamicSkyRoot**: Top-level scene controlling time, weather, and global parameters.
- **SkyboxPresetLibrary**: Collection of preset skybox nodes (configured resources/scenes).
- **DayNightController**: Controls sun/moon movement, ambient light, and time-of-day curves.
- **WeatherController**: Drives weather states, transitions, and VFX spawning.
- **CloudSystem2D**: Handles layered 2D cloudscapes, flow, animation, and evolution.
- **PostProcessController**: Applies stylized gradients, fog shaping, and sun glow.
- **VFXController**: Shooting stars and meteor showers.
- **ShadowProxySystem**: Faked cloud shadows projected onto world.
## Feature Specifications
### Preset Skybox Nodes
**Description**: Pre-authored skybox configurations that can be swapped at runtime.
**Requirements**:
- At least 6 presets: Clear Day, Sunset, Night, Overcast, Storm, Dawn.
- Each preset includes:
- Skybox texture/gradient resources.
- Sun and moon color presets.
- Ambient and fog color presets.
- Default cloud layers and post-process settings.
- Presets can be overridden by runtime controllers without losing base values.
- Preset application is non-destructive and reversible.
**Editor UX**:
- Preset picker dropdown.
- “Apply Preset” button and “Revert to Preset” button.
### Day/Night Cycle
**Description**: Stylized, configurable cycle controlling lighting and sky appearance.
**Requirements**:
- Time scale with configurable length (e.g., 10240 minutes per day).
- Supports manual time-of-day override and pause.
- Sun and moon directional motion (arc across sky dome).
- Color and intensity curves:
- Sun color and intensity.
- Moon color and intensity.
- Ambient color and intensity.
- Fog color and density.
- Smooth transitions between time states.
- Optional star visibility curve.
**Data**:
- TimeOfDay (0.01.0 normalized)
- Curves for color/intensity across time.
### Weather System
**Description**: State-based weather controller with transitions and parameters.
**Weather States**:
- Clear
- Cloudy
- Overcast
- Rain
- Storm
- Snow (optional, can be disabled)
**Requirements**:
- State machine with configurable transition durations.
- Each state defines:
- Cloud density, opacity, and coverage.
- Wind speed and direction (affects cloud flow).
- VFX sets (rain, lightning, snow).
- Ambient and fog adjustments.
- Randomized or scripted weather timeline support.
- Events/callbacks on state enter/exit.
### 2D Customizable Cloudscapes
**Description**: Layered 2D clouds rendered on the sky dome or as screen-space quads.
**Requirements**:
- Up to 4 independent layers: low, mid, high, wisps.
- Per-layer settings:
- Texture/atlas selection.
- Tiling and scale.
- Opacity and color tint.
- Parallax depth factor.
- Flow direction and speed.
- Global cloud coverage control.
### Cloud Flow, Animation, and Evolution
**Description**: Dynamic motion and shape changes over time.
**Requirements**:
- Continuous UV flow with wind influence.
- Optional noise-based distortion for stylized movement.
- Evolution parameter to morph coverage and shape (low-frequency drift).
- Time-based variation seed to keep deterministic playback when seeded.
### Cloud Lighting Reacts with Sun and Moon
**Description**: Cloud shading changes based on celestial light direction and intensity.
**Requirements**:
- Light direction taken from sun/moon controller.
- Bright side and shadowed side color control.
- Night-time soft illumination from moon.
- Optional rim glow at dawn/dusk.
### Weather Particle Kill Volumes
**Description**: Volumes that stop precipitation or weather particles in indoor or sheltered spaces.
**Requirements**:
- Axis-aligned and/or custom-shaped kill volumes.
- Particles inside kill volume are disabled or instantly culled.
- Priority/stacking rules when volumes overlap.
- Optional fade-out margin to avoid hard cutoffs.
### Stylized Post Process for Fog and Sun Gradients
**Description**: Screen-space effects to enhance gradients and fog stylization.
**Requirements**:
- Adjustable sky gradient curve blending.
- Fog shaping controls (height fog, distance fog).
- Sun glow halo with stylized falloff.
- Dusk/dawn gradient emphasis.
- Option to toggle for performance profiles.
### Shooting Star and Meteor Shower VFX
**Description**: Procedural or timed visual effects visible in the sky.
**Requirements**:
- Occasional shooting stars (randomized frequency).
- Meteor shower events with configurable duration and intensity.
- Trail length, brightness, and color control.
- Optional audio event hook for meteor shower start.
### Faked Cloud Shadows
**Description**: Stylized cloud shadow overlay projected onto the world.
**Requirements**:
- Shadow texture projected in world space or onto a large quad.
- Movement matches cloud flow direction and speed.
- Darkness/opacity controls and color tint.
- Optional blur for softer look.
## Data and Configuration
- **SkyConfig Resource**: Stores defaults for all controllers.
- **Preset Resource**: Encapsulates a SkyConfig plus skybox assets.
- **WeatherProfile**: Per-state settings and VFX parameters.
- **CloudLayerConfig**: Texture/flow/lighting settings per layer.
- **PostProcessProfile**: Gradient, fog, and sun glow parameters.
## Performance Targets
- Target 60 FPS on mid-range hardware.
- Cloud layers should allow selective disabling.
- VFX update rates configurable.
## Debug and Tooling
- On-screen debug panel showing time, weather state, cloud parameters.
- Seed display for deterministic playback.
- Editor gizmos for kill volumes.
## Acceptance Criteria
- All required features are present and configurable in editor.
- Presets can be applied and reverted without data loss.
- Day/night transitions are smooth with no visible jumps.
- Weather transitions alter clouds, lighting, and VFX consistently.
- Cloud lighting visibly reacts to sun/moon changes.
- Kill volumes prevent precipitation in indoor spaces.
- Post process improves gradients and fog stylization.
- Shooting stars and meteor showers visible at night.
- Cloud shadows visibly move across the world.

View File

@@ -0,0 +1,394 @@
[gd_scene format=3 uid="uid://doiojb8xxlp2s"]
[ext_resource type="Script" uid="uid://8r3do6ow5nmn" path="res://dynamic-sky/scripts/dynamic_sky_root.gd" id="1_dynamic_sky"]
[ext_resource type="Script" uid="uid://cb8c6ug44jx4t" path="res://dynamic-sky/scripts/skybox_preset_library.gd" id="2_preset_library"]
[ext_resource type="Script" uid="uid://c48eq2y4ikok8" path="res://dynamic-sky/resources/sky_preset.gd" id="3_c6jpa"]
[ext_resource type="Script" uid="uid://gypnoh5ikwjn" path="res://dynamic-sky/scripts/day_night_controller.gd" id="3_day_night"]
[ext_resource type="Script" uid="uid://beu0xhk0rvpky" path="res://dynamic-sky/resources/cloud_layer_config.gd" id="4_ssi15"]
[ext_resource type="Script" uid="uid://blyx8v2gq8nlw" path="res://dynamic-sky/scripts/weather_controller.gd" id="4_weather"]
[ext_resource type="Script" uid="uid://c3iniqgu2l0ek" path="res://dynamic-sky/resources/sky_config.gd" id="5_2w5ig"]
[ext_resource type="Script" uid="uid://cwplvsarljsf" path="res://dynamic-sky/scripts/cloud_system_2d.gd" id="5_clouds"]
[ext_resource type="Script" uid="uid://cwqg0bg2m2bme" path="res://dynamic-sky/scripts/post_process_controller.gd" id="6_post_process"]
[ext_resource type="Texture2D" uid="uid://crrj260kiyoy" path="res://dynamic-sky/sky_44_2k.png" id="6_ssi15"]
[ext_resource type="Script" uid="uid://cnvj3m5fpsdtj" path="res://dynamic-sky/scripts/vfx_controller.gd" id="7_vfx"]
[ext_resource type="Script" uid="uid://d2srir3wsi045" path="res://dynamic-sky/resources/weather_profile.gd" id="8_cnx2n"]
[ext_resource type="Script" uid="uid://hvs2g6acqryq" path="res://dynamic-sky/scripts/shadow_proxy_system.gd" id="8_shadows"]
[ext_resource type="Script" uid="uid://ctrw2ycud36yd" path="res://dynamic-sky/resources/post_process_profile.gd" id="11_djhca"]
[ext_resource type="Environment" uid="uid://dq64sos14f8hu" path="res://dynamic-sky/environment.tres" id="13_2w5ig"]
[sub_resource type="Resource" id="Resource_c27no"]
script = ExtResource("5_2w5ig")
sun_color = Color(1, 0.95, 0.85, 1)
sun_intensity = 1.2
moon_intensity = 0.0
ambient_color = Color(0.5, 0.6, 0.8, 1)
ambient_intensity = 0.6
fog_color = Color(0.7, 0.8, 0.95, 1)
fog_density = 0.005
sky_top_color = Color(0.2, 0.4, 0.85, 1)
sky_horizon_color = Color(0.6, 0.75, 0.95, 1)
sky_bottom_color = Color(0.5, 0.6, 0.7, 1)
cloud_coverage = 0.2
cloud_opacity = 0.7
sun_glow_intensity = 0.3
[sub_resource type="Resource" id="Resource_ssi15"]
script = ExtResource("11_djhca")
metadata/_custom_type_script = "uid://ctrw2ycud36yd"
[sub_resource type="Resource" id="Resource_ajt3x"]
script = ExtResource("3_c6jpa")
preset_name = "Clear Day"
preset_type = 0
config = SubResource("Resource_c27no")
skybox_texture = ExtResource("6_ssi15")
post_process_profile = SubResource("Resource_ssi15")
[sub_resource type="Resource" id="Resource_cp2fs"]
script = ExtResource("5_2w5ig")
sun_color = Color(1, 0.5, 0.2, 1)
sun_intensity = 0.8
moon_intensity = 0.0
ambient_color = Color(0.8, 0.5, 0.4, 1)
ambient_intensity = 0.4
fog_color = Color(0.9, 0.6, 0.4, 1)
fog_density = 0.015
sky_top_color = Color(0.3, 0.2, 0.5, 1)
sky_horizon_color = Color(1, 0.5, 0.3, 1)
sky_bottom_color = Color(0.9, 0.4, 0.2, 1)
stars_visibility = 0.1
cloud_opacity = 0.9
cloud_color = Color(1, 0.7, 0.5, 1)
cloud_shadow_color = Color(0.4, 0.2, 0.3, 1)
sun_glow_intensity = 0.8
sun_glow_size = 0.4
[sub_resource type="Resource" id="Resource_sptlx"]
script = ExtResource("3_c6jpa")
preset_name = "Sunset"
preset_type = 1
config = SubResource("Resource_cp2fs")
[sub_resource type="Resource" id="Resource_bmqu2"]
script = ExtResource("5_2w5ig")
sun_intensity = 0.0
moon_color = Color(0.7, 0.8, 1, 1)
moon_intensity = 0.4
ambient_color = Color(0.1, 0.15, 0.25, 1)
ambient_intensity = 0.2
fog_color = Color(0.1, 0.12, 0.2, 1)
fog_density = 0.02
sky_top_color = Color(0.02, 0.03, 0.08, 1)
sky_horizon_color = Color(0.1, 0.12, 0.2, 1)
sky_bottom_color = Color(0.05, 0.06, 0.1, 1)
stars_visibility = 1.0
cloud_coverage = 0.15
cloud_opacity = 0.5
cloud_color = Color(0.2, 0.22, 0.3, 1)
cloud_shadow_color = Color(0.05, 0.05, 0.1, 1)
sun_glow_intensity = 0.0
[sub_resource type="Resource" id="Resource_fqsrh"]
script = ExtResource("3_c6jpa")
preset_name = "Night"
preset_type = 2
config = SubResource("Resource_bmqu2")
[sub_resource type="Resource" id="Resource_4i5ra"]
script = ExtResource("5_2w5ig")
sun_color = Color(0.9, 0.9, 0.85, 1)
sun_intensity = 0.4
moon_intensity = 0.0
ambient_color = Color(0.5, 0.5, 0.55, 1)
ambient_intensity = 0.7
fog_color = Color(0.6, 0.62, 0.65, 1)
fog_density = 0.03
sky_top_color = Color(0.45, 0.48, 0.55, 1)
sky_horizon_color = Color(0.6, 0.62, 0.65, 1)
sky_bottom_color = Color(0.5, 0.52, 0.55, 1)
cloud_coverage = 0.9
cloud_opacity = 1.0
cloud_color = Color(0.7, 0.72, 0.75, 1)
cloud_shadow_color = Color(0.4, 0.42, 0.45, 1)
sun_glow_intensity = 0.1
[sub_resource type="Resource" id="Resource_drex5"]
script = ExtResource("3_c6jpa")
preset_name = "Overcast"
preset_type = 3
config = SubResource("Resource_4i5ra")
[sub_resource type="Resource" id="Resource_gm4lj"]
script = ExtResource("5_2w5ig")
sun_color = Color(0.7, 0.7, 0.75, 1)
sun_intensity = 0.2
moon_intensity = 0.0
ambient_color = Color(0.35, 0.38, 0.45, 1)
fog_color = Color(0.4, 0.42, 0.5, 1)
fog_density = 0.05
sky_top_color = Color(0.25, 0.28, 0.35, 1)
sky_horizon_color = Color(0.4, 0.42, 0.48, 1)
sky_bottom_color = Color(0.35, 0.38, 0.42, 1)
cloud_coverage = 1.0
cloud_opacity = 1.0
cloud_color = Color(0.4, 0.42, 0.5, 1)
cloud_shadow_color = Color(0.2, 0.22, 0.28, 1)
sun_glow_intensity = 0.0
[sub_resource type="Resource" id="Resource_5lttf"]
script = ExtResource("3_c6jpa")
preset_name = "Storm"
preset_type = 4
config = SubResource("Resource_gm4lj")
[sub_resource type="Resource" id="Resource_ym26c"]
script = ExtResource("5_2w5ig")
sun_color = Color(1, 0.7, 0.5, 1)
sun_intensity = 0.5
moon_intensity = 0.1
ambient_color = Color(0.4, 0.35, 0.45, 1)
ambient_intensity = 0.35
fog_color = Color(0.6, 0.5, 0.55, 1)
fog_density = 0.025
sky_top_color = Color(0.15, 0.2, 0.45, 1)
sky_horizon_color = Color(0.9, 0.6, 0.5, 1)
sky_bottom_color = Color(0.7, 0.45, 0.4, 1)
stars_visibility = 0.3
cloud_coverage = 0.25
cloud_color = Color(0.95, 0.7, 0.6, 1)
cloud_shadow_color = Color(0.3, 0.25, 0.35, 1)
sun_glow_intensity = 0.6
sun_glow_size = 0.35
[sub_resource type="Resource" id="Resource_umd7u"]
script = ExtResource("3_c6jpa")
preset_name = "Dawn"
preset_type = 5
config = SubResource("Resource_ym26c")
[sub_resource type="Gradient" id="Gradient_sxvon"]
offsets = PackedFloat32Array(0, 0.25, 0.5, 0.75, 1)
colors = PackedColorArray(1, 0.6, 0.4, 1, 1, 0.5, 0.3, 1, 1, 1, 0.95, 1, 1, 0.7, 0.4, 1, 1, 1, 1, 1)
[sub_resource type="Curve" id="Curve_fm80q"]
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.2, 0), 0.0, 0.0, 0, 0, Vector2(0.3, 0.8), 0.0, 0.0, 0, 0, Vector2(0.5, 1), 0.0, 0.0, 0, 0, Vector2(0.7, 0.8), 0.0, 0.0, 0, 0, Vector2(0.85, 0), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
point_count = 7
[sub_resource type="Gradient" id="Gradient_kcyr7"]
colors = PackedColorArray(0.7, 0.8, 1, 1, 0.7, 0.8, 1, 1)
[sub_resource type="Curve" id="Curve_qti3v"]
_data = [Vector2(0, 0.4), 0.0, 0.0, 0, 0, Vector2(0.2, 0.3), 0.0, 0.0, 0, 0, Vector2(0.25, 0), 0.0, 0.0, 0, 0, Vector2(0.75, 0), 0.0, 0.0, 0, 0, Vector2(0.85, 0.3), 0.0, 0.0, 0, 0, Vector2(1, 0.4), 0.0, 0.0, 0, 0]
point_count = 6
[sub_resource type="Gradient" id="Gradient_bq8yq"]
offsets = PackedFloat32Array(0, 0.25, 0.5, 0.75, 1)
colors = PackedColorArray(0.15, 0.18, 0.3, 1, 0.15, 0.18, 0.3, 1, 0.6, 0.65, 0.75, 1, 0.55, 0.5, 0.55, 1, 1, 1, 1, 1)
[sub_resource type="Curve" id="Curve_x6xf2"]
_data = [Vector2(0, 0.2), 0.0, 0.0, 0, 0, Vector2(0.25, 0.5), 0.0, 0.0, 0, 0, Vector2(0.5, 0.7), 0.0, 0.0, 0, 0, Vector2(0.75, 0.5), 0.0, 0.0, 0, 0, Vector2(1, 0.2), 0.0, 0.0, 0, 0]
point_count = 5
[sub_resource type="Gradient" id="Gradient_tn5up"]
offsets = PackedFloat32Array(0, 0.25, 0.5, 0.75, 1)
colors = PackedColorArray(0.1, 0.12, 0.2, 1, 0.1, 0.12, 0.2, 1, 0.7, 0.75, 0.85, 1, 0.7, 0.55, 0.5, 1, 1, 1, 1, 1)
[sub_resource type="Curve" id="Curve_ej5u1"]
_data = [Vector2(0, 0.02), 0.0, 0.0, 0, 0, Vector2(0.25, 0.015), 0.0, 0.0, 0, 0, Vector2(0.5, 0.01), 0.0, 0.0, 0, 0, Vector2(0.75, 0.015), 0.0, 0.0, 0, 0, Vector2(1, 0.02), 0.0, 0.0, 0, 0]
point_count = 5
[sub_resource type="Curve" id="Curve_ta3fj"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.2, 0.8), 0.0, 0.0, 0, 0, Vector2(0.3, 0), 0.0, 0.0, 0, 0, Vector2(0.7, 0), 0.0, 0.0, 0, 0, Vector2(0.85, 0.8), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 6
[sub_resource type="Resource" id="Resource_dfjcy"]
script = ExtResource("8_cnx2n")
cloud_density = 0.1
cloud_opacity = 0.6
cloud_coverage = 0.2
wind_speed = 0.5
[sub_resource type="Resource" id="Resource_sjmt0"]
script = ExtResource("8_cnx2n")
weather_state = 1
display_name = "Cloudy"
cloud_density = 0.5
cloud_opacity = 0.8
cloud_coverage = 0.5
ambient_intensity_multiplier = 0.9
[sub_resource type="Resource" id="Resource_uap2q"]
script = ExtResource("8_cnx2n")
weather_state = 2
display_name = "Overcast"
cloud_density = 0.9
cloud_opacity = 1.0
cloud_coverage = 0.9
cloud_color = Color(0.7, 0.72, 0.75, 1)
wind_speed = 1.5
ambient_intensity_multiplier = 0.7
fog_density_multiplier = 1.5
[sub_resource type="Resource" id="Resource_mcoyn"]
script = ExtResource("8_cnx2n")
weather_state = 3
display_name = "Rain"
cloud_density = 0.85
cloud_opacity = 0.95
cloud_coverage = 0.85
cloud_color = Color(0.55, 0.58, 0.65, 1)
wind_speed = 2.0
ambient_intensity_multiplier = 0.6
fog_density_multiplier = 2.0
rain_intensity = 0.7
default_transition_duration = 8.0
[sub_resource type="Resource" id="Resource_mr14h"]
script = ExtResource("8_cnx2n")
weather_state = 4
display_name = "Storm"
cloud_density = 1.0
cloud_opacity = 1.0
cloud_coverage = 1.0
cloud_color = Color(0.4, 0.42, 0.5, 1)
cloud_shadow_color = Color(0.2, 0.22, 0.28, 1)
wind_speed = 4.0
wind_turbulence = 0.4
ambient_intensity_multiplier = 0.4
fog_density_multiplier = 3.0
rain_intensity = 1.0
lightning_enabled = true
lightning_frequency = 0.15
default_transition_duration = 10.0
[sub_resource type="Resource" id="Resource_ps44b"]
script = ExtResource("8_cnx2n")
weather_state = 5
display_name = "Snow"
cloud_density = 0.8
cloud_opacity = 0.9
cloud_coverage = 0.8
cloud_color = Color(0.85, 0.87, 0.92, 1)
ambient_color_multiplier = Color(0.9, 0.92, 1, 1)
ambient_intensity_multiplier = 0.75
fog_color_multiplier = Color(0.95, 0.97, 1, 1)
fog_density_multiplier = 1.8
snow_intensity = 0.8
default_transition_duration = 12.0
[sub_resource type="Curve" id="Curve_uhdsh"]
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.5, 0.5), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="Resource" id="Resource_xh4oc"]
script = ExtResource("4_ssi15")
layer_type = 0
tiling = Vector2(2, 2)
opacity = 0.9
flow_speed = 0.03
parallax_depth = 0.3
[sub_resource type="Resource" id="Resource_ahi10"]
script = ExtResource("4_ssi15")
tiling = Vector2(3, 3)
opacity = 0.7
parallax_depth = 0.6
[sub_resource type="Resource" id="Resource_a5xai"]
script = ExtResource("4_ssi15")
layer_type = 2
opacity = 0.5
flow_speed = 0.01
parallax_depth = 0.9
[sub_resource type="Resource" id="Resource_uxs4q"]
script = ExtResource("4_ssi15")
layer_type = 3
tiling = Vector2(6, 6)
opacity = 0.3
flow_speed = 0.015
[sub_resource type="Compositor" id="Compositor_ssi15"]
[node name="DynamicSky" type="Node3D" unique_id=516593260 node_paths=PackedStringArray("preset_library", "day_night_controller", "weather_controller", "cloud_system", "post_process_controller", "vfx_controller", "shadow_proxy_system", "world_environment", "sun_light", "moon_light")]
script = ExtResource("1_dynamic_sky")
preset_library = NodePath("PresetLibrary")
day_night_controller = NodePath("DayNightController")
weather_controller = NodePath("WeatherController")
cloud_system = NodePath("CloudSystem")
post_process_controller = NodePath("PostProcessController")
vfx_controller = NodePath("VFXController")
shadow_proxy_system = NodePath("ShadowProxySystem")
world_environment = NodePath("WorldEnvironment")
sun_light = NodePath("SunLight")
moon_light = NodePath("MoonLight")
[node name="PresetLibrary" type="Node" parent="." unique_id=1947927418]
script = ExtResource("2_preset_library")
presets = Array[ExtResource("3_c6jpa")]([SubResource("Resource_ajt3x"), SubResource("Resource_sptlx"), SubResource("Resource_fqsrh"), SubResource("Resource_drex5"), SubResource("Resource_5lttf"), SubResource("Resource_umd7u")])
[node name="DayNightController" type="Node" parent="." unique_id=1083149574]
script = ExtResource("3_day_night")
time_of_day = 0.1482273141975634
current_day = 4
sun_color_curve = SubResource("Gradient_sxvon")
sun_intensity_curve = SubResource("Curve_fm80q")
moon_color_curve = SubResource("Gradient_kcyr7")
moon_intensity_curve = SubResource("Curve_qti3v")
ambient_color_curve = SubResource("Gradient_bq8yq")
ambient_intensity_curve = SubResource("Curve_x6xf2")
fog_color_curve = SubResource("Gradient_tn5up")
fog_density_curve = SubResource("Curve_ej5u1")
stars_visibility_curve = SubResource("Curve_ta3fj")
[node name="WeatherController" type="Node" parent="." unique_id=1440281936]
script = ExtResource("4_weather")
weather_profiles = Array[ExtResource("8_cnx2n")]([SubResource("Resource_dfjcy"), SubResource("Resource_sjmt0"), SubResource("Resource_uap2q"), SubResource("Resource_mcoyn"), SubResource("Resource_mr14h"), SubResource("Resource_ps44b")])
transition_curve = SubResource("Curve_uhdsh")
[node name="CloudSystem" type="Node3D" parent="." unique_id=680200253]
script = ExtResource("5_clouds")
layer_configs = Array[ExtResource("4_ssi15")]([SubResource("Resource_xh4oc"), SubResource("Resource_ahi10"), SubResource("Resource_a5xai"), SubResource("Resource_uxs4q")])
global_coverage = 0.2
global_wind_speed = 0.5
sun_direction = Vector3(0.6647294, -0.6939493, -0.2767116)
sun_color = Color(1, 0.55136895, 0.3513689, 1)
sun_intensity = 0.0
moon_direction = Vector3(0.6799649, 0.7098545, 0.18372385)
moon_intensity = 0.33406785
[node name="PostProcessController" type="Node" parent="." unique_id=186747041]
script = ExtResource("6_post_process")
profile = SubResource("Resource_ssi15")
target_environment = ExtResource("13_2w5ig")
sky_top_color = Color(0.2, 0.4, 0.85, 1)
sky_horizon_color = Color(0.6, 0.75, 0.95, 1)
sky_bottom_color = Color(0.5, 0.6, 0.7, 1)
skybox_texture = ExtResource("6_ssi15")
fog_color = Color(0.1, 0.12, 0.2, 1)
fog_density = 0.017602645
sun_direction = Vector3(0.6647294, -0.6939493, -0.2767116)
[node name="VFXController" type="Node3D" parent="." unique_id=207251300]
script = ExtResource("7_vfx")
[node name="ShadowProxySystem" type="Node3D" parent="." unique_id=1982828842]
script = ExtResource("8_shadows")
shadow_opacity = 0.1
flow_speed = 2.5
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=700760798]
environment = ExtResource("13_2w5ig")
compositor = SubResource("Compositor_ssi15")
[node name="SunLight" type="DirectionalLight3D" parent="." unique_id=1278065589]
transform = Transform3D(-0.38430893, -0.9231061, 0.0134779755, 0, 0.014599117, 0.99989367, -0.9232045, 0.384268, -0.0056105736, 0, 50, 0)
visible = false
light_color = Color(1, 0.55136895, 0.3513689, 1)
light_energy = 0.0
shadow_enabled = true
[node name="MoonLight" type="DirectionalLight3D" parent="." unique_id=52072807]
transform = Transform3D(0.2608423, -0.9652886, 0.013407094, 0, 0.013887873, 0.9999038, -0.9653814, -0.26081723, 0.003622545, 0, 50, 0)
light_color = Color(0.7, 0.8, 1, 1)
light_energy = 0.33406785

View File

@@ -0,0 +1,25 @@
[gd_scene format=3 uid="uid://c0752ygxeq4rc"]
[ext_resource type="PackedScene" uid="uid://doiojb8xxlp2s" path="res://dynamic-sky/dynamic_sky.tscn" id="1_cuadp"]
[ext_resource type="Script" uid="uid://cimhpqq1qo80b" path="res://dynamic-sky/scripts/sky_debug_panel.gd" id="2_o3fwy"]
[node name="DynamicSkyToy" type="Node3D" unique_id=1968105337]
[node name="DynamicSky" parent="." unique_id=516593260 instance=ExtResource("1_cuadp")]
[node name="Camera3D" type="Camera3D" parent="." unique_id=805686804]
[node name="Control" type="Control" parent="." unique_id=83422594]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="SkyDebugPanel" type="Control" parent="Control" unique_id=1408698131]
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("2_o3fwy")
metadata/_custom_type_script = "uid://cimhpqq1qo80b"

View File

@@ -0,0 +1,136 @@
[gd_resource type="Environment" format=3 uid="uid://dq64sos14f8hu"]
[ext_resource type="Texture2D" uid="uid://crrj260kiyoy" path="res://dynamic-sky/sky_44_2k.png" id="1_vd8dq"]
[sub_resource type="Shader" id="Shader_porxs"]
code = "
shader_type sky;
uniform sampler2D skybox_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform bool skybox_enabled = false;
uniform float skybox_blend = 1.0;
uniform vec4 sky_top_color : source_color = vec4(0.2, 0.4, 0.8, 1.0);
uniform vec4 sky_horizon_color : source_color = vec4(0.6, 0.7, 0.9, 1.0);
uniform vec4 sky_bottom_color : source_color = vec4(0.4, 0.5, 0.6, 1.0);
uniform float gradient_intensity = 1.0;
uniform float horizon_blend = 0.5;
uniform bool sun_glow_enabled = true;
uniform vec3 sun_direction = vec3(0.0, 1.0, 0.0);
uniform vec4 sun_glow_color : source_color = vec4(1.0, 0.95, 0.8, 1.0);
uniform float sun_glow_intensity = 0.5;
uniform float sun_glow_size = 0.2;
uniform float sun_glow_falloff = 2.0;
uniform bool dawn_dusk_enabled = true;
uniform vec4 dawn_dusk_color : source_color = vec4(1.0, 0.6, 0.4, 1.0);
uniform float dawn_dusk_intensity = 0.5;
uniform float dawn_dusk_blend = 0.0;
uniform float stars_visibility = 0.0;
uniform vec4 stars_tint : source_color = vec4(1.0);
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float stars(vec3 dir) {
vec2 uv = dir.xz / (abs(dir.y) + 0.001) * 50.0;
vec2 cell = floor(uv);
vec2 local = fract(uv) - 0.5;
float star = 0.0;
float h = hash(cell);
if (h > 0.97) {
float dist = length(local);
star = smoothstep(0.1, 0.0, dist) * (h - 0.97) / 0.03;
star *= step(0.0, dir.y);
}
return star;
}
vec2 pano_uv(vec3 dir) {
dir = normalize(dir);
float u = atan(dir.x, dir.z) / (2.0 * PI) + 0.5;
float v = acos(clamp(dir.y, -1.0, 1.0)) / PI;
return vec2(u, v);
}
void sky() {
vec3 dir = EYEDIR;
float y = dir.y;
vec3 color;
if (y > 0.0) {
float t = pow(y, horizon_blend);
color = mix(sky_horizon_color.rgb, sky_top_color.rgb, t);
} else {
float t = pow(-y, horizon_blend);
color = mix(sky_horizon_color.rgb, sky_bottom_color.rgb, t);
}
color = mix(vec3(0.5), color, gradient_intensity);
if (skybox_enabled) {
vec3 pano_color = texture(skybox_texture, pano_uv(dir)).rgb;
color = mix(color, pano_color, skybox_blend);
}
if (sun_glow_enabled && sun_direction.y > -0.2) {
float sun_dot = max(0.0, dot(dir, normalize(sun_direction)));
float glow = pow(sun_dot, 1.0 / max(0.001, sun_glow_size));
glow = pow(glow, sun_glow_falloff);
color += sun_glow_color.rgb * glow * sun_glow_intensity;
}
if (dawn_dusk_enabled && dawn_dusk_blend > 0.0) {
float horizon_factor = 1.0 - abs(y);
horizon_factor = pow(horizon_factor, 2.0);
color = mix(color, dawn_dusk_color.rgb, horizon_factor * dawn_dusk_intensity * dawn_dusk_blend);
}
if (stars_visibility > 0.0 && y > 0.0) {
float star_value = stars(dir) * stars_visibility;
color += stars_tint.rgb * star_value;
}
COLOR = color;
}
"
[sub_resource type="ShaderMaterial" id="ShaderMaterial_jmmec"]
shader = SubResource("Shader_porxs")
shader_parameter/skybox_texture = ExtResource("1_vd8dq")
shader_parameter/skybox_enabled = true
shader_parameter/skybox_blend = 1.0
shader_parameter/sky_top_color = Color(0.2, 0.4, 0.85, 1)
shader_parameter/sky_horizon_color = Color(0.6, 0.75, 0.95, 1)
shader_parameter/sky_bottom_color = Color(0.5, 0.6, 0.7, 1)
shader_parameter/gradient_intensity = 1.0
shader_parameter/horizon_blend = 0.5
shader_parameter/sun_glow_enabled = true
shader_parameter/sun_direction = Vector3(0.9079945, -0.38918287, -0.1551863)
shader_parameter/sun_glow_color = Color(1, 0.5257787, 0.32577872, 1)
shader_parameter/sun_glow_intensity = 0.0
shader_parameter/sun_glow_size = 0.2
shader_parameter/sun_glow_falloff = 2.0
shader_parameter/dawn_dusk_enabled = true
shader_parameter/dawn_dusk_color = Color(1, 0.6, 0.4, 1)
shader_parameter/dawn_dusk_intensity = 0.5
shader_parameter/dawn_dusk_blend = 0.0
shader_parameter/stars_visibility = 0.80297995
shader_parameter/stars_tint = Color(1, 1, 1, 1)
[sub_resource type="Sky" id="Sky_ssi15"]
sky_material = SubResource("ShaderMaterial_jmmec")
[resource]
background_mode = 2
sky = SubResource("Sky_ssi15")
ambient_light_color = Color(0.15, 0.18, 0.3, 1)
ambient_light_energy = 0.4504698
fog_enabled = true
fog_light_color = Color(0.1, 0.12, 0.2, 1)
fog_density = 0.015825503
fog_height_density = 0.1

View File

@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="SkyPreset" format=3 uid="uid://dose3mxe2snku"]
[ext_resource type="Script" uid="uid://c48eq2y4ikok8" path="res://dynamic-sky/resources/sky_preset.gd" id="1"]
[ext_resource type="Script" uid="uid://beu0xhk0rvpky" path="res://dynamic-sky/resources/cloud_layer_config.gd" id="1_5fmul"]
[ext_resource type="Script" uid="uid://c3iniqgu2l0ek" path="res://dynamic-sky/resources/sky_config.gd" id="2_2a7hp"]
[ext_resource type="Texture2D" uid="uid://crrj260kiyoy" path="res://dynamic-sky/sky_44_2k.png" id="4_a3ytv"]
[sub_resource type="Resource" id="Resource_ktpc1"]
script = ExtResource("2_2a7hp")
metadata/_custom_type_script = "uid://c3iniqgu2l0ek"
[resource]
script = ExtResource("1")
preset_name = "Clear Day"
preset_type = 0
config = SubResource("Resource_ktpc1")
skybox_texture = ExtResource("4_a3ytv")

View File

@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="SkyPreset" format=3 uid="uid://cg0qi1v475fvo"]
[ext_resource type="Script" uid="uid://c48eq2y4ikok8" path="res://dynamic-sky/resources/sky_preset.gd" id="1"]
[ext_resource type="Script" uid="uid://beu0xhk0rvpky" path="res://dynamic-sky/resources/cloud_layer_config.gd" id="1_etfyk"]
[ext_resource type="Script" uid="uid://c3iniqgu2l0ek" path="res://dynamic-sky/resources/sky_config.gd" id="2_yd2ys"]
[ext_resource type="Texture2D" uid="uid://sl180wnwa3su" path="res://dynamic-sky/sky_25_2k.png" id="4_ckylf"]
[sub_resource type="Resource" id="Resource_o0y23"]
script = ExtResource("2_yd2ys")
metadata/_custom_type_script = "uid://c3iniqgu2l0ek"
[resource]
script = ExtResource("1")
preset_name = "Dawn"
preset_type = 5
config = SubResource("Resource_o0y23")
skybox_texture = ExtResource("4_ckylf")

View File

@@ -0,0 +1,11 @@
[gd_resource type="Resource" script_class="SkyPreset" format=3 uid="uid://b08fj288b38q6"]
[ext_resource type="Script" uid="uid://c48eq2y4ikok8" path="res://dynamic-sky/resources/sky_preset.gd" id="1"]
[ext_resource type="Script" uid="uid://beu0xhk0rvpky" path="res://dynamic-sky/resources/cloud_layer_config.gd" id="1_eyyte"]
[ext_resource type="Texture2D" uid="uid://cu4ogwg6537jc" path="res://dynamic-sky/sky_16_2k.png" id="3_75xut"]
[resource]
script = ExtResource("1")
preset_name = "Night"
preset_type = 2
skybox_texture = ExtResource("3_75xut")

View File

@@ -0,0 +1,15 @@
[gd_resource type="Resource" script_class="SkyPreset" format=3 uid="uid://b6hytb5q01khi"]
[ext_resource type="Script" uid="uid://c48eq2y4ikok8" path="res://dynamic-sky/resources/sky_preset.gd" id="1"]
[ext_resource type="Script" uid="uid://beu0xhk0rvpky" path="res://dynamic-sky/resources/cloud_layer_config.gd" id="1_vtd2i"]
[ext_resource type="Script" uid="uid://c3iniqgu2l0ek" path="res://dynamic-sky/resources/sky_config.gd" id="2_1tetv"]
[sub_resource type="Resource" id="Resource_k34u2"]
script = ExtResource("2_1tetv")
metadata/_custom_type_script = "uid://c3iniqgu2l0ek"
[resource]
script = ExtResource("1")
preset_name = "Overcast"
preset_type = 3
config = SubResource("Resource_k34u2")

View File

@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="SkyPreset" load_steps=2 format=3]
[ext_resource type="Script" path="res://dynamic-sky/resources/sky_preset.gd" id="1"]
[resource]
script = ExtResource("1")
preset_name = "Storm"
preset_type = 4

View File

@@ -0,0 +1,8 @@
[gd_resource type="Resource" script_class="SkyPreset" load_steps=2 format=3]
[ext_resource type="Script" path="res://dynamic-sky/resources/sky_preset.gd" id="1"]
[resource]
script = ExtResource("1")
preset_name = "Sunset"
preset_type = 1

View File

@@ -0,0 +1,65 @@
@tool
class_name CloudLayerConfig
extends Resource
enum LayerType {
LOW,
MID,
HIGH,
WISPS
}
@export var layer_type: LayerType = LayerType.MID
@export var enabled: bool = true
@export_group("Texture")
@export var texture: Texture2D
@export var texture_scale: Vector2 = Vector2(1.0, 1.0)
@export var tiling: Vector2 = Vector2(4.0, 4.0)
@export_group("Appearance")
@export var opacity: float = 0.8
@export var color_tint: Color = Color.WHITE
@export var shadow_color: Color = Color(0.6, 0.6, 0.7)
@export_group("Motion")
@export var flow_direction: Vector2 = Vector2(1.0, 0.0)
@export var flow_speed: float = 0.02
@export var parallax_depth: float = 1.0
@export_group("Lighting")
@export var receive_light: bool = true
@export var light_influence: float = 1.0
@export var rim_glow_intensity: float = 0.3
func get_layer_height() -> float:
match layer_type:
LayerType.LOW:
return 0.2
LayerType.MID:
return 0.5
LayerType.HIGH:
return 0.8
LayerType.WISPS:
return 0.9
return 0.5
func duplicate_config() -> CloudLayerConfig:
var copy := CloudLayerConfig.new()
copy.layer_type = layer_type
copy.enabled = enabled
copy.texture = texture
copy.texture_scale = texture_scale
copy.tiling = tiling
copy.opacity = opacity
copy.color_tint = color_tint
copy.shadow_color = shadow_color
copy.flow_direction = flow_direction
copy.flow_speed = flow_speed
copy.parallax_depth = parallax_depth
copy.receive_light = receive_light
copy.light_influence = light_influence
copy.rim_glow_intensity = rim_glow_intensity
return copy

View File

@@ -0,0 +1 @@
uid://beu0xhk0rvpky

View File

@@ -0,0 +1,55 @@
@tool
class_name PostProcessProfile
extends Resource
@export_group("Sky Gradient")
@export var gradient_enabled: bool = true
@export var gradient_intensity: float = 1.0
@export var gradient_curve: Curve
@export_group("Fog Shaping")
@export var fog_enabled: bool = true
@export var height_fog_enabled: bool = true
@export var height_fog_start: float = 0.0
@export var height_fog_end: float = 100.0
@export var height_fog_curve: float = 1.0
@export var distance_fog_enabled: bool = true
@export var distance_fog_start: float = 50.0
@export var distance_fog_end: float = 500.0
@export_group("Sun Glow")
@export var sun_glow_enabled: bool = true
@export var sun_glow_intensity: float = 0.5
@export var sun_glow_size: float = 0.2
@export var sun_glow_falloff: float = 2.0
@export var sun_glow_color: Color = Color(1.0, 0.95, 0.8)
@export_group("Dawn/Dusk Enhancement")
@export var dawn_dusk_enabled: bool = true
@export var dawn_dusk_intensity: float = 0.5
@export var dawn_dusk_color: Color = Color(1.0, 0.6, 0.4)
@export var dawn_dusk_blend_range: float = 0.15
func lerp_to(other: PostProcessProfile, weight: float) -> PostProcessProfile:
var result := PostProcessProfile.new()
result.gradient_enabled = gradient_enabled if weight < 0.5 else other.gradient_enabled
result.gradient_intensity = lerpf(gradient_intensity, other.gradient_intensity, weight)
result.fog_enabled = fog_enabled if weight < 0.5 else other.fog_enabled
result.height_fog_enabled = height_fog_enabled if weight < 0.5 else other.height_fog_enabled
result.height_fog_start = lerpf(height_fog_start, other.height_fog_start, weight)
result.height_fog_end = lerpf(height_fog_end, other.height_fog_end, weight)
result.height_fog_curve = lerpf(height_fog_curve, other.height_fog_curve, weight)
result.distance_fog_enabled = distance_fog_enabled if weight < 0.5 else other.distance_fog_enabled
result.distance_fog_start = lerpf(distance_fog_start, other.distance_fog_start, weight)
result.distance_fog_end = lerpf(distance_fog_end, other.distance_fog_end, weight)
result.sun_glow_enabled = sun_glow_enabled if weight < 0.5 else other.sun_glow_enabled
result.sun_glow_intensity = lerpf(sun_glow_intensity, other.sun_glow_intensity, weight)
result.sun_glow_size = lerpf(sun_glow_size, other.sun_glow_size, weight)
result.sun_glow_falloff = lerpf(sun_glow_falloff, other.sun_glow_falloff, weight)
result.sun_glow_color = sun_glow_color.lerp(other.sun_glow_color, weight)
result.dawn_dusk_enabled = dawn_dusk_enabled if weight < 0.5 else other.dawn_dusk_enabled
result.dawn_dusk_intensity = lerpf(dawn_dusk_intensity, other.dawn_dusk_intensity, weight)
result.dawn_dusk_color = dawn_dusk_color.lerp(other.dawn_dusk_color, weight)
result.dawn_dusk_blend_range = lerpf(dawn_dusk_blend_range, other.dawn_dusk_blend_range, weight)
return result

View File

@@ -0,0 +1 @@
uid://ctrw2ycud36yd

View File

@@ -0,0 +1,104 @@
@tool
class_name SkyConfig
extends Resource
@export_group("Sun Settings")
@export var sun_color: Color = Color(1.0, 0.95, 0.8)
@export var sun_intensity: float = 1.0
@export var sun_size: float = 0.05
@export_group("Moon Settings")
@export var moon_color: Color = Color(0.8, 0.85, 1.0)
@export var moon_intensity: float = 0.3
@export var moon_size: float = 0.03
@export_group("Ambient Settings")
@export var ambient_color: Color = Color(0.4, 0.5, 0.6)
@export var ambient_intensity: float = 0.5
@export_group("Fog Settings")
@export var fog_color: Color = Color(0.7, 0.8, 0.9)
@export var fog_density: float = 0.01
@export var fog_height: float = 0.0
@export var fog_height_density: float = 0.1
@export_group("Sky Gradient")
@export var sky_top_color: Color = Color(0.2, 0.4, 0.8)
@export var sky_horizon_color: Color = Color(0.6, 0.7, 0.9)
@export var sky_bottom_color: Color = Color(0.4, 0.5, 0.6)
@export var horizon_blend: float = 0.5
@export_group("Stars")
@export var stars_visibility: float = 0.0
@export var stars_tint: Color = Color.WHITE
@export_group("Cloud Defaults")
@export var cloud_coverage: float = 0.3
@export var cloud_opacity: float = 0.8
@export var cloud_color: Color = Color.WHITE
@export var cloud_shadow_color: Color = Color(0.6, 0.6, 0.7)
@export_group("Post Process")
@export var sun_glow_intensity: float = 0.5
@export var sun_glow_size: float = 0.2
@export var gradient_intensity: float = 1.0
func lerp_to(other: SkyConfig, weight: float) -> SkyConfig:
var result := SkyConfig.new()
result.sun_color = sun_color.lerp(other.sun_color, weight)
result.sun_intensity = lerpf(sun_intensity, other.sun_intensity, weight)
result.sun_size = lerpf(sun_size, other.sun_size, weight)
result.moon_color = moon_color.lerp(other.moon_color, weight)
result.moon_intensity = lerpf(moon_intensity, other.moon_intensity, weight)
result.moon_size = lerpf(moon_size, other.moon_size, weight)
result.ambient_color = ambient_color.lerp(other.ambient_color, weight)
result.ambient_intensity = lerpf(ambient_intensity, other.ambient_intensity, weight)
result.fog_color = fog_color.lerp(other.fog_color, weight)
result.fog_density = lerpf(fog_density, other.fog_density, weight)
result.fog_height = lerpf(fog_height, other.fog_height, weight)
result.fog_height_density = lerpf(fog_height_density, other.fog_height_density, weight)
result.sky_top_color = sky_top_color.lerp(other.sky_top_color, weight)
result.sky_horizon_color = sky_horizon_color.lerp(other.sky_horizon_color, weight)
result.sky_bottom_color = sky_bottom_color.lerp(other.sky_bottom_color, weight)
result.horizon_blend = lerpf(horizon_blend, other.horizon_blend, weight)
result.stars_visibility = lerpf(stars_visibility, other.stars_visibility, weight)
result.stars_tint = stars_tint.lerp(other.stars_tint, weight)
result.cloud_coverage = lerpf(cloud_coverage, other.cloud_coverage, weight)
result.cloud_opacity = lerpf(cloud_opacity, other.cloud_opacity, weight)
result.cloud_color = cloud_color.lerp(other.cloud_color, weight)
result.cloud_shadow_color = cloud_shadow_color.lerp(other.cloud_shadow_color, weight)
result.sun_glow_intensity = lerpf(sun_glow_intensity, other.sun_glow_intensity, weight)
result.sun_glow_size = lerpf(sun_glow_size, other.sun_glow_size, weight)
result.gradient_intensity = lerpf(gradient_intensity, other.gradient_intensity, weight)
return result
func duplicate_config() -> SkyConfig:
var copy := SkyConfig.new()
copy.sun_color = sun_color
copy.sun_intensity = sun_intensity
copy.sun_size = sun_size
copy.moon_color = moon_color
copy.moon_intensity = moon_intensity
copy.moon_size = moon_size
copy.ambient_color = ambient_color
copy.ambient_intensity = ambient_intensity
copy.fog_color = fog_color
copy.fog_density = fog_density
copy.fog_height = fog_height
copy.fog_height_density = fog_height_density
copy.sky_top_color = sky_top_color
copy.sky_horizon_color = sky_horizon_color
copy.sky_bottom_color = sky_bottom_color
copy.horizon_blend = horizon_blend
copy.stars_visibility = stars_visibility
copy.stars_tint = stars_tint
copy.cloud_coverage = cloud_coverage
copy.cloud_opacity = cloud_opacity
copy.cloud_color = cloud_color
copy.cloud_shadow_color = cloud_shadow_color
copy.sun_glow_intensity = sun_glow_intensity
copy.sun_glow_size = sun_glow_size
copy.gradient_intensity = gradient_intensity
return copy

View File

@@ -0,0 +1 @@
uid://c3iniqgu2l0ek

View File

@@ -0,0 +1,164 @@
@tool
class_name SkyPreset
extends Resource
enum PresetType {
CLEAR_DAY,
SUNSET,
NIGHT,
OVERCAST,
STORM,
DAWN,
CUSTOM
}
@export var preset_name: String = "Custom"
@export var preset_type: PresetType = PresetType.CUSTOM
@export var config: SkyConfig
@export var skybox_texture: Texture2D
@export var cloud_layer_configs: Array[CloudLayerConfig]
@export var post_process_profile: PostProcessProfile
static func create_clear_day() -> SkyPreset:
var preset := SkyPreset.new()
preset.preset_name = "Clear Day"
preset.preset_type = PresetType.CLEAR_DAY
preset.config = SkyConfig.new()
preset.config.sun_color = Color(1.0, 0.95, 0.85)
preset.config.sun_intensity = 1.2
preset.config.moon_intensity = 0.0
preset.config.ambient_color = Color(0.5, 0.6, 0.8)
preset.config.ambient_intensity = 0.6
preset.config.fog_color = Color(0.7, 0.8, 0.95)
preset.config.fog_density = 0.005
preset.config.sky_top_color = Color(0.2, 0.4, 0.85)
preset.config.sky_horizon_color = Color(0.6, 0.75, 0.95)
preset.config.sky_bottom_color = Color(0.5, 0.6, 0.7)
preset.config.stars_visibility = 0.0
preset.config.cloud_coverage = 0.2
preset.config.cloud_opacity = 0.7
preset.config.sun_glow_intensity = 0.3
return preset
static func create_sunset() -> SkyPreset:
var preset := SkyPreset.new()
preset.preset_name = "Sunset"
preset.preset_type = PresetType.SUNSET
preset.config = SkyConfig.new()
preset.config.sun_color = Color(1.0, 0.5, 0.2)
preset.config.sun_intensity = 0.8
preset.config.moon_intensity = 0.0
preset.config.ambient_color = Color(0.8, 0.5, 0.4)
preset.config.ambient_intensity = 0.4
preset.config.fog_color = Color(0.9, 0.6, 0.4)
preset.config.fog_density = 0.015
preset.config.sky_top_color = Color(0.3, 0.2, 0.5)
preset.config.sky_horizon_color = Color(1.0, 0.5, 0.3)
preset.config.sky_bottom_color = Color(0.9, 0.4, 0.2)
preset.config.stars_visibility = 0.1
preset.config.cloud_coverage = 0.3
preset.config.cloud_opacity = 0.9
preset.config.cloud_color = Color(1.0, 0.7, 0.5)
preset.config.cloud_shadow_color = Color(0.4, 0.2, 0.3)
preset.config.sun_glow_intensity = 0.8
preset.config.sun_glow_size = 0.4
return preset
static func create_night() -> SkyPreset:
var preset := SkyPreset.new()
preset.preset_name = "Night"
preset.preset_type = PresetType.NIGHT
preset.config = SkyConfig.new()
preset.config.sun_intensity = 0.0
preset.config.moon_color = Color(0.7, 0.8, 1.0)
preset.config.moon_intensity = 0.4
preset.config.ambient_color = Color(0.1, 0.15, 0.25)
preset.config.ambient_intensity = 0.2
preset.config.fog_color = Color(0.1, 0.12, 0.2)
preset.config.fog_density = 0.02
preset.config.sky_top_color = Color(0.02, 0.03, 0.08)
preset.config.sky_horizon_color = Color(0.1, 0.12, 0.2)
preset.config.sky_bottom_color = Color(0.05, 0.06, 0.1)
preset.config.stars_visibility = 1.0
preset.config.cloud_coverage = 0.15
preset.config.cloud_opacity = 0.5
preset.config.cloud_color = Color(0.2, 0.22, 0.3)
preset.config.cloud_shadow_color = Color(0.05, 0.05, 0.1)
preset.config.sun_glow_intensity = 0.0
return preset
static func create_overcast() -> SkyPreset:
var preset := SkyPreset.new()
preset.preset_name = "Overcast"
preset.preset_type = PresetType.OVERCAST
preset.config = SkyConfig.new()
preset.config.sun_color = Color(0.9, 0.9, 0.85)
preset.config.sun_intensity = 0.4
preset.config.moon_intensity = 0.0
preset.config.ambient_color = Color(0.5, 0.5, 0.55)
preset.config.ambient_intensity = 0.7
preset.config.fog_color = Color(0.6, 0.62, 0.65)
preset.config.fog_density = 0.03
preset.config.sky_top_color = Color(0.45, 0.48, 0.55)
preset.config.sky_horizon_color = Color(0.6, 0.62, 0.65)
preset.config.sky_bottom_color = Color(0.5, 0.52, 0.55)
preset.config.stars_visibility = 0.0
preset.config.cloud_coverage = 0.9
preset.config.cloud_opacity = 1.0
preset.config.cloud_color = Color(0.7, 0.72, 0.75)
preset.config.cloud_shadow_color = Color(0.4, 0.42, 0.45)
preset.config.sun_glow_intensity = 0.1
return preset
static func create_storm() -> SkyPreset:
var preset := SkyPreset.new()
preset.preset_name = "Storm"
preset.preset_type = PresetType.STORM
preset.config = SkyConfig.new()
preset.config.sun_color = Color(0.7, 0.7, 0.75)
preset.config.sun_intensity = 0.2
preset.config.moon_intensity = 0.0
preset.config.ambient_color = Color(0.35, 0.38, 0.45)
preset.config.ambient_intensity = 0.5
preset.config.fog_color = Color(0.4, 0.42, 0.5)
preset.config.fog_density = 0.05
preset.config.sky_top_color = Color(0.25, 0.28, 0.35)
preset.config.sky_horizon_color = Color(0.4, 0.42, 0.48)
preset.config.sky_bottom_color = Color(0.35, 0.38, 0.42)
preset.config.stars_visibility = 0.0
preset.config.cloud_coverage = 1.0
preset.config.cloud_opacity = 1.0
preset.config.cloud_color = Color(0.4, 0.42, 0.5)
preset.config.cloud_shadow_color = Color(0.2, 0.22, 0.28)
preset.config.sun_glow_intensity = 0.0
return preset
static func create_dawn() -> SkyPreset:
var preset := SkyPreset.new()
preset.preset_name = "Dawn"
preset.preset_type = PresetType.DAWN
preset.config = SkyConfig.new()
preset.config.sun_color = Color(1.0, 0.7, 0.5)
preset.config.sun_intensity = 0.5
preset.config.moon_intensity = 0.1
preset.config.ambient_color = Color(0.4, 0.35, 0.45)
preset.config.ambient_intensity = 0.35
preset.config.fog_color = Color(0.6, 0.5, 0.55)
preset.config.fog_density = 0.025
preset.config.sky_top_color = Color(0.15, 0.2, 0.45)
preset.config.sky_horizon_color = Color(0.9, 0.6, 0.5)
preset.config.sky_bottom_color = Color(0.7, 0.45, 0.4)
preset.config.stars_visibility = 0.3
preset.config.cloud_coverage = 0.25
preset.config.cloud_opacity = 0.8
preset.config.cloud_color = Color(0.95, 0.7, 0.6)
preset.config.cloud_shadow_color = Color(0.3, 0.25, 0.35)
preset.config.sun_glow_intensity = 0.6
preset.config.sun_glow_size = 0.35
return preset

View File

@@ -0,0 +1 @@
uid://c48eq2y4ikok8

View File

@@ -0,0 +1,158 @@
@tool
class_name WeatherProfile
extends Resource
enum WeatherState {
CLEAR,
CLOUDY,
OVERCAST,
RAIN,
STORM,
SNOW
}
@export var weather_state: WeatherState = WeatherState.CLEAR
@export var display_name: String = "Clear"
@export_group("Clouds")
@export var cloud_density: float = 0.2
@export var cloud_opacity: float = 0.7
@export var cloud_coverage: float = 0.3
@export var cloud_color: Color = Color.WHITE
@export var cloud_shadow_color: Color = Color(0.6, 0.6, 0.7)
@export_group("Wind")
@export var wind_speed: float = 1.0
@export var wind_direction: Vector2 = Vector2(1.0, 0.0)
@export var wind_turbulence: float = 0.1
@export_group("Ambient Adjustments")
@export var ambient_color_multiplier: Color = Color.WHITE
@export var ambient_intensity_multiplier: float = 1.0
@export_group("Fog Adjustments")
@export var fog_color_multiplier: Color = Color.WHITE
@export var fog_density_multiplier: float = 1.0
@export_group("VFX")
@export var rain_intensity: float = 0.0
@export var snow_intensity: float = 0.0
@export var lightning_enabled: bool = false
@export var lightning_frequency: float = 0.1
@export_group("Transition")
@export var default_transition_duration: float = 5.0
static func create_clear() -> WeatherProfile:
var profile := WeatherProfile.new()
profile.weather_state = WeatherState.CLEAR
profile.display_name = "Clear"
profile.cloud_density = 0.1
profile.cloud_opacity = 0.6
profile.cloud_coverage = 0.2
profile.wind_speed = 0.5
return profile
static func create_cloudy() -> WeatherProfile:
var profile := WeatherProfile.new()
profile.weather_state = WeatherState.CLOUDY
profile.display_name = "Cloudy"
profile.cloud_density = 0.5
profile.cloud_opacity = 0.8
profile.cloud_coverage = 0.5
profile.wind_speed = 1.0
profile.ambient_intensity_multiplier = 0.9
return profile
static func create_overcast() -> WeatherProfile:
var profile := WeatherProfile.new()
profile.weather_state = WeatherState.OVERCAST
profile.display_name = "Overcast"
profile.cloud_density = 0.9
profile.cloud_opacity = 1.0
profile.cloud_coverage = 0.9
profile.cloud_color = Color(0.7, 0.72, 0.75)
profile.wind_speed = 1.5
profile.ambient_intensity_multiplier = 0.7
profile.fog_density_multiplier = 1.5
return profile
static func create_rain() -> WeatherProfile:
var profile := WeatherProfile.new()
profile.weather_state = WeatherState.RAIN
profile.display_name = "Rain"
profile.cloud_density = 0.85
profile.cloud_opacity = 0.95
profile.cloud_coverage = 0.85
profile.cloud_color = Color(0.55, 0.58, 0.65)
profile.wind_speed = 2.0
profile.ambient_intensity_multiplier = 0.6
profile.fog_density_multiplier = 2.0
profile.rain_intensity = 0.7
profile.default_transition_duration = 8.0
return profile
static func create_storm() -> WeatherProfile:
var profile := WeatherProfile.new()
profile.weather_state = WeatherState.STORM
profile.display_name = "Storm"
profile.cloud_density = 1.0
profile.cloud_opacity = 1.0
profile.cloud_coverage = 1.0
profile.cloud_color = Color(0.4, 0.42, 0.5)
profile.cloud_shadow_color = Color(0.2, 0.22, 0.28)
profile.wind_speed = 4.0
profile.wind_turbulence = 0.4
profile.ambient_intensity_multiplier = 0.4
profile.fog_density_multiplier = 3.0
profile.rain_intensity = 1.0
profile.lightning_enabled = true
profile.lightning_frequency = 0.15
profile.default_transition_duration = 10.0
return profile
static func create_snow() -> WeatherProfile:
var profile := WeatherProfile.new()
profile.weather_state = WeatherState.SNOW
profile.display_name = "Snow"
profile.cloud_density = 0.8
profile.cloud_opacity = 0.9
profile.cloud_coverage = 0.8
profile.cloud_color = Color(0.85, 0.87, 0.92)
profile.wind_speed = 1.0
profile.ambient_color_multiplier = Color(0.9, 0.92, 1.0)
profile.ambient_intensity_multiplier = 0.75
profile.fog_color_multiplier = Color(0.95, 0.97, 1.0)
profile.fog_density_multiplier = 1.8
profile.snow_intensity = 0.8
profile.default_transition_duration = 12.0
return profile
func lerp_to(other: WeatherProfile, weight: float) -> WeatherProfile:
var result := WeatherProfile.new()
result.weather_state = weather_state if weight < 0.5 else other.weather_state
result.display_name = display_name if weight < 0.5 else other.display_name
result.cloud_density = lerpf(cloud_density, other.cloud_density, weight)
result.cloud_opacity = lerpf(cloud_opacity, other.cloud_opacity, weight)
result.cloud_coverage = lerpf(cloud_coverage, other.cloud_coverage, weight)
result.cloud_color = cloud_color.lerp(other.cloud_color, weight)
result.cloud_shadow_color = cloud_shadow_color.lerp(other.cloud_shadow_color, weight)
result.wind_speed = lerpf(wind_speed, other.wind_speed, weight)
result.wind_direction = wind_direction.lerp(other.wind_direction, weight).normalized()
result.wind_turbulence = lerpf(wind_turbulence, other.wind_turbulence, weight)
result.ambient_color_multiplier = ambient_color_multiplier.lerp(other.ambient_color_multiplier, weight)
result.ambient_intensity_multiplier = lerpf(ambient_intensity_multiplier, other.ambient_intensity_multiplier, weight)
result.fog_color_multiplier = fog_color_multiplier.lerp(other.fog_color_multiplier, weight)
result.fog_density_multiplier = lerpf(fog_density_multiplier, other.fog_density_multiplier, weight)
result.rain_intensity = lerpf(rain_intensity, other.rain_intensity, weight)
result.snow_intensity = lerpf(snow_intensity, other.snow_intensity, weight)
result.lightning_enabled = lightning_enabled or other.lightning_enabled
result.lightning_frequency = lerpf(lightning_frequency, other.lightning_frequency, weight)
return result

View File

@@ -0,0 +1 @@
uid://d2srir3wsi045

View File

@@ -0,0 +1,301 @@
@tool
class_name CloudSystem2D
extends Node3D
signal cloud_coverage_changed(coverage: float)
const MAX_LAYERS := 4
@export_group("Cloud Layers")
@export var layer_configs: Array[CloudLayerConfig] = []
@export var global_coverage: float = 0.5:
set(value):
global_coverage = clampf(value, 0.0, 1.0)
_update_coverage()
cloud_coverage_changed.emit(global_coverage)
@export_group("Evolution")
@export var evolution_enabled: bool = true
@export var evolution_speed: float = 0.01
@export var evolution_scale: float = 1.0
@export var evolution_seed: int = 0
@export_group("Wind Override")
@export var use_global_wind: bool = true
@export var global_wind_direction: Vector2 = Vector2(1.0, 0.0)
@export var global_wind_speed: float = 1.0
@export_group("Lighting")
@export var sun_direction: Vector3 = Vector3(0.0, 1.0, 0.0)
@export var sun_color: Color = Color.WHITE
@export var sun_intensity: float = 1.0
@export var moon_direction: Vector3 = Vector3(0.0, -1.0, 0.0)
@export var moon_color: Color = Color(0.7, 0.8, 1.0)
@export var moon_intensity: float = 0.3
@export_group("Performance")
@export var update_rate: float = 60.0
var _cloud_materials: Array[ShaderMaterial] = []
var _cloud_meshes: Array[MeshInstance3D] = []
var _time_accumulator: float = 0.0
var _evolution_offset: float = 0.0
var _update_timer: float = 0.0
func _ready() -> void:
if layer_configs.is_empty():
_initialize_default_layers()
_create_cloud_meshes()
_update_all_layers()
func _process(delta: float) -> void:
_update_timer += delta
var update_interval := 1.0 / update_rate
if _update_timer >= update_interval:
_update_timer = 0.0
_time_accumulator += delta * update_rate * update_interval
if evolution_enabled:
_evolution_offset += delta * evolution_speed
_update_flow(delta)
_update_lighting()
func _initialize_default_layers() -> void:
layer_configs.clear()
var low_layer := CloudLayerConfig.new()
low_layer.layer_type = CloudLayerConfig.LayerType.LOW
low_layer.opacity = 0.9
low_layer.flow_speed = 0.03
low_layer.parallax_depth = 0.3
low_layer.tiling = Vector2(2.0, 2.0)
layer_configs.append(low_layer)
var mid_layer := CloudLayerConfig.new()
mid_layer.layer_type = CloudLayerConfig.LayerType.MID
mid_layer.opacity = 0.7
mid_layer.flow_speed = 0.02
mid_layer.parallax_depth = 0.6
mid_layer.tiling = Vector2(3.0, 3.0)
layer_configs.append(mid_layer)
var high_layer := CloudLayerConfig.new()
high_layer.layer_type = CloudLayerConfig.LayerType.HIGH
high_layer.opacity = 0.5
high_layer.flow_speed = 0.01
high_layer.parallax_depth = 0.9
high_layer.tiling = Vector2(4.0, 4.0)
layer_configs.append(high_layer)
var wisps_layer := CloudLayerConfig.new()
wisps_layer.layer_type = CloudLayerConfig.LayerType.WISPS
wisps_layer.opacity = 0.3
wisps_layer.flow_speed = 0.015
wisps_layer.parallax_depth = 1.0
wisps_layer.tiling = Vector2(6.0, 6.0)
layer_configs.append(wisps_layer)
func _create_cloud_meshes() -> void:
for mesh in _cloud_meshes:
if is_instance_valid(mesh):
mesh.queue_free()
_cloud_meshes.clear()
_cloud_materials.clear()
for i in layer_configs.size():
var config := layer_configs[i]
if not config.enabled:
continue
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = "CloudLayer_%d" % i
var quad := QuadMesh.new()
quad.size = Vector2(1000.0, 1000.0)
quad.orientation = PlaneMesh.FACE_Y
mesh_instance.mesh = quad
var material := _create_cloud_material(config)
mesh_instance.material_override = material
var height := config.get_layer_height() * 100.0 + 50.0
mesh_instance.position.y = height
add_child(mesh_instance)
_cloud_meshes.append(mesh_instance)
_cloud_materials.append(material)
func _create_cloud_material(config: CloudLayerConfig) -> ShaderMaterial:
var material := ShaderMaterial.new()
material.shader = _get_cloud_shader()
material.set_shader_parameter("tiling", config.tiling)
material.set_shader_parameter("opacity", config.opacity * global_coverage)
material.set_shader_parameter("color_tint", config.color_tint)
material.set_shader_parameter("shadow_color", config.shadow_color)
material.set_shader_parameter("flow_direction", config.flow_direction)
material.set_shader_parameter("flow_speed", config.flow_speed)
material.set_shader_parameter("light_influence", config.light_influence)
material.set_shader_parameter("rim_glow_intensity", config.rim_glow_intensity)
if config.texture:
material.set_shader_parameter("cloud_texture", config.texture)
return material
func _get_cloud_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type spatial;
render_mode unshaded, depth_draw_never, cull_disabled;
uniform sampler2D cloud_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 tiling = vec2(4.0, 4.0);
uniform float opacity = 0.8;
uniform vec4 color_tint : source_color = vec4(1.0);
uniform vec4 shadow_color : source_color = vec4(0.6, 0.6, 0.7, 1.0);
uniform vec2 flow_direction = vec2(1.0, 0.0);
uniform float flow_speed = 0.02;
uniform float flow_offset = 0.0;
uniform vec3 light_direction = vec3(0.0, 1.0, 0.0);
uniform vec4 light_color : source_color = vec4(1.0);
uniform float light_intensity = 1.0;
uniform float light_influence = 1.0;
uniform float rim_glow_intensity = 0.3;
uniform float evolution_offset = 0.0;
uniform float coverage = 1.0;
varying vec2 world_uv;
void vertex() {
world_uv = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xz;
}
void fragment() {
vec2 uv = world_uv * tiling * 0.001;
uv += flow_direction * flow_offset;
vec2 distorted_uv = uv + vec2(sin(uv.y * 10.0 + evolution_offset) * 0.01, cos(uv.x * 10.0 + evolution_offset) * 0.01);
vec4 cloud_sample = texture(cloud_texture, distorted_uv);
float cloud_alpha = cloud_sample.r * opacity * coverage;
float light_dot = dot(normalize(light_direction), vec3(0.0, 1.0, 0.0));
float light_factor = mix(0.5, 1.0, (light_dot + 1.0) * 0.5) * light_influence;
vec3 lit_color = mix(shadow_color.rgb, color_tint.rgb, light_factor);
lit_color *= light_color.rgb * light_intensity;
float rim = pow(1.0 - cloud_sample.r, 2.0) * rim_glow_intensity;
lit_color += light_color.rgb * rim;
ALBEDO = lit_color;
ALPHA = cloud_alpha;
}
"""
return shader
func _update_flow(delta: float) -> void:
for i in _cloud_materials.size():
if i >= layer_configs.size():
continue
var config := layer_configs[i]
var material := _cloud_materials[i]
var wind_dir := global_wind_direction if use_global_wind else config.flow_direction
var wind_spd := global_wind_speed if use_global_wind else 1.0
#var flow_offset: float = material.get_shader_parameter("flow_offset")
#flow_offset += config.flow_speed * wind_spd * delta
#
#material.set_shader_parameter("flow_offset", flow_offset)
#material.set_shader_parameter("flow_direction", wind_dir)
#smaterial.set_shader_parameter("evolution_offset", _evolution_offset)
func _update_lighting() -> void:
for material in _cloud_materials:
material.set_shader_parameter("light_direction", sun_direction)
material.set_shader_parameter("light_color", sun_color)
material.set_shader_parameter("light_intensity", sun_intensity)
func _update_coverage() -> void:
for i in _cloud_materials.size():
if i >= layer_configs.size():
continue
var config := layer_configs[i]
_cloud_materials[i].set_shader_parameter("coverage", global_coverage)
_cloud_materials[i].set_shader_parameter("opacity", config.opacity)
func _update_all_layers() -> void:
for i in _cloud_materials.size():
if i >= layer_configs.size():
continue
var config := layer_configs[i]
var material := _cloud_materials[i]
material.set_shader_parameter("tiling", config.tiling)
material.set_shader_parameter("opacity", config.opacity)
material.set_shader_parameter("color_tint", config.color_tint)
material.set_shader_parameter("shadow_color", config.shadow_color)
material.set_shader_parameter("flow_direction", config.flow_direction)
material.set_shader_parameter("flow_speed", config.flow_speed)
material.set_shader_parameter("light_influence", config.light_influence)
material.set_shader_parameter("rim_glow_intensity", config.rim_glow_intensity)
material.set_shader_parameter("coverage", global_coverage)
func set_layer_enabled(layer_index: int, enabled: bool) -> void:
if layer_index < 0 or layer_index >= _cloud_meshes.size():
return
_cloud_meshes[layer_index].visible = enabled
func set_layer_opacity(layer_index: int, opacity: float) -> void:
if layer_index < 0 or layer_index >= _cloud_materials.size():
return
if layer_index < layer_configs.size():
layer_configs[layer_index].opacity = opacity
_cloud_materials[layer_index].set_shader_parameter("opacity", opacity)
func set_layer_color(layer_index: int, color: Color) -> void:
if layer_index < 0 or layer_index >= _cloud_materials.size():
return
if layer_index < layer_configs.size():
layer_configs[layer_index].color_tint = color
_cloud_materials[layer_index].set_shader_parameter("color_tint", color)
func set_global_wind(direction: Vector2, speed: float) -> void:
global_wind_direction = direction.normalized()
global_wind_speed = speed
func set_sun_lighting(direction: Vector3, color: Color, intensity: float) -> void:
sun_direction = direction.normalized()
sun_color = color
sun_intensity = intensity
_update_lighting()
func set_moon_lighting(direction: Vector3, color: Color, intensity: float) -> void:
moon_direction = direction.normalized()
moon_color = color
moon_intensity = intensity
func rebuild_layers() -> void:
_create_cloud_meshes()
_update_all_layers()

View File

@@ -0,0 +1 @@
uid://cwplvsarljsf

View File

@@ -0,0 +1,313 @@
@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("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.2, 0.3))
moon_intensity_curve.add_point(Vector2(0.25, 0.0))
moon_intensity_curve.add_point(Vector2(0.75, 0.0))
moon_intensity_curve.add_point(Vector2(0.85, 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.2))
ambient_intensity_curve.add_point(Vector2(0.25, 0.5))
ambient_intensity_curve.add_point(Vector2(0.5, 0.7))
ambient_intensity_curve.add_point(Vector2(0.75, 0.5))
ambient_intensity_curve.add_point(Vector2(1.0, 0.2))
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 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 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"

View File

@@ -0,0 +1 @@
uid://gypnoh5ikwjn

View File

@@ -0,0 +1,399 @@
@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_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
func _ready() -> void:
if auto_create_controllers:
_ensure_controllers_exist()
_connect_signals()
property_list_changed_notify()
_sync_preset_name()
_apply_seed()
_sync_all_systems()
func _process(delta: float) -> void:
_sync_all_systems()
if show_debug_panel:
_update_debug_info()
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 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()
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()
if sun_light:
sun_light.look_at(global_position - sun_dir, Vector3.UP)
sun_light.light_color = day_night_controller.get_sun_color()
sun_light.light_energy = day_night_controller.get_sun_intensity()
sun_light.visible = day_night_controller.get_sun_intensity() > 0.01
if moon_light:
moon_light.look_at(global_position - moon_dir, Vector3.UP)
moon_light.light_color = day_night_controller.get_moon_color()
moon_light.light_energy = day_night_controller.get_moon_intensity()
moon_light.visible = day_night_controller.get_moon_intensity() > 0.01
if world_environment and world_environment.environment:
world_environment.environment.ambient_light_color = day_night_controller.get_ambient_color()
world_environment.environment.ambient_light_energy = day_night_controller.get_ambient_intensity()
func _sync_clouds() -> void:
if cloud_system == null:
return
if day_night_controller:
cloud_system.set_sun_lighting(
day_night_controller.get_sun_direction(),
day_night_controller.get_sun_color(),
day_night_controller.get_sun_intensity()
)
cloud_system.set_moon_lighting(
day_night_controller.get_moon_direction(),
day_night_controller.get_moon_color(),
day_night_controller.get_moon_intensity()
)
if weather_controller:
cloud_system.global_coverage = weather_controller.get_cloud_coverage()
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()
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
)
post_process_controller.set_fog_settings(
day_night_controller.get_fog_color(),
day_night_controller.get_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()
)
if _current_config:
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)
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 _get_property_list() -> Array:
var properties := []
var names: PackedStringArray = []
if preset_library:
names = preset_library.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
if preset_library and String(preset_name) != "":
preset_library.apply_preset(String(preset_name))
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 _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 _update_debug_info() -> void:
pass
func set_time_of_day(time: float) -> void:
if day_night_controller:
day_night_controller.time_of_day = time
func set_weather(state: WeatherProfile.WeatherState, transition_time: float = -1.0) -> void:
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

View File

@@ -0,0 +1 @@
uid://8r3do6ow5nmn

View File

@@ -0,0 +1,310 @@
@tool
class_name PostProcessController
extends Node
@export_group("Profile")
@export var profile: PostProcessProfile:
set(value):
profile = value
_apply_profile()
@export_group("Environment")
@export var target_environment: Environment
@export_group("Sky Gradient")
@export var sky_top_color: Color = Color(0.2, 0.4, 0.8)
@export var sky_horizon_color: Color = Color(0.6, 0.7, 0.9)
@export var sky_bottom_color: Color = Color(0.4, 0.5, 0.6)
@export var gradient_intensity: float = 1.0
@export_group("Skybox")
@export var skybox_texture: Texture2D:
set(value):
skybox_texture = value
_update_sky_material()
@export var skybox_blend: float = 1.0:
set(value):
skybox_blend = clampf(value, 0.0, 1.0)
_update_sky_material()
@export_group("Fog Shaping")
@export var fog_enabled: bool = true
@export var fog_color: Color = Color(0.7, 0.8, 0.9)
@export var fog_density: float = 0.01
@export var height_fog_enabled: bool = true
@export var height_fog_min: float = 0.0
@export var height_fog_max: float = 100.0
@export var height_fog_curve: float = 1.0
@export_group("Sun Glow")
@export var sun_glow_enabled: bool = true
@export var sun_glow_intensity: float = 0.5
@export var sun_glow_size: float = 0.2
@export var sun_glow_falloff: float = 2.0
@export var sun_glow_color: Color = Color(1.0, 0.95, 0.8)
@export var sun_direction: Vector3 = Vector3(0.0, 1.0, 0.0)
@export_group("Dawn/Dusk Enhancement")
@export var dawn_dusk_enabled: bool = true
@export var dawn_dusk_intensity: float = 0.5
@export var dawn_dusk_blend: float = 0.0
@export_group("Performance")
@export var effects_enabled: bool = true
var _sky_material: ShaderMaterial
var _post_process_quad: MeshInstance3D
func _ready() -> void:
if profile == null:
profile = PostProcessProfile.new()
_apply_profile()
_setup_environment()
func _apply_profile() -> void:
if profile == null:
return
gradient_intensity = profile.gradient_intensity
fog_enabled = profile.fog_enabled
height_fog_enabled = profile.height_fog_enabled
height_fog_min = profile.height_fog_start
height_fog_max = profile.height_fog_end
height_fog_curve = profile.height_fog_curve
sun_glow_enabled = profile.sun_glow_enabled
sun_glow_intensity = profile.sun_glow_intensity
sun_glow_size = profile.sun_glow_size
sun_glow_falloff = profile.sun_glow_falloff
sun_glow_color = profile.sun_glow_color
dawn_dusk_enabled = profile.dawn_dusk_enabled
dawn_dusk_intensity = profile.dawn_dusk_intensity
func _setup_environment() -> void:
if target_environment == null:
return
_update_fog()
_update_sky()
func _update_fog() -> void:
if target_environment == null:
return
target_environment.fog_enabled = fog_enabled and effects_enabled
if fog_enabled:
target_environment.fog_light_color = fog_color
target_environment.fog_density = fog_density
if height_fog_enabled:
target_environment.fog_height = height_fog_min
target_environment.fog_height_density = height_fog_curve * 0.1
func _update_sky() -> void:
if target_environment == null:
return
if target_environment.sky == null:
target_environment.sky = Sky.new()
if target_environment.sky.sky_material == null or not target_environment.sky.sky_material is ShaderMaterial:
_create_sky_material()
_sky_material = target_environment.sky.sky_material as ShaderMaterial
if _sky_material:
_update_sky_material()
func _create_sky_material() -> void:
_sky_material = ShaderMaterial.new()
_sky_material.shader = _get_sky_shader()
if target_environment and target_environment.sky:
target_environment.sky.sky_material = _sky_material
func _get_sky_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type sky;
uniform sampler2D skybox_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform bool skybox_enabled = false;
uniform float skybox_blend = 1.0;
uniform vec4 sky_top_color : source_color = vec4(0.2, 0.4, 0.8, 1.0);
uniform vec4 sky_horizon_color : source_color = vec4(0.6, 0.7, 0.9, 1.0);
uniform vec4 sky_bottom_color : source_color = vec4(0.4, 0.5, 0.6, 1.0);
uniform float gradient_intensity = 1.0;
uniform float horizon_blend = 0.5;
uniform bool sun_glow_enabled = true;
uniform vec3 sun_direction = vec3(0.0, 1.0, 0.0);
uniform vec4 sun_glow_color : source_color = vec4(1.0, 0.95, 0.8, 1.0);
uniform float sun_glow_intensity = 0.5;
uniform float sun_glow_size = 0.2;
uniform float sun_glow_falloff = 2.0;
uniform bool dawn_dusk_enabled = true;
uniform vec4 dawn_dusk_color : source_color = vec4(1.0, 0.6, 0.4, 1.0);
uniform float dawn_dusk_intensity = 0.5;
uniform float dawn_dusk_blend = 0.0;
uniform float stars_visibility = 0.0;
uniform vec4 stars_tint : source_color = vec4(1.0);
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float stars(vec3 dir) {
vec2 uv = dir.xz / (abs(dir.y) + 0.001) * 50.0;
vec2 cell = floor(uv);
vec2 local = fract(uv) - 0.5;
float star = 0.0;
float h = hash(cell);
if (h > 0.97) {
float dist = length(local);
star = smoothstep(0.1, 0.0, dist) * (h - 0.97) / 0.03;
star *= step(0.0, dir.y);
}
return star;
}
vec2 pano_uv(vec3 dir) {
dir = normalize(dir);
float u = atan(dir.x, dir.z) / (2.0 * PI) + 0.5;
float v = acos(clamp(dir.y, -1.0, 1.0)) / PI;
return vec2(u, v);
}
void sky() {
vec3 dir = EYEDIR;
float y = dir.y;
vec3 color;
if (y > 0.0) {
float t = pow(y, horizon_blend);
color = mix(sky_horizon_color.rgb, sky_top_color.rgb, t);
} else {
float t = pow(-y, horizon_blend);
color = mix(sky_horizon_color.rgb, sky_bottom_color.rgb, t);
}
color = mix(vec3(0.5), color, gradient_intensity);
if (skybox_enabled) {
vec3 pano_color = texture(skybox_texture, pano_uv(dir)).rgb;
color = mix(color, pano_color, skybox_blend);
}
if (sun_glow_enabled && sun_direction.y > -0.2) {
float sun_dot = max(0.0, dot(dir, normalize(sun_direction)));
float glow = pow(sun_dot, 1.0 / max(0.001, sun_glow_size));
glow = pow(glow, sun_glow_falloff);
color += sun_glow_color.rgb * glow * sun_glow_intensity;
}
if (dawn_dusk_enabled && dawn_dusk_blend > 0.0) {
float horizon_factor = 1.0 - abs(y);
horizon_factor = pow(horizon_factor, 2.0);
color = mix(color, dawn_dusk_color.rgb, horizon_factor * dawn_dusk_intensity * dawn_dusk_blend);
}
if (stars_visibility > 0.0 && y > 0.0) {
float star_value = stars(dir) * stars_visibility;
color += stars_tint.rgb * star_value;
}
COLOR = color;
}
"""
return shader
func _update_sky_material() -> void:
if _sky_material == null:
return
if skybox_texture:
_sky_material.set_shader_parameter("skybox_enabled", true)
_sky_material.set_shader_parameter("skybox_texture", skybox_texture)
_sky_material.set_shader_parameter("skybox_blend", skybox_blend)
else:
_sky_material.set_shader_parameter("skybox_enabled", false)
_sky_material.set_shader_parameter("skybox_blend", skybox_blend)
_sky_material.set_shader_parameter("sky_top_color", sky_top_color)
_sky_material.set_shader_parameter("sky_horizon_color", sky_horizon_color)
_sky_material.set_shader_parameter("sky_bottom_color", sky_bottom_color)
_sky_material.set_shader_parameter("gradient_intensity", gradient_intensity)
_sky_material.set_shader_parameter("sun_glow_enabled", sun_glow_enabled and effects_enabled)
_sky_material.set_shader_parameter("sun_direction", sun_direction)
_sky_material.set_shader_parameter("sun_glow_color", sun_glow_color)
_sky_material.set_shader_parameter("sun_glow_intensity", sun_glow_intensity)
_sky_material.set_shader_parameter("sun_glow_size", sun_glow_size)
_sky_material.set_shader_parameter("sun_glow_falloff", sun_glow_falloff)
_sky_material.set_shader_parameter("dawn_dusk_enabled", dawn_dusk_enabled and effects_enabled)
_sky_material.set_shader_parameter("dawn_dusk_blend", dawn_dusk_blend)
_sky_material.set_shader_parameter("dawn_dusk_intensity", dawn_dusk_intensity)
func set_sky_colors(top: Color, horizon: Color, bottom: Color) -> void:
sky_top_color = top
sky_horizon_color = horizon
sky_bottom_color = bottom
_update_sky_material()
func set_fog_settings(color: Color, density: float) -> void:
fog_color = color
fog_density = density
_update_fog()
func set_sun_glow(direction: Vector3, color: Color, intensity: float, size: float) -> void:
sun_direction = direction.normalized()
sun_glow_color = color
sun_glow_intensity = intensity
sun_glow_size = size
_update_sky_material()
func set_dawn_dusk_blend(blend: float) -> void:
dawn_dusk_blend = clampf(blend, 0.0, 1.0)
_update_sky_material()
func set_stars_visibility(visibility: float, tint: Color = Color.WHITE) -> void:
if _sky_material:
_sky_material.set_shader_parameter("stars_visibility", visibility)
_sky_material.set_shader_parameter("stars_tint", tint)
func set_effects_enabled(enabled: bool) -> void:
effects_enabled = enabled
_update_fog()
_update_sky_material()
func apply_config(config: SkyConfig) -> void:
sky_top_color = config.sky_top_color
sky_horizon_color = config.sky_horizon_color
sky_bottom_color = config.sky_bottom_color
fog_color = config.fog_color
fog_density = config.fog_density
sun_glow_intensity = config.sun_glow_intensity
sun_glow_size = config.sun_glow_size
gradient_intensity = config.gradient_intensity
_update_fog()
_update_sky_material()
set_stars_visibility(config.stars_visibility, config.stars_tint)

View File

@@ -0,0 +1 @@
uid://cwqg0bg2m2bme

View File

@@ -0,0 +1,93 @@
@tool
class_name ProceduralCloudTexture
extends Resource
@export var size: int = 512
@export var octaves: int = 6
@export var persistence: float = 0.5
@export var lacunarity: float = 2.0
@export var scale: float = 4.0
@export var seed_value: int = 0
@export var cloud_threshold: float = 0.4
@export var cloud_softness: float = 0.3
var _noise: FastNoiseLite
var _texture: ImageTexture
func _init() -> void:
_setup_noise()
func _setup_noise() -> void:
_noise = FastNoiseLite.new()
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
_noise.seed = seed_value
_noise.frequency = 1.0 / (scale * 64.0)
_noise.fractal_type = FastNoiseLite.FRACTAL_FBM
_noise.fractal_octaves = octaves
_noise.fractal_lacunarity = lacunarity
_noise.fractal_gain = persistence
func generate() -> ImageTexture:
_setup_noise()
var image := Image.create(size, size, false, Image.FORMAT_RGBA8)
for y in size:
for x in size:
var noise_value := (_noise.get_noise_2d(float(x), float(y)) + 1.0) * 0.5
var cloud_value := smoothstep(cloud_threshold - cloud_softness, cloud_threshold + cloud_softness, noise_value)
var color := Color(cloud_value, cloud_value, cloud_value, cloud_value)
image.set_pixel(x, y, color)
_texture = ImageTexture.create_from_image(image)
return _texture
func get_texture() -> ImageTexture:
if _texture == null:
return generate()
return _texture
static func create_default_cloud_texture(layer_type: int = 0) -> ImageTexture:
var generator := ProceduralCloudTexture.new()
generator.size = 512
match layer_type:
0: # Low clouds - larger, denser
generator.scale = 3.0
generator.octaves = 4
generator.cloud_threshold = 0.35
generator.cloud_softness = 0.25
1: # Mid clouds - medium
generator.scale = 4.0
generator.octaves = 5
generator.cloud_threshold = 0.4
generator.cloud_softness = 0.3
2: # High clouds - wispy
generator.scale = 6.0
generator.octaves = 6
generator.cloud_threshold = 0.5
generator.cloud_softness = 0.35
3: # Wisps - very thin
generator.scale = 8.0
generator.octaves = 7
generator.cloud_threshold = 0.55
generator.cloud_softness = 0.4
return generator.generate()
static func create_shadow_texture() -> ImageTexture:
var generator := ProceduralCloudTexture.new()
generator.size = 256
generator.scale = 4.0
generator.octaves = 4
generator.cloud_threshold = 0.3
generator.cloud_softness = 0.4
return generator.generate()

View File

@@ -0,0 +1 @@
uid://dyjrdeqft0xlk

View File

@@ -0,0 +1,220 @@
@tool
class_name ShadowProxySystem
extends Node3D
@export_group("Shadow Settings")
@export var enabled: bool = true:
set(value):
enabled = value
if _shadow_mesh:
_shadow_mesh.visible = enabled
@export var shadow_texture: Texture2D:
set(value):
shadow_texture = value
_update_shadow_texture()
@export var shadow_opacity: float = 0.3:
set(value):
shadow_opacity = clampf(value, 0.0, 1.0)
_update_shadow_material()
@export var shadow_color: Color = Color(0.0, 0.0, 0.1):
set(value):
shadow_color = value
_update_shadow_material()
@export_group("Movement")
@export var flow_direction: Vector2 = Vector2(1.0, 0.0)
@export var flow_speed: float = 5.0
@export var sync_with_clouds: bool = true
@export_group("Projection")
@export var projection_size: float = 500.0:
set(value):
projection_size = value
_update_projection_size()
@export var projection_height: float = 0.1
@export var tiling: Vector2 = Vector2(2.0, 2.0):
set(value):
tiling = value
_update_shadow_material()
@export_group("Blur")
@export var blur_enabled: bool = true
@export var blur_amount: float = 0.5:
set(value):
blur_amount = clampf(value, 0.0, 1.0)
_update_shadow_material()
@export_group("Performance")
@export var follow_camera: bool = true
@export var update_distance: float = 50.0
var _shadow_mesh: MeshInstance3D
var _shadow_material: ShaderMaterial
var _uv_offset: Vector2 = Vector2.ZERO
var _last_camera_position: Vector3 = Vector3.ZERO
func _ready() -> void:
_create_shadow_mesh()
_create_shadow_material()
_update_shadow_material()
func _process(delta: float) -> void:
if not enabled:
return
_update_flow(delta)
if follow_camera:
_follow_camera()
func _create_shadow_mesh() -> void:
if _shadow_mesh:
_shadow_mesh.queue_free()
_shadow_mesh = MeshInstance3D.new()
_shadow_mesh.name = "CloudShadowMesh"
var plane := PlaneMesh.new()
plane.size = Vector2(projection_size, projection_size)
_shadow_mesh.mesh = plane
_shadow_mesh.position.y = projection_height
_shadow_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
add_child(_shadow_mesh)
func _create_shadow_material() -> void:
_shadow_material = ShaderMaterial.new()
_shadow_material.shader = _get_shadow_shader()
if _shadow_mesh:
_shadow_mesh.material_override = _shadow_material
func _get_shadow_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type spatial;
render_mode unshaded, depth_draw_opaque, cull_disabled, shadows_disabled;
uniform sampler2D shadow_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform vec4 shadow_color : source_color = vec4(0.0, 0.0, 0.1, 1.0);
uniform float opacity = 0.3;
uniform vec2 tiling = vec2(2.0, 2.0);
uniform vec2 uv_offset = vec2(0.0, 0.0);
uniform float blur_amount = 0.5;
uniform float edge_fade = 0.1;
varying vec2 world_uv;
void vertex() {
world_uv = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xz;
}
void fragment() {
vec2 uv = world_uv * tiling * 0.001 + uv_offset;
vec4 shadow_sample;
if (blur_amount > 0.01) {
float blur = blur_amount * 0.01;
shadow_sample = texture(shadow_texture, uv) * 0.25;
shadow_sample += texture(shadow_texture, uv + vec2(blur, 0.0)) * 0.125;
shadow_sample += texture(shadow_texture, uv - vec2(blur, 0.0)) * 0.125;
shadow_sample += texture(shadow_texture, uv + vec2(0.0, blur)) * 0.125;
shadow_sample += texture(shadow_texture, uv - vec2(0.0, blur)) * 0.125;
shadow_sample += texture(shadow_texture, uv + vec2(blur, blur)) * 0.0625;
shadow_sample += texture(shadow_texture, uv - vec2(blur, blur)) * 0.0625;
shadow_sample += texture(shadow_texture, uv + vec2(blur, -blur)) * 0.0625;
shadow_sample += texture(shadow_texture, uv + vec2(-blur, blur)) * 0.0625;
} else {
shadow_sample = texture(shadow_texture, uv);
}
float shadow_value = shadow_sample.r;
vec2 local_uv = fract(world_uv * 0.001);
float edge = smoothstep(0.0, edge_fade, local_uv.x);
edge *= smoothstep(0.0, edge_fade, local_uv.y);
edge *= smoothstep(0.0, edge_fade, 1.0 - local_uv.x);
edge *= smoothstep(0.0, edge_fade, 1.0 - local_uv.y);
ALBEDO = shadow_color.rgb;
ALPHA = shadow_value * opacity * edge;
}
"""
return shader
func _update_shadow_material() -> void:
if _shadow_material == null:
return
_shadow_material.set_shader_parameter("shadow_color", shadow_color)
_shadow_material.set_shader_parameter("opacity", shadow_opacity)
_shadow_material.set_shader_parameter("tiling", tiling)
_shadow_material.set_shader_parameter("blur_amount", blur_amount if blur_enabled else 0.0)
func _update_shadow_texture() -> void:
if _shadow_material and shadow_texture:
_shadow_material.set_shader_parameter("shadow_texture", shadow_texture)
func _update_projection_size() -> void:
if _shadow_mesh and _shadow_mesh.mesh is PlaneMesh:
var plane := _shadow_mesh.mesh as PlaneMesh
plane.size = Vector2(projection_size, projection_size)
func _update_flow(delta: float) -> void:
_uv_offset += flow_direction * flow_speed * delta * 0.001
_uv_offset.x = fmod(_uv_offset.x, 1.0)
_uv_offset.y = fmod(_uv_offset.y, 1.0)
if _shadow_material:
_shadow_material.set_shader_parameter("uv_offset", _uv_offset)
func _follow_camera() -> void:
var camera := get_viewport().get_camera_3d()
if camera == null:
return
var camera_pos := camera.global_position
var distance := camera_pos.distance_to(_last_camera_position)
if distance >= update_distance:
_last_camera_position = camera_pos
global_position.x = camera_pos.x
global_position.z = camera_pos.z
func set_flow(direction: Vector2, speed: float) -> void:
flow_direction = direction.normalized()
flow_speed = speed
func set_shadow_appearance(color: Color, opacity_value: float, blur: float) -> void:
shadow_color = color
shadow_opacity = opacity_value
blur_amount = blur
_update_shadow_material()
func sync_with_cloud_system(cloud_system: CloudSystem2D) -> void:
if sync_with_clouds and cloud_system:
flow_direction = cloud_system.global_wind_direction
flow_speed = cloud_system.global_wind_speed * 5.0
func set_cloud_coverage(coverage: float) -> void:
shadow_opacity = coverage * 0.5
_update_shadow_material()

View File

@@ -0,0 +1 @@
uid://hvs2g6acqryq

View File

@@ -0,0 +1,171 @@
@tool
class_name SkyDebugPanel
extends Control
@export var dynamic_sky: DynamicSkyRoot
var _panel: PanelContainer
var _vbox: VBoxContainer
var _time_label: Label
var _weather_label: Label
var _cloud_label: Label
var _seed_label: Label
var _time_slider: HSlider
var _weather_dropdown: OptionButton
var _pause_button: Button
func _ready() -> void:
_create_ui()
_connect_signals()
func _process(_delta: float) -> void:
if dynamic_sky == null:
return
_update_labels()
func _create_ui() -> void:
_panel = PanelContainer.new()
_panel.name = "DebugPanel"
_panel.custom_minimum_size = Vector2(300, 200)
add_child(_panel)
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 10)
margin.add_theme_constant_override("margin_right", 10)
margin.add_theme_constant_override("margin_top", 10)
margin.add_theme_constant_override("margin_bottom", 10)
_panel.add_child(margin)
_vbox = VBoxContainer.new()
_vbox.add_theme_constant_override("separation", 8)
margin.add_child(_vbox)
var title := Label.new()
title.text = "Dynamic Sky Debug"
title.add_theme_font_size_override("font_size", 18)
_vbox.add_child(title)
_vbox.add_child(HSeparator.new())
_time_label = Label.new()
_time_label.text = "Time: 00:00 (0.00)"
_vbox.add_child(_time_label)
var time_hbox := HBoxContainer.new()
_vbox.add_child(time_hbox)
var time_label := Label.new()
time_label.text = "Time:"
time_label.custom_minimum_size.x = 60
time_hbox.add_child(time_label)
_time_slider = HSlider.new()
_time_slider.min_value = 0.0
_time_slider.max_value = 1.0
_time_slider.step = 0.001
_time_slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
time_hbox.add_child(_time_slider)
_pause_button = Button.new()
_pause_button.text = "Pause"
_pause_button.toggle_mode = true
_vbox.add_child(_pause_button)
_vbox.add_child(HSeparator.new())
_weather_label = Label.new()
_weather_label.text = "Weather: Clear"
_vbox.add_child(_weather_label)
var weather_hbox := HBoxContainer.new()
_vbox.add_child(weather_hbox)
var weather_label := Label.new()
weather_label.text = "Set:"
weather_label.custom_minimum_size.x = 60
weather_hbox.add_child(weather_label)
_weather_dropdown = OptionButton.new()
_weather_dropdown.size_flags_horizontal = Control.SIZE_EXPAND_FILL
for state_name in WeatherProfile.WeatherState.keys():
_weather_dropdown.add_item(state_name)
weather_hbox.add_child(_weather_dropdown)
_vbox.add_child(HSeparator.new())
_cloud_label = Label.new()
_cloud_label.text = "Clouds: 0%"
_vbox.add_child(_cloud_label)
_seed_label = Label.new()
_seed_label.text = "Seed: 0"
_vbox.add_child(_seed_label)
var meteor_button := Button.new()
meteor_button.text = "Trigger Meteor Shower"
meteor_button.pressed.connect(_on_meteor_pressed)
_vbox.add_child(meteor_button)
func _connect_signals() -> void:
_time_slider.value_changed.connect(_on_time_slider_changed)
_pause_button.toggled.connect(_on_pause_toggled)
_weather_dropdown.item_selected.connect(_on_weather_selected)
func _update_labels() -> void:
if dynamic_sky == null:
return
var time := dynamic_sky.get_time_of_day()
var hours := int(time * 24.0)
var minutes := int(fmod(time * 24.0 * 60.0, 60.0))
_time_label.text = "Time: %02d:%02d (%.3f)" % [hours, minutes, time]
if not _time_slider.has_focus():
_time_slider.set_value_no_signal(time)
var weather := dynamic_sky.get_current_weather()
var weather_name: String = WeatherProfile.WeatherState.keys()[weather]
_weather_label.text = "Weather: %s" % weather_name
if dynamic_sky.weather_controller:
var coverage := dynamic_sky.weather_controller.get_cloud_coverage()
_cloud_label.text = "Clouds: %d%%" % int(coverage * 100)
if dynamic_sky.weather_controller.is_transitioning():
var progress := dynamic_sky.weather_controller.get_transition_progress()
_weather_label.text += " (transitioning %.0f%%)" % (progress * 100)
_seed_label.text = "Seed: %d" % dynamic_sky.deterministic_seed
if dynamic_sky.day_night_controller:
_pause_button.set_pressed_no_signal(dynamic_sky.day_night_controller.paused)
func _on_time_slider_changed(value: float) -> void:
if dynamic_sky:
dynamic_sky.set_time_of_day(value)
func _on_pause_toggled(pressed: bool) -> void:
if dynamic_sky:
if pressed:
dynamic_sky.pause_time()
else:
dynamic_sky.resume_time()
_pause_button.text = "Resume" if pressed else "Pause"
func _on_weather_selected(index: int) -> void:
if dynamic_sky:
var state: WeatherProfile.WeatherState = index as WeatherProfile.WeatherState
dynamic_sky.set_weather(state)
func _on_meteor_pressed() -> void:
if dynamic_sky:
dynamic_sky.trigger_meteor_shower(30.0)

View File

@@ -0,0 +1 @@
uid://cimhpqq1qo80b

View File

@@ -0,0 +1,131 @@
@tool
class_name SkyboxPresetLibrary
extends Node
signal preset_changed(preset: SkyPreset)
signal preset_applied(preset_name: String)
signal preset_reverted(preset_name: String)
@export var presets: Array[SkyPreset] = []
@export var current_preset_index: int = 0:
set(value):
current_preset_index = clampi(value, 0, maxi(0, presets.size() - 1))
if current_preset_index < presets.size():
_on_preset_selected(presets[current_preset_index])
var _base_preset: SkyPreset
var _runtime_overrides: SkyConfig
func _ready() -> void:
if presets.is_empty():
_initialize_default_presets()
if current_preset_index < presets.size():
_base_preset = presets[current_preset_index]
func _initialize_default_presets() -> void:
presets.clear()
presets.append(SkyPreset.create_clear_day())
presets.append(SkyPreset.create_sunset())
presets.append(SkyPreset.create_night())
presets.append(SkyPreset.create_overcast())
presets.append(SkyPreset.create_storm())
presets.append(SkyPreset.create_dawn())
func get_preset_names() -> PackedStringArray:
var names := PackedStringArray()
for preset in presets:
names.append(preset.preset_name)
return names
func get_current_preset() -> SkyPreset:
if current_preset_index < presets.size():
return presets[current_preset_index]
return null
func get_preset_by_name(preset_name: String) -> SkyPreset:
for preset in presets:
if preset.preset_name == preset_name:
return preset
return null
func get_preset_by_type(preset_type: SkyPreset.PresetType) -> SkyPreset:
for preset in presets:
if preset.preset_type == preset_type:
return preset
return null
func apply_preset(preset_name: String) -> bool:
var preset := get_preset_by_name(preset_name)
if preset == null:
push_warning("SkyboxPresetLibrary: Preset '%s' not found" % preset_name)
return false
for i in presets.size():
if presets[i] == preset:
current_preset_index = i
break
_base_preset = preset
_runtime_overrides = null
preset_applied.emit(preset_name)
preset_changed.emit(preset)
return true
func apply_preset_by_type(preset_type: SkyPreset.PresetType) -> bool:
var preset := get_preset_by_type(preset_type)
if preset == null:
push_warning("SkyboxPresetLibrary: Preset type '%s' not found" % SkyPreset.PresetType.keys()[preset_type])
return false
return apply_preset(preset.preset_name)
func revert_to_preset() -> void:
_runtime_overrides = null
if _base_preset:
preset_reverted.emit(_base_preset.preset_name)
preset_changed.emit(_base_preset)
func set_runtime_override(config: SkyConfig) -> void:
_runtime_overrides = config
func get_effective_config() -> SkyConfig:
if _runtime_overrides != null:
return _runtime_overrides
if _base_preset != null and _base_preset.config != null:
return _base_preset.config
return SkyConfig.new()
func has_runtime_overrides() -> bool:
return _runtime_overrides != null
func add_preset(preset: SkyPreset) -> void:
if preset not in presets:
presets.append(preset)
func remove_preset(preset_name: String) -> bool:
for i in presets.size():
if presets[i].preset_name == preset_name:
presets.remove_at(i)
if current_preset_index >= presets.size():
current_preset_index = maxi(0, presets.size() - 1)
return true
return false
func _on_preset_selected(preset: SkyPreset) -> void:
_base_preset = preset
_runtime_overrides = null
preset_changed.emit(preset)

View File

@@ -0,0 +1 @@
uid://cb8c6ug44jx4t

View File

@@ -0,0 +1,299 @@
@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_timer: float = 0.0
var _meteor_spawn_accumulator: float = 0.0
var _meteor_shower_elapsed: float = 0.0
var _active_stars: Array[Dictionary] = []
var _star_mesh: MeshInstance3D
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 rotation := Basis(Vector3.UP, randf() * TAU)
rotation = rotation.rotated(Vector3.RIGHT, randf_range(-variance, variance))
return (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
mesh_instance.look_at(position + direction, Vector3.UP)
mesh_instance.rotate_object_local(Vector3.RIGHT, PI / 2.0)
add_child(mesh_instance)
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

View File

@@ -0,0 +1 @@
uid://cnvj3m5fpsdtj

View File

@@ -0,0 +1,278 @@
@tool
class_name WeatherController
extends Node
signal weather_state_changed(old_state: WeatherProfile.WeatherState, new_state: WeatherProfile.WeatherState)
signal weather_transition_started(from: WeatherProfile.WeatherState, to: WeatherProfile.WeatherState)
signal weather_transition_completed(state: WeatherProfile.WeatherState)
signal weather_state_entered(state: WeatherProfile.WeatherState)
signal weather_state_exited(state: WeatherProfile.WeatherState)
@export_group("Weather Profiles")
@export var weather_profiles: Array[WeatherProfile] = []
@export var current_weather: WeatherProfile.WeatherState = WeatherProfile.WeatherState.CLEAR:
set(value):
if value != current_weather:
_request_weather_change(value)
@export_group("Transition Settings")
@export var default_transition_duration: float = 5.0
@export var transition_curve: Curve
@export_group("Random Weather")
@export var enable_random_weather: bool = false
@export var min_weather_duration: float = 60.0
@export var max_weather_duration: float = 300.0
@export var snow_enabled: bool = true
@export_group("Scripted Timeline")
@export var use_scripted_timeline: bool = false
@export var weather_timeline: Array[Dictionary] = []
var _current_profile: WeatherProfile
var _target_profile: WeatherProfile
var _transition_weight: float = 1.0
var _is_transitioning: bool = false
var _transition_duration: float = 0.0
var _transition_elapsed: float = 0.0
var _random_weather_timer: float = 0.0
var _next_weather_change: float = 0.0
var _timeline_index: int = 0
var _timeline_timer: float = 0.0
func _ready() -> void:
if weather_profiles.is_empty():
_initialize_default_profiles()
if transition_curve == null:
transition_curve = Curve.new()
transition_curve.add_point(Vector2(0.0, 0.0))
transition_curve.add_point(Vector2(0.5, 0.5))
transition_curve.add_point(Vector2(1.0, 1.0))
_current_profile = get_profile_for_state(current_weather)
_target_profile = _current_profile
_schedule_next_random_weather()
func _process(delta: float) -> void:
if _is_transitioning:
_process_transition(delta)
if enable_random_weather and not use_scripted_timeline:
_process_random_weather(delta)
if use_scripted_timeline:
_process_scripted_timeline(delta)
func _initialize_default_profiles() -> void:
weather_profiles.clear()
weather_profiles.append(WeatherProfile.create_clear())
weather_profiles.append(WeatherProfile.create_cloudy())
weather_profiles.append(WeatherProfile.create_overcast())
weather_profiles.append(WeatherProfile.create_rain())
weather_profiles.append(WeatherProfile.create_storm())
weather_profiles.append(WeatherProfile.create_snow())
func _process_transition(delta: float) -> void:
_transition_elapsed += delta
var t := clampf(_transition_elapsed / _transition_duration, 0.0, 1.0)
if transition_curve:
_transition_weight = transition_curve.sample(t)
else:
_transition_weight = t
if t >= 1.0:
_complete_transition()
func _complete_transition() -> void:
_is_transitioning = false
_transition_weight = 1.0
_current_profile = _target_profile
var new_state := _current_profile.weather_state
weather_transition_completed.emit(new_state)
weather_state_entered.emit(new_state)
func _process_random_weather(delta: float) -> void:
_random_weather_timer += delta
if _random_weather_timer >= _next_weather_change:
_random_weather_timer = 0.0
_trigger_random_weather_change()
_schedule_next_random_weather()
func _schedule_next_random_weather() -> void:
_next_weather_change = randf_range(min_weather_duration, max_weather_duration)
func _trigger_random_weather_change() -> void:
var available_states: Array[WeatherProfile.WeatherState] = []
for state in WeatherProfile.WeatherState.values():
if state == current_weather:
continue
if state == WeatherProfile.WeatherState.SNOW and not snow_enabled:
continue
available_states.append(state)
if available_states.is_empty():
return
var new_state: WeatherProfile.WeatherState = available_states.pick_random()
set_weather(new_state)
func _process_scripted_timeline(delta: float) -> void:
if weather_timeline.is_empty():
return
_timeline_timer += delta
if _timeline_index < weather_timeline.size():
var entry: Dictionary = weather_timeline[_timeline_index]
var trigger_time: float = entry.get("time", 0.0)
if _timeline_timer >= trigger_time:
var state_name: String = entry.get("state", "CLEAR")
var duration: float = entry.get("duration", default_transition_duration)
if WeatherProfile.WeatherState.has(state_name):
var state: WeatherProfile.WeatherState = WeatherProfile.WeatherState.get(state_name)
set_weather(state, duration)
_timeline_index += 1
func _request_weather_change(new_state: WeatherProfile.WeatherState) -> void:
var profile := get_profile_for_state(new_state)
if profile == null:
push_warning("WeatherController: No profile for state %s" % WeatherProfile.WeatherState.keys()[new_state])
return
set_weather(new_state, profile.default_transition_duration)
func get_profile_for_state(state: WeatherProfile.WeatherState) -> WeatherProfile:
for profile in weather_profiles:
if profile.weather_state == state:
return profile
return null
func set_weather(state: WeatherProfile.WeatherState, transition_duration: float = -1.0) -> void:
var profile := get_profile_for_state(state)
if profile == null:
push_warning("WeatherController: No profile for state %s" % WeatherProfile.WeatherState.keys()[state])
return
if _current_profile:
weather_state_exited.emit(_current_profile.weather_state)
var old_state := current_weather
current_weather = state
_target_profile = profile
if transition_duration < 0:
transition_duration = profile.default_transition_duration
_transition_duration = transition_duration
_transition_elapsed = 0.0
_is_transitioning = true
_transition_weight = 0.0
weather_transition_started.emit(old_state, state)
weather_state_changed.emit(old_state, state)
func set_weather_immediate(state: WeatherProfile.WeatherState) -> void:
var profile := get_profile_for_state(state)
if profile == null:
return
if _current_profile:
weather_state_exited.emit(_current_profile.weather_state)
var old_state := current_weather
current_weather = state
_current_profile = profile
_target_profile = profile
_is_transitioning = false
_transition_weight = 1.0
weather_state_changed.emit(old_state, state)
weather_state_entered.emit(state)
func get_current_profile() -> WeatherProfile:
if _is_transitioning and _current_profile and _target_profile:
return _current_profile.lerp_to(_target_profile, _transition_weight)
return _current_profile
func get_cloud_density() -> float:
var profile := get_current_profile()
return profile.cloud_density if profile else 0.2
func get_cloud_opacity() -> float:
var profile := get_current_profile()
return profile.cloud_opacity if profile else 0.7
func get_cloud_coverage() -> float:
var profile := get_current_profile()
return profile.cloud_coverage if profile else 0.3
func get_cloud_color() -> Color:
var profile := get_current_profile()
return profile.cloud_color if profile else Color.WHITE
func get_wind_speed() -> float:
var profile := get_current_profile()
return profile.wind_speed if profile else 1.0
func get_wind_direction() -> Vector2:
var profile := get_current_profile()
return profile.wind_direction if profile else Vector2(1.0, 0.0)
func get_rain_intensity() -> float:
var profile := get_current_profile()
return profile.rain_intensity if profile else 0.0
func get_snow_intensity() -> float:
var profile := get_current_profile()
return profile.snow_intensity if profile else 0.0
func is_lightning_active() -> bool:
var profile := get_current_profile()
return profile.lightning_enabled if profile else false
func get_lightning_frequency() -> float:
var profile := get_current_profile()
return profile.lightning_frequency if profile else 0.0
func get_transition_progress() -> float:
return _transition_weight
func is_transitioning() -> bool:
return _is_transitioning
func reset_timeline() -> void:
_timeline_index = 0
_timeline_timer = 0.0

View File

@@ -0,0 +1 @@
uid://blyx8v2gq8nlw

View File

@@ -0,0 +1,277 @@
@tool
class_name WeatherKillVolume
extends Area3D
enum VolumeShape {
BOX,
SPHERE,
CYLINDER
}
@export_group("Volume Settings")
@export var volume_shape: VolumeShape = VolumeShape.BOX:
set(value):
volume_shape = value
_update_collision_shape()
@export var volume_size: Vector3 = Vector3(10.0, 5.0, 10.0):
set(value):
volume_size = value
_update_collision_shape()
@export var volume_radius: float = 5.0:
set(value):
volume_radius = value
_update_collision_shape()
@export_group("Kill Behavior")
@export var enabled: bool = true
@export var priority: int = 0
@export var instant_kill: bool = true
@export var fade_out_margin: float = 1.0
@export var fade_out_duration: float = 0.3
@export_group("Particle Types")
@export var block_rain: bool = true
@export var block_snow: bool = true
@export var block_all_precipitation: bool = true
@export_group("Debug")
@export var show_debug_gizmo: bool = true
@export var debug_color: Color = Color(0.2, 0.5, 1.0, 0.3)
var _collision_shape: CollisionShape3D
var _particles_inside: Dictionary = {}
func _ready() -> void:
_setup_collision()
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
area_entered.connect(_on_area_entered)
area_exited.connect(_on_area_exited)
func _setup_collision() -> void:
_collision_shape = CollisionShape3D.new()
_collision_shape.name = "KillVolumeShape"
add_child(_collision_shape)
_update_collision_shape()
collision_layer = 0
collision_mask = 1 << 20
monitorable = true
monitoring = true
func _update_collision_shape() -> void:
if _collision_shape == null:
return
var shape: Shape3D
match volume_shape:
VolumeShape.BOX:
var box := BoxShape3D.new()
box.size = volume_size
shape = box
VolumeShape.SPHERE:
var sphere := SphereShape3D.new()
sphere.radius = volume_radius
shape = sphere
VolumeShape.CYLINDER:
var cylinder := CylinderShape3D.new()
cylinder.radius = volume_radius
cylinder.height = volume_size.y
shape = cylinder
_collision_shape.shape = shape
func _on_body_entered(body: Node3D) -> void:
if not enabled:
return
if _is_precipitation_particle(body):
_handle_particle_enter(body)
func _on_body_exited(body: Node3D) -> void:
_handle_particle_exit(body)
func _on_area_entered(area: Area3D) -> void:
if not enabled:
return
if area.has_meta("precipitation_particle"):
_handle_particle_enter(area)
func _on_area_exited(area: Area3D) -> void:
_handle_particle_exit(area)
func _is_precipitation_particle(node: Node3D) -> bool:
if node.has_meta("particle_type"):
var particle_type: String = node.get_meta("particle_type")
if block_all_precipitation:
return particle_type in ["rain", "snow", "hail", "sleet"]
if block_rain and particle_type == "rain":
return true
if block_snow and particle_type == "snow":
return true
return false
func _handle_particle_enter(node: Node3D) -> void:
if instant_kill:
_kill_particle(node)
else:
_particles_inside[node.get_instance_id()] = {
"node": node,
"fade_time": 0.0
}
func _handle_particle_exit(node: Node3D) -> void:
var id := node.get_instance_id()
if _particles_inside.has(id):
_particles_inside.erase(id)
func _kill_particle(node: Node3D) -> void:
if node.has_method("kill"):
node.call("kill")
elif node is GPUParticles3D:
node.emitting = false
elif node is CPUParticles3D:
node.emitting = false
func _process(delta: float) -> void:
if instant_kill or _particles_inside.is_empty():
return
var to_remove: Array[int] = []
for id in _particles_inside:
var data: Dictionary = _particles_inside[id]
var node: Node3D = data["node"]
if not is_instance_valid(node):
to_remove.append(id)
continue
data["fade_time"] += delta
var fade_progress: float = data["fade_time"] / fade_out_duration
if fade_progress >= 1.0:
_kill_particle(node)
to_remove.append(id)
else:
_apply_fade(node, 1.0 - fade_progress)
for id in to_remove:
_particles_inside.erase(id)
func _apply_fade(node: Node3D, alpha: float) -> void:
if node.has_method("set_alpha"):
node.call("set_alpha", alpha)
elif node is GPUParticles3D or node is CPUParticles3D:
pass
func is_point_inside(point: Vector3) -> bool:
var local_point := to_local(point)
match volume_shape:
VolumeShape.BOX:
var half_size := volume_size * 0.5
return abs(local_point.x) <= half_size.x and \
abs(local_point.y) <= half_size.y and \
abs(local_point.z) <= half_size.z
VolumeShape.SPHERE:
return local_point.length() <= volume_radius
VolumeShape.CYLINDER:
var horizontal_dist := Vector2(local_point.x, local_point.z).length()
var half_height := volume_size.y * 0.5
return horizontal_dist <= volume_radius and abs(local_point.y) <= half_height
return false
func is_point_in_fade_margin(point: Vector3) -> bool:
if fade_out_margin <= 0:
return false
var local_point := to_local(point)
match volume_shape:
VolumeShape.BOX:
var half_size := volume_size * 0.5
var inner_half := half_size - Vector3(fade_out_margin, fade_out_margin, fade_out_margin)
var in_outer := abs(local_point.x) <= half_size.x and \
abs(local_point.y) <= half_size.y and \
abs(local_point.z) <= half_size.z
var in_inner := abs(local_point.x) <= inner_half.x and \
abs(local_point.y) <= inner_half.y and \
abs(local_point.z) <= inner_half.z
return in_outer and not in_inner
VolumeShape.SPHERE:
var dist := local_point.length()
return dist <= volume_radius and dist >= volume_radius - fade_out_margin
VolumeShape.CYLINDER:
var horizontal_dist := Vector2(local_point.x, local_point.z).length()
var half_height := volume_size.y * 0.5
var in_outer := horizontal_dist <= volume_radius and abs(local_point.y) <= half_height
var in_inner := horizontal_dist <= volume_radius - fade_out_margin and \
abs(local_point.y) <= half_height - fade_out_margin
return in_outer and not in_inner
return false
func get_fade_factor(point: Vector3) -> float:
if not is_point_inside(point):
return 1.0
if not is_point_in_fade_margin(point):
return 0.0
var local_point := to_local(point)
var min_distance_to_edge := fade_out_margin
match volume_shape:
VolumeShape.BOX:
var half_size := volume_size * 0.5
min_distance_to_edge = minf(min_distance_to_edge, half_size.x - abs(local_point.x))
min_distance_to_edge = minf(min_distance_to_edge, half_size.y - abs(local_point.y))
min_distance_to_edge = minf(min_distance_to_edge, half_size.z - abs(local_point.z))
VolumeShape.SPHERE:
min_distance_to_edge = volume_radius - local_point.length()
VolumeShape.CYLINDER:
var horizontal_dist := Vector2(local_point.x, local_point.z).length()
var half_height := volume_size.y * 0.5
min_distance_to_edge = minf(volume_radius - horizontal_dist, half_height - abs(local_point.y))
return clampf(min_distance_to_edge / fade_out_margin, 0.0, 1.0)

View File

@@ -0,0 +1 @@
uid://burw0wjl85d8a

BIN
dynamic-sky/sky_04_2k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
dynamic-sky/sky_06_2k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
dynamic-sky/sky_11_2k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
dynamic-sky/sky_16_2k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
dynamic-sky/sky_25_2k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

BIN
dynamic-sky/sky_44_2k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB