Real thunder (Gitea #4): six CC0 recordings, close cracks and distant rolls, in place of the synthesised rumble; a storm's flash is followed by a roll seconds later or a crack sooner, quieter indoors, and a strike on a tree cracks from the tree

This commit is contained in:
Paul Bailey
2026-11-08 20:29:48 +00:00
parent 07b8db1683
commit 8f3c58c810
18 changed files with 132 additions and 14 deletions

View File

@@ -7,8 +7,11 @@ extends Node3D
## - Ground wetness, settled snow and wind strength are global shader uniforms
## (weather_wet, weather_snow, weather_wind) read by the terrain, grass,
## foliage and rock shaders.
## - Sound is synthesised: filtered noise for rain and wind, a decaying
## low rumble for thunder. No audio assets, nothing to license.
## - Rain and wind are synthesised (filtered noise). Thunder is recorded
## (CC0, 0.6.2: assets/audio/sfx/thunder, Sfx "thunder_far"/"thunder_near"):
## after each flash in a storm, usually a distant roll a few seconds later,
## sometimes a nearer crack sooner. Quieter under a roof. (A strike on a
## tree near you cracks from the tree itself: hazards.gd.)
##
## Client-side only: in M2 the server runs weather.gd and never this.
@@ -31,10 +34,11 @@ var _rng := RandomNumberGenerator.new()
var _rain_lp := 0.0
var _rain_hp := 0.0
var _brown := 0.0
var _brown2 := 0.0
var _gust_t := 0.0
var _thunder_env := 0.0
var _thunder_delay := -1.0
var _thunder_id := ""
var _thunder: AudioStreamPlayer
const NEAR_CHANCE := 0.3 ## of a storm's flashes, the share that are close
var _seen_strikes := 0
var _rain_amp := 0.0
var _wind_amp := 0.0
@@ -83,6 +87,10 @@ func setup(w: Node, p: Node3D) -> void:
add_child(_audio)
_audio.play()
_playback = _audio.get_stream_playback()
_thunder = AudioStreamPlayer.new()
_thunder.name = "Thunder"
_thunder.bus = "SFX" if AudioServer.get_bus_index("SFX") >= 0 else "Master"
add_child(_thunder)
func _make_particles(amount: int, life: float, pm: ParticleProcessMaterial, mesh: Mesh) -> GPUParticles3D:
var p := GPUParticles3D.new()
@@ -173,13 +181,32 @@ func _process(delta: float) -> void:
_wind_amp = weather.wind * weather.wind * 0.22 * (0.35 if sheltered else 1.0)
if weather.lightning_count != _seen_strikes:
_seen_strikes = weather.lightning_count
_thunder_delay = _rng.randf_range(0.5, 2.5)
lightning()
if _thunder_delay >= 0.0:
_thunder_delay -= delta
if _thunder_delay < 0.0:
_thunder_env = 1.0
_thunder_now()
_fill_audio()
## A flash in the sky: its thunder follows, after the time the sound takes
## to come (a near one sooner, a distant one later).
func lightning() -> void:
var near := _rng.randf() < NEAR_CHANCE
_thunder_id = "thunder_near" if near else "thunder_far"
_thunder_delay = _rng.randf_range(0.3, 0.9) if near else _rng.randf_range(1.5, 4.0)
func _thunder_now() -> void:
Sfx.log_heard(_thunder_id)
if _thunder == null:
return
var st: AudioStream = Sfx.stream_for(_thunder_id)
if st == null:
return
_thunder.stream = st
_thunder.volume_db = Sfx.db_for(_thunder_id) - 8.0 * _muffle
_thunder.pitch_scale = _rng.randf_range(0.9, 1.05)
_thunder.play()
func _set_amount(p: GPUParticles3D, a: float) -> void:
p.amount_ratio = a
p.emitting = a > 0.0
@@ -194,7 +221,6 @@ func _fill_audio() -> void:
var rain_cut := lerpf(0.55, 0.12, _muffle)
var buf := PackedVector2Array()
buf.resize(n)
var thunder_decay := exp(-1.0 / (MIX_RATE * 0.9))
for i in range(n):
var white := _rng.randf() * 2.0 - 1.0
# Rain: band-limited hiss (low-pass, minus a slower low-pass).
@@ -206,12 +232,6 @@ func _fill_audio() -> void:
_gust_t += 1.0 / MIX_RATE
var gust := 0.6 + 0.4 * sin(_gust_t * 0.7) * sin(_gust_t * 0.23 + 1.0)
var wind_s := _brown * _wind_amp * gust * 2.0
# Thunder: a heavier brown rumble with a long decay.
var thunder := 0.0
if _thunder_env > 0.001:
_brown2 = clampf(_brown2 * 0.9995 + white * 0.02, -1.0, 1.0)
thunder = _brown2 * _thunder_env * 2.2
_thunder_env *= thunder_decay
var s := clampf(rain + wind_s + thunder, -1.0, 1.0)
var s := clampf(rain + wind_s, -1.0, 1.0)
buf[i] = Vector2(s, s)
_playback.push_buffer(buf)