class_name MarineSnow extends GPUParticles3D ## Suspended particles (marine snow, sediment) around the viewer. The emitter is a ## world-space box re-centred on the active camera every frame, so flakes keep their ## own slow drift while the submarine moves through them instead of travelling with ## it. Sinks a few centimetres per second with a little turbulence, as detritus does. const BOX_EXTENTS_M := Vector3(16.0, 10.0, 16.0) const PARTICLE_COUNT := 2400 const LIFETIME_S := 16.0 const SINK_SPEED_M_S := 0.035 const DRIFT_SPEED_M_S := 0.06 const FLAKE_SIZE_M := Vector2(0.05, 0.16) const SNOW_SHADER = preload("res://game/world/sites/marine_snow.gdshader") func _ready() -> void: local_coords = false amount = PARTICLE_COUNT lifetime = LIFETIME_S preprocess = LIFETIME_S fixed_fps = 30 visibility_aabb = AABB(-BOX_EXTENTS_M * 1.5, BOX_EXTENTS_M * 3.0) process_material = _process_material() var quad := QuadMesh.new() quad.size = Vector2.ONE var material := ShaderMaterial.new() material.shader = SNOW_SHADER quad.material = material draw_pass_1 = quad emitting = true ## A jump larger than the box (spawn, teleport, first camera) would leave the viewer in ## empty water for a whole lifetime: the emitter restarts and preprocesses in place. func _process(_delta: float) -> void: var camera := get_viewport().get_camera_3d() if is_inside_tree() else null if camera == null: return var jumped := global_position.distance_to(camera.global_position) > BOX_EXTENTS_M.y global_position = camera.global_position if jumped: restart() func _process_material() -> ParticleProcessMaterial: var material := ParticleProcessMaterial.new() material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX material.emission_box_extents = BOX_EXTENTS_M material.direction = Vector3(1.0, 0.0, 0.3) material.spread = 180.0 material.initial_velocity_min = DRIFT_SPEED_M_S * 0.3 material.initial_velocity_max = DRIFT_SPEED_M_S material.gravity = Vector3(0.0, -SINK_SPEED_M_S, 0.0) material.turbulence_enabled = true material.turbulence_noise_strength = 0.4 material.turbulence_noise_scale = 6.0 material.turbulence_noise_speed_random = 0.2 material.turbulence_influence_min = 0.02 material.turbulence_influence_max = 0.06 material.scale_min = FLAKE_SIZE_M.x material.scale_max = FLAKE_SIZE_M.y # Fade in and out through alpha so flakes never pop at the box edge or at respawn. var alpha := Gradient.new() alpha.set_offset(0, 0.0) alpha.set_color(0, Color(1.0, 1.0, 1.0, 0.0)) alpha.add_point(0.15, Color(1.0, 1.0, 1.0, 1.0)) alpha.add_point(0.85, Color(1.0, 1.0, 1.0, 1.0)) alpha.set_offset(alpha.get_point_count() - 1, 1.0) alpha.set_color(alpha.get_point_count() - 1, Color(1.0, 1.0, 1.0, 0.0)) var ramp := GradientTexture1D.new() ramp.gradient = alpha material.color_ramp = ramp return material