extends AudioStreamPlayer3D class_name SpatialAudioPlayer @export var maxRaycastDistance: float = 30.0 @export var updateFrequencySeconds: float = 0.5 @export var maxReverbWetness: float = 0.5 @export var wallLowpassCutoffAmount: int = 600 var raycastArray: Array = [] var distanceArray: Array = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] var lastUpdateTime: float = 0.0 var updateDistances: bool = true var currentRaycastIndex: int = 0 var audioBusIndex = null var audioBusName: String = "" var reverbEffect: AudioEffectReverb var lowpassFilterEffect: AudioEffectLowPassFilter var targetLowpassCutoff: float = 20000 var targetReverbRoomSize: float = 0.0 var targetReverbWetness: float = 0.0 var targetVolumeDb: float = 0.0 func _ready() -> void: audioBusIndex = AudioServer.bus_count audioBusName = "SpatialBus#"+str(audioBusIndex) AudioServer.add_bus(audioBusIndex) AudioServer.set_bus_name(audioBusIndex, audioBusName) AudioServer.set_bus_send(audioBusIndex, bus) self.bus = audioBusName AudioServer.add_bus_effect(audioBusIndex, AudioEffectReverb.new(), 0) reverbEffect = AudioServer.get_bus_effect(audioBusIndex, 0) AudioServer.add_bus_effect(audioBusIndex, AudioEffectLowPassFilter.new(), 1) lowpassFilterEffect = AudioServer.get_bus_effect(audioBusIndex, 1) targetVolumeDb = volume_db volume_db = -60 $RayCastDown.target_position = Vector3(0, -maxRaycastDistance, 0) $RayCastLeft.target_position = Vector3(maxRaycastDistance, 0, 0) $RayCastRight.target_position = Vector3(-maxRaycastDistance, 0, 0) $RayCastForward.target_position = Vector3(0, 0, maxRaycastDistance) $RayCastForwardLeft.target_position = Vector3(0, 0, maxRaycastDistance) $RayCastForwardRight.target_position = Vector3(0, 0, maxRaycastDistance) $RayCastBackwardRight.target_position = Vector3(0, 0, -maxRaycastDistance) $RayCastBackwardLeft.target_position = Vector3(0, 0, -maxRaycastDistance) $RayCastBackward.target_position = Vector3(0, 0, -maxRaycastDistance) $RayCastUp.target_position = Vector3(0, maxRaycastDistance, 0) raycastArray.append($RayCastDown) raycastArray.append($RayCastLeft) raycastArray.append($RayCastRight) raycastArray.append($RayCastForward) raycastArray.append($RayCastForwardLeft) raycastArray.append($RayCastForwardRight) raycastArray.append($RayCastBackwardRight) raycastArray.append($RayCastBackwardLeft) raycastArray.append($RayCastBackward) raycastArray.append($RayCastUp) play() func onUpdateRaycastDistance(raycast: RayCast3D, raycastIndex: int) -> void: raycast.force_raycast_update() var collider = raycast.get_collider() if collider != null: distanceArray[raycastIndex] = self.global_position.distance_to(raycast.get_collision_point()) else: distanceArray[raycastIndex] = -1 raycast.enabled = false func onUpdateSpatialAudio(player: Node3D) -> void: onUpdateReverb(player) onUpdateLowpassFilter(player) func onUpdateReverb(player: Node3D) -> void: if reverbEffect != null: var roomSize: float = 0.0 var wetness: float = 0.0 for distance in distanceArray: if distance >= 0: roomSize += (distance / maxRaycastDistance) / (float(distanceArray.size())) roomSize = min(roomSize, 1.0) else: wetness -= 1.0 / float(distanceArray.size()) wetness = max(wetness, 0.0) targetReverbWetness = wetness targetReverbRoomSize = roomSize func onUpdateLowpassFilter(player : Node3D): if lowpassFilterEffect != null: var query = PhysicsRayQueryParameters3D.create(self.global_position,self.global_position + (player.global_position - self.global_position).normalized() * maxRaycastDistance, $RayCastForward.get_collision_mask()) var spaceState = get_world_3d().direct_space_state var result = spaceState.intersect_ray(query) var lowpassCutoff = 20000 if !result.is_empty(): var rayDistance = self.global_position.distance_to(result["position"]) var distanceToPlayer = self.global_position.distance_to(player.global_position) var wallToPlayerRatio = rayDistance / max(distanceToPlayer, 0.001) if rayDistance < distanceToPlayer: lowpassCutoff = wallLowpassCutoffAmount * wallToPlayerRatio targetLowpassCutoff = lowpassCutoff func lerpParameters(delta: float) -> void: volume_db = lerp(volume_db, targetVolumeDb, delta) lowpassFilterEffect.cutoff_hz = lerp(lowpassFilterEffect.cutoff_hz, targetLowpassCutoff, delta * 5.0) reverbEffect.wet = lerp(reverbEffect.wet, targetReverbRoomSize * maxReverbWetness, delta * 5.0) reverbEffect.room_size = lerp(reverbEffect.room_size, targetReverbRoomSize, delta * 5.0) func _physics_process(delta: float) -> void: lastUpdateTime += delta if updateDistances: onUpdateRaycastDistance(raycastArray[currentRaycastIndex], currentRaycastIndex) currentRaycastIndex += 1 if currentRaycastIndex >= distanceArray.size(): currentRaycastIndex = 0 updateDistances = false if lastUpdateTime > updateFrequencySeconds: var playerCamera = get_viewport().get_camera_3d() if playerCamera != null: onUpdateSpatialAudio(playerCamera) updateDistances = true lastUpdateTime = 0.0 lerpParameters(delta) print("Audio:", global_position) print("Camera:", get_viewport().get_camera_3d().global_position)