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:
@@ -19,6 +19,7 @@ extends Node
|
||||
const K := "res://assets/audio/sfx/kenney/"
|
||||
const S := "res://assets/audio/sfx/synth/"
|
||||
const C := "res://assets/audio/sfx/creatures/"
|
||||
const T := "res://assets/audio/sfx/thunder/"
|
||||
const EARSHOT := 45.0 ## a server sends a sound to players this close
|
||||
|
||||
## id -> files (or a [prefix, count] pair), volume (dB), pitch range, reach (m),
|
||||
@@ -28,6 +29,10 @@ const SOUNDS := {
|
||||
"tree_fall": {"set": [K + "wood_heavy_%d.ogg", 5], "db": 0.0, "pitch": [0.6, 0.7], "reach": 60.0, "with": "creak"},
|
||||
"creak": {"set": [K + "creak_%d.ogg", 3], "db": -2.0, "pitch": [0.8, 0.95], "reach": 50.0},
|
||||
"mine": {"set": [K + "mine_%d.ogg", 5], "db": -1.0, "pitch": [0.9, 1.1]},
|
||||
# Thunder (0.6.2, CC0 recordings: assets/audio/CREDITS.md). The weather
|
||||
# plays the storm's own (weather_fx.gd); a strike on a tree cracks from it.
|
||||
"thunder_near": {"set": [T + "thunder_near_%d.ogg", 3], "db": 0.0, "pitch": [0.92, 1.05], "reach": 400.0},
|
||||
"thunder_far": {"set": [T + "thunder_far_%d.ogg", 3], "db": -5.0, "pitch": [0.9, 1.05], "reach": 400.0},
|
||||
"rock_break": {"set": [K + "mine_%d.ogg", 5], "db": 1.0, "pitch": [0.6, 0.7], "reach": 50.0},
|
||||
"rustle": {"set": [K + "cloth_%d.ogg", 4], "db": -4.0, "pitch": [0.7, 0.9]},
|
||||
"pickup": {"set": [K + "leather_%d.ogg", 3], "db": -6.0, "pitch": [0.95, 1.15], "reach": 15.0},
|
||||
@@ -196,6 +201,17 @@ func me(id: String) -> void:
|
||||
_log(id, null)
|
||||
_play2(id)
|
||||
|
||||
## A random one of sound `id`'s files, and its volume: for a player of your
|
||||
## own (the weather's thunder). Not logged: call log_heard() when it plays.
|
||||
func stream_for(id: String) -> AudioStream:
|
||||
return _pick(id) if SOUNDS.has(id) else null
|
||||
|
||||
func db_for(id: String) -> float:
|
||||
return float(SOUNDS.get(id, {}).get("db", 0.0))
|
||||
|
||||
func log_heard(id: String) -> void:
|
||||
_log(id, null)
|
||||
|
||||
## Menus and the HUD.
|
||||
func ui(id: String) -> void:
|
||||
_log(id, null)
|
||||
|
||||
@@ -92,6 +92,7 @@ func pick_tree() -> Node3D:
|
||||
func strike(n: Node3D) -> void:
|
||||
strikes += 1
|
||||
show_strike(n)
|
||||
Sfx.at("thunder_near", n.global_position + Vector3(0, 6, 0)) # (a crack from the tree itself)
|
||||
if world.server_mode:
|
||||
WorldNet.struck(n)
|
||||
for p in world.players():
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user