오디오

풀링된 소스와 일시정지 인식 페이드를 갖춘 Unity AudioMixer 기반의 음악·효과음·보이스.

세 개의 AudioMixer 채널로 음악·효과음·보이스 재생 · 풀링된 소스, 자동 페이드, 일시정지 인식 동작 · No-op 대체 구현: NullAudio

CGS는 게임 내 모든 사운드를 **IAudioService**로 라우팅합니다 — Unity AudioMixer 위의 세 전용 채널: Music(루프 재생되는 배경 음악), Sfx(원샷 효과음, 2D 또는 3D 위치 지정), Voice(대사와 안내 음성, 한 번에 한 클립). 풀링된 AudioSource 컴포넌트 덕분에 효과음이 연달아 터져도 가비지 컬렉션 스파이크가 없고, 페이드와 크로스페이드가 내장되어 있으며, 일시정지는 플레이어가 기대하는 대로 동작합니다 — 음악은 계속 흐르고 게임플레이 효과음은 멈춥니다. 네트워크 호출도, 서드파티 플러그인도 없으며 IL2CPP에 안전합니다.

주요 기능

  • 자동 페이드가 있는 음악. PlayMusic(clip)은 트랙을 페이드 인합니다. 다른 클립으로 다시 호출하면 뚝 끊는 대신 크로스페이드합니다. StopMusic()은 페이드 아웃합니다.
  • 풀링된 효과음. PlaySfx(clip)은 미리 할당된 풀에서 소스를 꺼내고, 반복 재생돼도 자연스럽게 들리도록 약간의 랜덤 피치 변화(기본 약 ±10%)를 적용하며, 클립이 끝나면 소스를 풀로 돌려보냅니다. 모든 소스가 사용 중일 때는 가장 오래 재생 중인 효과음을 멈춰 자리를 만듭니다.
  • 한 번에 하나만 재생되는 보이스. PlayVoice(clip)은 재생 중이던 보이스 라인을 멈추고 새 클립을 시작합니다 — 대사가 스스로 겹치는 일이 없습니다.
  • 믹서 스냅샷. 스냅샷은 저장된 믹서 볼륨 레벨 세트입니다. 일반 게임플레이, 대사 아래로 음악이 낮아진 상태, 메뉴 뒤에서 먹먹해진 상태 같은 프리셋 간 전환을 호출 한 번과 부드러운 페이드로 처리합니다.
  • 실시간 볼륨 설정. Configuration 서비스를 통해 연결된 볼륨 슬라이더가 실시간으로 믹서에 반영됩니다.

서비스 가져오기

[DefaultExecutionOrder(100)]가 붙은 클래스의 Awake에서 리졸브하고 캐시하세요:

using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)]
public class SoundManager : MonoBehaviour
{
    private IAudioService _audio;

    private void Awake()
    {
        _audio = ServiceLocator.Resolve<IAudioService>();
    }
}

Update() 안에서 Resolve를 호출하지 마세요 — 조회는 딕셔너리 검색입니다. 한 번만 캐시하세요.

API 레퍼런스

배경 음악

void PlayMusic(AudioClip clip, float fadeInSeconds = -1f, float crossfadeSeconds = -1f)

페이드 인과 함께 루프 트랙을 시작합니다. 이미 트랙이 재생 중이면 대신 crossfadeSeconds에 걸쳐 새 트랙으로 크로스페이드합니다. -1f를 넘기면 기본값(1.5초 페이드 인, 1.0초 크로스페이드)을 사용하고, 0f를 넘기면 즉시 전환됩니다.

void StopMusic(float fadeOutSeconds = -1f) // Fade out and stop (default 2.0 seconds)
bool IsMusicPlaying { get; }               // true while a track is playing

효과음 (2D와 3D)

bool PlaySfx(AudioClip clip, float pitchVariation = -1f)
bool PlaySfx(AudioClip clip, Vector3 worldPosition, float pitchVariation = -1f)

첫 번째 오버로드는 평면 2D 효과음을 재생하고, 두 번째는 월드 좌표에서 거리 기반 감쇠(rolloff)가 적용된 3D 효과음을 재생합니다. 둘 다 성공 시 true를 반환하고, 클립이 null이거나 아직 로드되지 않았거나 풀이 요청을 거부하면 false를 반환합니다. pitchVariation은 랜덤 피치의 절반 범위입니다: -1f는 기본값(약 ±10%)을 사용하고, 0f는 변화를 끄며, 0.5f를 넘는 값은 경고와 함께 클램프됩니다.

int ActiveSfxCount { get; } // Number of effect sources currently playing

보이스와 대사

void PlayVoice(AudioClip clip) // Starts the clip; any prior voice clip stops immediately
void StopVoice()               // Stops the current voice clip
bool IsVoicePlaying { get; }   // true while voice is playing

믹서 스냅샷과 전체 정지

void SetSnapshot(AudioSnapshot snapshot, float transitionSeconds = -1f)
void SetSnapshot(string snapshotName, float transitionSeconds = -1f)
void StopAll()

