Quality Gate / Quality Gate (pull_request) Failing after 3m10s
A GPUParticles3D emitter in the dive site re-centres on the active camera every frame and restarts after a jump, so flakes keep their own slow sink and drift while the submarine moves through them. Soft additive billboard discs dimmed by the water column and faded over their life replace the sphere volume that was attached to the submarine and turned with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
77 lines
2.8 KiB
GDScript
77 lines
2.8 KiB
GDScript
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
|