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
+24
-21
@@ -70,10 +70,13 @@ func _build_geometry() -> void:
|
||||
|
||||
# ── Utility ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String = "") -> StaticBody3D:
|
||||
func _box_static(pos: Vector3, size: Vector3, color: Color, node_name: String = "",
|
||||
acoustic: String = "") -> StaticBody3D:
|
||||
var body := StaticBody3D.new()
|
||||
body.name = node_name if not node_name.is_empty() else "Static_%s" % str(pos)
|
||||
body.position = pos
|
||||
if acoustic != "":
|
||||
body.set_meta("acoustic_material", acoustic)
|
||||
add_child(body)
|
||||
var shape := CollisionShape3D.new()
|
||||
shape.shape = BoxShape3D.new()
|
||||
@@ -114,13 +117,13 @@ func _build_walls_arena() -> void:
|
||||
var wall_color := Color(0.35, 0.28, 0.22)
|
||||
var wall_height := 10.0
|
||||
# North
|
||||
_box_static(Vector3(0, wall_height * 0.5, -40), Vector3(80, wall_height, 0.5), wall_color, "Wall_North")
|
||||
_box_static(Vector3(0, wall_height * 0.5, -40), Vector3(80, wall_height, 0.5), wall_color, "Wall_North", "brick")
|
||||
# South
|
||||
_box_static(Vector3(0, wall_height * 0.5, 40), Vector3(80, wall_height, 0.5), wall_color, "Wall_South")
|
||||
_box_static(Vector3(0, wall_height * 0.5, 40), Vector3(80, wall_height, 0.5), wall_color, "Wall_South", "brick")
|
||||
# East
|
||||
_box_static(Vector3(40, wall_height * 0.5, 0), Vector3(0.5, wall_height, 80), wall_color, "Wall_East")
|
||||
_box_static(Vector3(40, wall_height * 0.5, 0), Vector3(0.5, wall_height, 80), wall_color, "Wall_East", "brick")
|
||||
# West
|
||||
_box_static(Vector3(-40, wall_height * 0.5, 0), Vector3(0.5, wall_height, 80), wall_color, "Wall_West")
|
||||
_box_static(Vector3(-40, wall_height * 0.5, 0), Vector3(0.5, wall_height, 80), wall_color, "Wall_West", "brick")
|
||||
|
||||
|
||||
# ── Ramps ─────────────────────────────────────────────────────────────────────
|
||||
@@ -157,25 +160,25 @@ func _build_platforms() -> void:
|
||||
var plat_color := Color(0.2, 0.45, 0.55) # Teal
|
||||
|
||||
# Low platforms (jump height test)
|
||||
_box_static(Vector3(10, 0.6, -20), Vector3(4, 1.2, 4), plat_color, "Plat_Low_1")
|
||||
_box_static(Vector3(16, 0.6, -20), Vector3(4, 1.2, 4), plat_color, "Plat_Low_2")
|
||||
_box_static(Vector3(22, 0.6, -20), Vector3(4, 1.2, 4), plat_color, "Plat_Low_3")
|
||||
_box_static(Vector3(10, 0.6, -20), Vector3(4, 1.2, 4), plat_color, "Plat_Low_1", "wood")
|
||||
_box_static(Vector3(16, 0.6, -20), Vector3(4, 1.2, 4), plat_color, "Plat_Low_2", "wood")
|
||||
_box_static(Vector3(22, 0.6, -20), Vector3(4, 1.2, 4), plat_color, "Plat_Low_3", "wood")
|
||||
|
||||
# Medium platforms (double jump required)
|
||||
var med_color := Color(0.25, 0.5, 0.4)
|
||||
_box_static(Vector3(10, 2.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_1")
|
||||
_box_static(Vector3(16, 3.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_2")
|
||||
_box_static(Vector3(22, 4.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_3")
|
||||
_box_static(Vector3(28, 5.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_4")
|
||||
_box_static(Vector3(10, 2.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_1", "wood")
|
||||
_box_static(Vector3(16, 3.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_2", "wood")
|
||||
_box_static(Vector3(22, 4.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_3", "wood")
|
||||
_box_static(Vector3(28, 5.5, -28), Vector3(3, 0.5, 3), med_color, "Plat_Med_4", "wood")
|
||||
|
||||
# High tower
|
||||
_box_static(Vector3(28, 4.0, -20), Vector3(5, 8.0, 5), Color(0.3, 0.3, 0.5), "Tower_1")
|
||||
|
||||
# Floating platforms (dash required)
|
||||
var dash_color := Color(0.6, 0.2, 0.5) # Purple
|
||||
_box_static(Vector3(10, 5.0, -35), Vector3(2.5, 0.3, 2.5), dash_color, "Plat_Dash_1")
|
||||
_box_static(Vector3(18, 5.0, -35), Vector3(2.5, 0.3, 2.5), dash_color, "Plat_Dash_2")
|
||||
_box_static(Vector3(26, 5.0, -35), Vector3(2.5, 0.3, 2.5), dash_color, "Plat_Dash_3")
|
||||
_box_static(Vector3(10, 5.0, -35), Vector3(2.5, 0.3, 2.5), dash_color, "Plat_Dash_1", "metal")
|
||||
_box_static(Vector3(18, 5.0, -35), Vector3(2.5, 0.3, 2.5), dash_color, "Plat_Dash_2", "metal")
|
||||
_box_static(Vector3(26, 5.0, -35), Vector3(2.5, 0.3, 2.5), dash_color, "Plat_Dash_3", "metal")
|
||||
|
||||
|
||||
# ── Wall Run Corridor ────────────────────────────────────────────────────────
|
||||
@@ -185,13 +188,13 @@ func _build_wall_run_corridor() -> void:
|
||||
|
||||
# Two parallel walls — spaced for wall running
|
||||
# Left wall
|
||||
_box_static(Vector3(-5, 5, 15), Vector3(0.5, 10, 25), wall_color, "WallRun_Left")
|
||||
_box_static(Vector3(-5, 5, 15), Vector3(0.5, 10, 25), wall_color, "WallRun_Left", "metal")
|
||||
# Right wall
|
||||
_box_static(Vector3(5, 5, 15), Vector3(0.5, 10, 25), wall_color, "WallRun_Right")
|
||||
_box_static(Vector3(5, 5, 15), Vector3(0.5, 10, 25), wall_color, "WallRun_Right", "metal")
|
||||
|
||||
# Obstacles in corridor to jump over
|
||||
_box_static(Vector3(0, 0.75, 10), Vector3(10, 1.5, 1), Color(0.6, 0.3, 0.3), "Obstacle_1")
|
||||
_box_static(Vector3(0, 0.75, 20), Vector3(10, 1.5, 1), Color(0.6, 0.3, 0.3), "Obstacle_2")
|
||||
_box_static(Vector3(0, 0.75, 10), Vector3(10, 1.5, 1), Color(0.6, 0.3, 0.3), "Obstacle_1", "wood")
|
||||
_box_static(Vector3(0, 0.75, 20), Vector3(10, 1.5, 1), Color(0.6, 0.3, 0.3), "Obstacle_2", "wood")
|
||||
|
||||
# Gap in floor (forces wall run)
|
||||
# Just raise the floor slightly on each side with a gap
|
||||
@@ -209,8 +212,8 @@ func _build_speed_corridor() -> void:
|
||||
var corridor_color := Color(0.2, 0.35, 0.2) # Dark green
|
||||
|
||||
# Side rails
|
||||
_box_static(Vector3(30, 1.5, 0), Vector3(0.3, 3, 60), corridor_color, "SpeedRail_Left")
|
||||
_box_static(Vector3(38, 1.5, 0), Vector3(0.3, 3, 60), corridor_color, "SpeedRail_Right")
|
||||
_box_static(Vector3(30, 1.5, 0), Vector3(0.3, 3, 60), corridor_color, "SpeedRail_Left", "metal")
|
||||
_box_static(Vector3(38, 1.5, 0), Vector3(0.3, 3, 60), corridor_color, "SpeedRail_Right", "metal")
|
||||
|
||||
# Speed bumps (small obstacles to hop over)
|
||||
for i in range(-25, 26, 10):
|
||||
|
||||
Reference in New Issue
Block a user