feat: material-aware sound occlusion + early reflections for localization
New Acoustics system (globals/acoustics.gd): - Occlusion: rays from listener to source collect up to 3 occluding bodies; each surface's material adds transmission loss and lowers the lowpass cutoff. A gunshot through wood stays warm (-8dB, 1.4kHz), brick muffles hard (-15dB, 600Hz), concrete harder (-18dB, 450Hz), metal keeps more highs (-11dB, 1kHz), glass barely muffles (-5dB, 2.6kHz). Applied via each AudioStreamPlayer3D's attenuation filter. - Early reflections: loud sounds (gunfire, explosions) probe 8 directions for nearby walls; each close surface plays a quiet, delayed (path/343ms), material-filtered copy from the mirror point — shots audibly slap back off structures so players can triangulate. Sub-30ms echoes are skipped (they perceptually merge with the direct sound). - Surface classification: builder-set acoustic_material meta -> node-name keywords -> metallic-material inference -> concrete default; cached. Integration: - AudioManager.play_3d applies occlusion at fire time and re-filters all playing positional sounds every 0.12s (walking behind a wall mid-sound audibly muffles it); flight loops opt in via "acoustic_occluded" group - Remote players' gunshots were entirely SILENT — rpc_play_fire_effects now plays the right weapon's shot at the shooter's position, occluded and reflected like everything else - Remote players' footsteps were also silent — now positional + occluded, cadence scaled to their speed - Test level tagged: brick arena walls, wooden platforms/obstacles, metal wall-run corridor/rails/dash platforms; dust2 sandstone reads as brick Tests: debug/acoustics_test.gd (12 checks: per-material loss ordering, multi-wall stacking, reflection delay/filter, name classification) — all pass; movement 11/11; smoke 0 failures. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
44460b52ae
commit
a51595e3b9
@@ -38,6 +38,7 @@ var ragdoll_instance: Node3D
|
||||
var footstep_player: AudioStreamPlayer
|
||||
var footstep_streams: Array = []
|
||||
var land_player: AudioStreamPlayer
|
||||
var _remote_footstep_timer: float = 0.0
|
||||
var dash_player: AudioStreamPlayer
|
||||
var recent_attackers: Dictionary = {} # attacker_id: timestamp
|
||||
var hit_player: AudioStreamPlayer
|
||||
@@ -499,7 +500,15 @@ func server_play_fire_effects(origin: Vector3, target_or_dir: Vector3, weapon_na
|
||||
func rpc_play_fire_effects(origin: Vector3, target_or_dir: Vector3, _weapon_name: String, is_hitscan: bool) -> void:
|
||||
# Local player already spawned effects locally with 0 latency
|
||||
if is_multiplayer_authority(): return
|
||||
|
||||
|
||||
# Positional gunshot for the remote shooter — occlusion + reflections via
|
||||
# AudioManager make it locatable ("that came from behind the wooden wall").
|
||||
var am = get_tree().root.get_node_or_null("AudioManager")
|
||||
if am:
|
||||
var sid: String = am.weapon_sound_id(_weapon_name)
|
||||
if sid != "" and am.has_sound(sid):
|
||||
am.play_3d(sid, origin)
|
||||
|
||||
if is_hitscan:
|
||||
# Spawn a standalone tracer directly into the scene
|
||||
_spawn_remote_tracer(origin, target_or_dir)
|
||||
@@ -978,6 +987,19 @@ func _process(delta: float) -> void:
|
||||
if synced_weapon_path != "" and synced_weapon_path != visual.get_meta("current_weapon_path", ""):
|
||||
visual.set_weapon(synced_weapon_path)
|
||||
visual.set_meta("current_weapon_path", synced_weapon_path)
|
||||
|
||||
# Positional footsteps for remote players (their movement states don't
|
||||
# run here). Occlusion via AudioManager makes them read through walls
|
||||
# correctly — the classic "someone's above me on the wooden platform".
|
||||
if not is_dead and synced_movement_state == "ground" and synced_movement_speed > 1.0:
|
||||
_remote_footstep_timer -= delta
|
||||
if _remote_footstep_timer <= 0.0:
|
||||
var am = get_tree().root.get_node_or_null("AudioManager")
|
||||
if am and am.has_sound("footstep"):
|
||||
am.play_3d("footstep", global_position + Vector3(0, -0.8, 0), -8.0)
|
||||
_remote_footstep_timer = maxf(0.2, 3.0 / synced_movement_speed)
|
||||
else:
|
||||
_remote_footstep_timer = 0.0
|
||||
return
|
||||
|
||||
# Update UI
|
||||
|
||||
Reference in New Issue
Block a user