@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) else: var procedural_tex := ProceduralCloudTexture.create_default_cloud_texture(config.layer_type) material.set_shader_parameter("cloud_texture", procedural_tex) 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 current_offset: Variant = material.get_shader_parameter("flow_offset") var flow_offset: float = current_offset if current_offset != null else 0.0 flow_offset += config.flow_speed * wind_spd * delta material.set_shader_parameter("flow_offset", flow_offset) material.set_shader_parameter("flow_direction", wind_dir) material.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()