enum 오버로드는 내장 프리셋 중 하나로 전환합니다 — Normal, DialogueDucked(대사 아래로 음악과 효과음을 약 6 dB 낮춤), MenuOpen. string 오버로드는 직접 만든 믹서에 추가한 아무 스냅샷이나 대상으로 삼을 수 있습니다. StopAll()은 페이드 없이 모든 채널을 즉시 무음으로 만듭니다.

전체 예제

using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)]
public class GameAudio : MonoBehaviour
{
    [SerializeField] private AudioClip _explosionClip;

    private IAudioService _audio;

    private void Awake()
    {
        _audio = ServiceLocator.Resolve<IAudioService>();
    }

    public void PlayBattleMusic(AudioClip clip)
    {
        // Fade in over 1.5 seconds; if music is already playing,
        // the service crossfades to the new track automatically.
        _audio.PlayMusic(clip, fadeInSeconds: 1.5f);
    }

    public void PlayExplosion(Vector3 position)
    {
        // 3D effect with the default pitch variation (about ±10%)
        _audio.PlaySfx(_explosionClip, position);
    }

    public void DialogueStart()
    {
        // Duck music and effects while dialogue plays
        _audio.SetSnapshot(AudioSnapshot.DialogueDucked);
    }

    public void PlayDialogue(AudioClip voiceClip)
    {
        _audio.PlayVoice(voiceClip);
    }

    public void DialogueEnd()
    {
        _audio.SetSnapshot(AudioSnapshot.Normal);
    }
}

끄기

ServiceLocator.Replace<IAudioService>(new NullAudio());

이 한 줄이면 서비스가 무음이 됩니다: 모든 메서드가 no-op이 되고 게임 코드는 아무것도 바꿀 필요가 없습니다. 테스트, 보이스오버 녹음 세션, 또는 자체 오디오 미들웨어를 쓸 때 유용합니다. AudioListener는 영향을 받지 않습니다 — 카메라에는 여전히 하나 필요하지만, 그것을 통해 아무것도 재생되지 않습니다.

흔한 함정

  • 메인 스레드 전용. 모든 PlayMusic, PlaySfx, PlayVoice, SetSnapshot 호출은 메인 스레드에서 이루어져야 합니다. 워커 스레드에서 사운드를 트리거해야 한다면 먼저 호출을 메인 스레드로 마샬링하세요 — Scheduler가 대신 처리해 줍니다.

  • 일시정지 동작이 내장되어 있습니다. 음악 페이드는 Time 서비스의 Background 클럭에서 돌아가므로 게임플레이가 일시정지돼도 음악은 계속 재생됩니다. 효과음 타이머는 Gameplay 클럭에서 돌아가므로 효과음은 멈춥니다. "음악은 계속, 효과음은 정지"에 추가 코드가 전혀 필요 없습니다.

  • 바쁜 씬 전에 풀을 프리웜하세요. 기본적으로 시작 시 효과음 소스 8개가 미리 할당됩니다. 첫 전투 씬에서 폭발 20개가 동시에 터지면 풀이 즉석에서 늘어나며 프레임 히치를 유발할 수 있습니다. 시작 시 AudioOptions.sfxPoolPrewarmCount를 올려 더 많이 미리 할당하세요 — 약간의 시작 메모리로 런타임 버벅임을 0으로 만들 수 있습니다.

  • 볼륨 슬라이더는 flush해야 적용됩니다. Configuration 서비스는 설정 쓰기를 배치 처리하므로 Set(new AudioSettings { ... })만 호출하면 변경이 큐에 들어갈 뿐 — 믹서에는 아직 아무것도 반영되지 않습니다. 실시간 슬라이더 피드백이 필요하면 Set 직후 리졸브해 둔 IConfiguration에서 FlushPending<AudioSettings>()를 호출하세요. flush되면 믹서가 즉시 갱신됩니다.

  • 커스텀 AudioMixer 파라미터 이름. AudioOptions를 통해 자체 AudioMixer를 제공한다면 네 개의 파라미터를 노출해야 합니다: MasterVolume, MusicVolume, SfxVolume, VoiceVolume(모두 dB 단위). 이름이 다르다면 AudioOptions.masterParam, musicParam, sfxParam, voiceParam으로 서비스에 알려주세요.

  • 클립 길이가 효과음 정리를 결정합니다. PlaySfx(clip) 후 소스는 clip.length초가 지나면 자동으로 풀로 돌아가며, 이 시간은 unscaled time으로 측정됩니다 — 슬로 모션이 정리를 늦추지 않습니다. 수동 관리가 필요 없습니다.

  • 커스텀 타입은 IL2CPP에서 link.xml이 필요합니다. 오디오 설정을 담는 자체 구성 타입을 추가한다면 IL2CPP가 스트리핑하지 않도록 link.xml 항목을 추가하세요:

    <assembly fullname="Assembly-CSharp">
      <type fullname="YourGame.CustomAudioConfig" preserve="all" />
    </assembly>
    

관련 페이지

  • Time — 일시정지 인식 페이드를 뒷받침하는 클럭
  • Configuration — 설정 그룹과 FlushPending
  • Scheduler — 메인 스레드로의 호출 마샬링