현대 3D 멀티플레이어 게임에서 시각적 그래픽은 몰입감의 절반에 불과합니다. 물리적 거리나 벽 같은 차폐물에 상관없이 동일한 크기로 재생되는 평면적인 2D 효과음은 게임의 현실감을 무너뜨리고 공간 지각을 방해합니다.
로블록스는 `SoundService`와 3D `Attachment`를 통해 정교한 사운드 파이프라인을 제공합니다. 계층적 `SoundGroups`를 설계하고, `RollOffMode` 감쇠 곡선을 조율하며, 구역별 `ReverbZones`와 레이캐스트 기반 `LowPassSoundEffect` 차폐 필터를 결합하면 모바일과 PC 모두에서 콘솔급 3D 입체 음향을 완성할 수 있습니다.
1. 계층적 SoundGroups 설계: 전문적인 오디오 믹싱 버스 구조
깔끔한 사운드 믹싱과 더킹(Ducking), 유저별 음량 조절을 보장하는 구조:
- 루트 믹싱 버스 트리: `SoundService` 하위에 `Master -> (SFX, Ambient, Music, UI, Voice)` 구조로 SoundGroup을 분리합니다. 배경음악을 줄여도 발소리 효과음이 유지되도록 합니다.
- 동적 오디오 더킹(Ducking): 보스 등장이나 NPC 대사 같은 중요 오디오가 재생될 때 트윈(Tween)을 사용해 배경음악 볼륨을 일시적으로 6~12dB 낮춥니다.
- 공통 DSP 이펙트 체인: 개별 사운드마다 효과를 넣지 않고 상위 SoundGroup에 `EqualizerSoundEffect`, `CompressorSoundEffect`를 걸어 CPU 낭비를 방지합니다.
- 메모리 로딩 최적화: 3D 부착 사운드는 청취 범위 내에서만 스트리밍되도록 설정하고, 2D UI 사운드는 지연 없는 즉각 재생을 위해 SoundService에 배치합니다.
2. 3D 공간 거리 감쇠: RollOff 곡선과 리스너(Listener) 좌표
공간에서 소리가 자연스럽게 멀어지도록 물리 전파를 설정하는 원리:
- RollOffMode 선택: `Linear` 방식은 거리가 멀어질 때 부자연스럽게 들립니다. 중간 거리의 은은한 환경음을 살리면서 원거리 소음을 깔끔히 차단하는 `InverseTapered`를 권장합니다.
- Min/Max 거리 보정: `RollOffMinDistance`는 소리가 줄어들기 시작하는 반경(발소리는 5~10 스터드, 총성은 40 스터드)으로 맞추고, `RollOffMaxDistance`로 불필요한 사운드 연산을 차단합니다.
- 리스너 위치 동기화: 기본 카메라는 3인칭 회전 시 좌우 음향 패닝이 왜곡될 수 있습니다. `SoundService:SetListener(Enum.ListenerType.ObjectPosition, character.Head)`로 캐릭터 머리를 기준으로 설정할 수 있습니다.
- 사운드 인스턴스 위치: 3D 공간음은 반드시 작업공간(Workspace)의 Part나 Attachment 아래에 배치해야 합니다. ReplicatedStorage에 두면 2D 모노 음향으로 재생됩니다.
--!strict
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local SoundService = game:GetService("SoundService")
local TweenService = game:GetService("TweenService")
local SpatialAudioManager = {}
type OccludedSound = {
sound: Sound,
emitterPart: BasePart,
lowPass: LowPassSoundEffect,
originalVolume: number
}
local trackedSounds: { OccludedSound } = {}
local RAY_PARAMS = RaycastParams.new()
RAY_PARAMS.FilterType = RaycastFilterType.Exclude
-- 월드 사운드를 등록하여 벽 차폐 시 로우패스 필터 적용
function SpatialAudioManager.RegisterOccludedSound(sound: Sound, emitter: BasePart)
local lpf = sound:FindFirstChildOfClass("LowPassSoundEffect")
if not lpf then
lpf = Instance.new("LowPassSoundEffect")
lpf.CutoffFrequency = 20000
lpf.Parent = sound
end
table.insert(trackedSounds, {
sound = sound,
emitterPart = emitter,
lowPass = lpf,
originalVolume = sound.Volume
})
end
-- 카메라(청취자) 시선과 사운드 발생체 간의 물리 차폐 판정
function SpatialAudioManager.UpdateOcclusion(camera: Camera)
local listenerPos = camera.CFrame.Position
RAY_PARAMS.FilterDescendantsInstances = { camera }
for _, item in ipairs(trackedSounds) do
if not item.sound.IsPlaying then continue end
local emitterPos = item.emitterPart.Position
local direction = emitterPos - listenerPos
local distance = direction.Magnitude
if distance > item.sound.RollOffMaxDistance then continue end
local hit = Workspace:Raycast(listenerPos, direction, RAY_PARAMS)
local isOccluded = hit and hit.Instance and hit.Instance ~= item.emitterPart and hit.Instance.CanCollide
-- 벽에 막히면 1.2 kHz 먹먹한 소리로 감쇠, 시야 확보 시 20 kHz 원음 복원
local targetCutoff = isOccluded and 1200 or 20000
local targetVol = isOccluded and (item.originalVolume * 0.45) or item.originalVolume
TweenService:Create(item.lowPass, TweenInfo.new(0.25), { CutoffFrequency = targetCutoff }):Play()
TweenService:Create(item.sound, TweenInfo.new(0.25), { Volume = targetVol }):Play()
end
end
return SpatialAudioManager
3. 실시간 레이캐스트 차폐: 벽 너머의 먹먹한(LowPass) 소리 구현
콘크리트 벽 뒤의 발소리가 맑게 들리는 비현실적 현상을 차단하는 기법:
- 시선(Line-of-Sight) 광선 추적: 리스너와 사운드 발생체 사이에 레이캐스트를 투사하여 `CanCollide = true`인 고체 벽이 가로막고 있는지 판정합니다.
- 로우패스 필터링 감쇠: 차폐 발생 시 `LowPassSoundEffect.CutoffFrequency`를 20,000Hz에서 1,200Hz 수준으로 낮춰 고주파를 차단, 벽을 뚫고 나오는 먹먹한 소리를 연출합니다.
- 재질별 흡음 계수 반영: 충돌 파트의 `Material`을 검사하여 유리는 높은 주파수(4,000Hz)를 통과시키고, 콘크리트나 암석은 강한 감쇠(800Hz)를 적용합니다.
- 연산 분산 최적화: 매 프레임 모든 사운드를 레이캐스트하지 않고, 0.1~0.2초 주기로 분할 실행하여 CPU 부하를 제어합니다.
4. 체적(Volumetric) 동적 리버브 존 환경 설계
야외, 동굴, 좁은 복도, 대성당 등 공간 특성에 맞는 음향 반사 구현:
- 기본 리버브 프리셋: `SoundService.AmbientReverb`는 `Cave`, `ConcertHall`, `StoneCorridor`, `Underwater` 등 최적화된 내장 음향 환경을 지원합니다.
- 공간 트리거 영역: 특정 방이나 건물에 투명한 감지 박스를 두고 플레이어가 진입할 때 1.5초에 걸쳐 부드럽게 리버브 파라미터를 보간합니다.
- 국소 리버브(Local Reverb): 전역 리버브를 갑자기 바꾸는 대신 특정 사운드그룹에 국소 ReverbSoundEffect를 걸어 자연스러운 방별 울림을 분리합니다.
- 수중 음향 왜곡: 플레이어 머리가 물(Water)에 잠기면 0.85x 피치 시프트와 600Hz 로우패스를 즉시 가동하여 수중 질감을 완성합니다.
5. 프로덕션 오디오 최적화 및 모바일 보이스 예산
사운드 누락(채널 초과) 없이 매끄럽게 돌아가는 대규모 음향 설계:
- 동시 재생 채널 한계: 모바일 기기는 보통 32~64개의 하드웨어 믹싱 채널만 지원합니다. 불필요한 사운드가 남발되면 결정적인 전투 효과음이 무음 처리됩니다.
- 거리 기반 보이스 컬링: `RollOffMaxDistance`를 벗어난 원거리 사운드는 볼륨만 0으로 두지 말고 즉시 `Stop()` 처리하여 오디오 보이스를 반환합니다.
- 모노(Mono) 음원 사용: 3D 위치 기반 사운드는 단일 채널 모노 파일로 제작해야 스테레오 위상 왜곡을 방지하고 메모리 사용량을 50% 절감할 수 있습니다.
- 핵심 전투 효과음 프리로드: `ContentProvider:PreloadAsync`를 활용해 발소리, 총기 재장전, 실드 파괴 등 반응성이 생명인 사운드를 로딩 시 미리 캐싱합니다.
Frequently Asked Questions
3D 공간 음향에 왜 스테레오가 아닌 모노(Mono) 파일을 써야 하나요?
스테레오 파일은 좌우 채널이 분리되어 있어 3D 공간 상의 단일 점(Point Source)에 배치될 때 위상 상쇄(Phase Cancellation)가 일어나 소리가 왜곡되고 메모리도 두 배로 소모됩니다. 모노 파일이 가장 정확하게 공간화됩니다.
사운드 차폐를 위해 레이캐스트를 쏘면 렉이 걸리지 않나요?
주변 50~60 스터드 내의 재생 중인 소리만 필터링하고 0.1초 간격으로 분할 검사하면 프레임당 0.1ms 미만의 CPU만 소모하여 성능에 영향을 주지 않습니다.
동굴에 들어갔을 때 배경음악까지 먹먹해지는 것을 막으려면?
BGM 사운드는 별도의 `Music` SoundGroup으로 라우팅하여 환경음(WorldSFX)에 적용되는 DSP 이펙트 체인의 간섭을 받지 않도록 분리해야 합니다.