[Unity] Project. DayLight_12

Lingtea_luv·2025년 7월 11일

Project

목록 보기
38/38
post-thumbnail

Project. DayLight


작업 완료 기능

  1. 빌드 이슈 수정
  2. 플레이어 소음 메커니즘 추가
  3. 몬스터 탐지 메커니즘 적용
  4. 컷씬 TimeLine 제작, 추가
  5. 오디오 매니저 클래스 구현

작업 중 기능

  1. 퀘스트 시스템 구현
  2. 대화 시스템 구현
  3. 퀘스트, 대화 UI 연동, 말풍선 제작
  4. 투척 무기 구현

공유 코드

이번에 공유할 코드는 오디오 기능 전반을 담당하는 AudioManager class이다.

Property & Event Method

AudioManager 클래스의 필드, 초기화 메서드와 이벤트 함수이다. 환경설정에서 볼륨을 조절하고 이를 반영시키기 위해 Setting Manager에 선언된 BGMSoundSFXSound proprety event에 구독하여 세팅된 볼륨으로 바뀌도록 연동시켰다.

SFX(효과음)는 오브젝트 풀 패턴을 적용시켜 따로 AudioSource를 관리하였는데, 다수의 효과음이 동시에 짧은 시간 동안만 출력되기 때문에 풀링 기반이 효과적이라고 생각했다. 이 경우 AudioSource 생성, 파괴보다 메모리 누수 관련 최적화를 기대할 수 있다.

public class AudioManager : Singleton<AudioManager>
{
    // 풀링에 사용할 프리팹 
    [SerializeField] private GameObject _sfxPrefab;
    [SerializeField] private GameObject _uiSfxPrefab;
    
    // 실제 재생될 AudioClip 파일들
    [SerializeField] private List<AudioClip> _sfxList;
    [SerializeField] private List<AudioClip> _uiSfxList;
    [SerializeField] private List<AudioClip> _bgmList;
	
    // 이름 기반으로 Clip을 검색하기 위한 Dictionary
    private Dictionary<string, AudioClip> _bgmDic;
    public Dictionary<string, AudioClip> _sfxDic;
    public Dictionary<string, AudioClip> _uiSfxDic;

	// 오브젝트 풀 Queue, 크기
    private Queue<AudioSource> _sfxPool;
    private Queue<AudioSource> _uiSfxPool;
	private int _amount;
    
	// BGM 전용 AudioSource → AudioManager에 추가
	private AudioSource _bgmSource;
    
    private bool _isPlayed;
    
    private float _curBGMVolume;
    private float _curSFXVolume;
    
    // 순차 재생을 위한 BGM List
    private Queue<AudioClip> _bgmQueue = new Queue<AudioClip>();

	// 초기화 메서드
    protected override void Awake()
    {
        base.Awake();
        Init();
    }

	// SettingManager에 선언된 프로퍼티 이벤트 등록 및 볼륨 초기화
    private void Start()
    {
        _curBGMVolume = SettingManager.Instance.BGMSound.Value;
        _curSFXVolume = SettingManager.Instance.SFXSound.Value;
        SettingManager.Instance.BGMSound.OnChanged += BGMSoundUpdate;
        SettingManager.Instance.SFXSound.OnChanged += SFXSoundUpdate;
    }

	// BGM은 풀링 기반이 아니기에 바로 AudioSource에 적용
    private void BGMSoundUpdate(float value)
    {
        _curBGMVolume = value;
        _bgmSource.volume = _curBGMVolume;
    }

	// 오브젝트 풀에서 가져올 때 적용하기 위한 캐싱
    private void SFXSoundUpdate(float value)
    {
        _curSFXVolume = value;
    }

    private void OnDestroy()
    {
        if (Application.isPlaying)
        {
            SettingManager.Instance.BGMSound.OnChanged -= BGMSoundUpdate;
            SettingManager.Instance.SFXSound.OnChanged -= SFXSoundUpdate;
        }
    }    

    private void Init()
    {
        _amount = 10;
        _sfxPool = new Queue<AudioSource>();
        _uiSfxPool = new Queue<AudioSource>();
        
        // 오브젝트 풀 초기화
        for (int i = 0; i < _amount; i++)
        {
            AudioSource sfx = Instantiate(_sfxPrefab).GetComponent<AudioSource>();
            AudioSource uiSfx = Instantiate(_uiSfxPrefab).GetComponent<AudioSource>();
            _sfxPool.Enqueue(sfx);
            _uiSfxPool.Enqueue(uiSfx);
        }
		
        // AudioClip Dictionary 초기화
        _sfxDic = new Dictionary<string, AudioClip>();
        _uiSfxDic = new Dictionary<string, AudioClip>();
        
        for (int i = 0; i < _sfxList.Count; i++)
        {
            _sfxDic.Add(_sfxList[i].name, _sfxList[i]);
        }
        
        for (int i = 0; i < _uiSfxList.Count; i++)
        {
            _uiSfxDic.Add(_uiSfxList[i].name, _uiSfxList[i]);
        }
        
        // BGM 재생
        EnqueueBGM();
    }
}

Private Method

	// = GetPool
	private AudioSource GetUISfxSource()
    {
    	if (_uiSfxPool == null) _uiSfxPool = new Queue<AudioSource>();
        if (_uiSfxPool.Count > 0) return _uiSfxPool.Dequeue();

        GameObject temp = Instantiate(_uiSfxPrefab);
        return temp.GetComponent<AudioSource>();
    }
	
    // = GetPool
    private AudioSource GetSfxSource()
    {
    	if (_sfxPool == null) _sfxPool = new Queue<AudioSource>();
        if (_sfxPool.Count > 0) return _sfxPool.Dequeue();

        GameObject temp = Instantiate(_sfxPrefab);
        return temp.GetComponent<AudioSource>();
    }
	
    // 효과음 재생 후 풀에 반납하는 메서드
    // 효과음 시간까지 플레이가 되어야하기에 코루틴으로 AudioSource가 지속되도록 구현
    private IEnumerator WaitSFX(float delay, AudioSource source, bool isUI = false)
    {
        yield return new WaitForSeconds(delay);
        source.Stop();
        source.clip = null;
        if (isUI)
        {
            _uiSfxPool.Enqueue(source);
        }
        else
        {
            _sfxPool.Enqueue(source);
        }
    }

	// BGM 전환 과정에서 페이드 인/아웃 기능 추가 메서드
    private IEnumerator BGMFadeIn(AudioSource source, float fadeTime)
    {
        float timer = 0f;
        while (fadeTime > timer)
        {
            source.volume = Mathf.Lerp(0f, _curBGMVolume, timer / fadeTime);
            timer += Time.deltaTime;
            yield return null;
        }

        source.volume = _curBGMVolume;
    }

    private IEnumerator BGMFadeOut(AudioSource source, float fadeTime)
    {
        float timer = 0f;
        while (fadeTime > timer)
        {
            source.volume = Mathf.Lerp(_curBGMVolume, 0f, timer / fadeTime);
            timer += Time.deltaTime;
            yield return null;
        }
        source.Stop();
        source.volume = _curBGMVolume;
    }
    
    private void EnqueueBGM()
    {
        foreach (var bgmName in _bgmList)
        {
            _bgmQueue.Enqueue(bgmName);
        }
    }

    // 다음 BGM 재생
    private void PlayNextBGMInQueue()
    {
        if (_bgmQueue.Count == 0) return;
        AudioClip nextClip = _bgmQueue.Dequeue();
        PlayBGM(nextClip, false);

        //  꺼낸 곡을 다시 큐 뒤에 추가
        _bgmQueue.Enqueue(nextClip);
    }

Public Method

외부에서 호출하여 원하는 효과음을 원하는 장소에 플레이할 수 있도록 구현한 기능들이다. AudioClip은 string, AudioClip 둘 다 접근이 가능하도록 오버로드하여 구현했다.

    public void PlayBGM(AudioClip clipName, bool loop)
    {
        //if (_bgmDic.TryGetValue(clipName, out AudioClip audioClip))
        {
            if (_isPlayed && _bgmSource.clip == clipName) return;
            _bgmSource.spatialBlend = 0;
            _bgmSource.clip = clipName;
            _bgmSource.loop = loop;
            _bgmSource.Play();
            StartCoroutine(BGMFadeIn(_bgmSource, 3f));
            _isPlayed = true;
        }
    }

    public void StopBGM()
    {
        StartCoroutine(BGMFadeOut(_bgmSource, 1f));
        _isPlayed = false;
    }

    public void PlaySFX(AudioClip audioClip, Vector3 position)
    {
        if (audioClip != null)
        {
            AudioSource audioSource = GetSfxSource();
            audioSource.transform.position = position;
            audioSource.volume = _curSFXVolume;
            audioSource.spatialBlend = 1f;
            audioSource.PlayOneShot(audioClip);
            StartCoroutine(WaitSFX(audioClip.length, audioSource));
        }
    }

    public void PlaySFX(string clipName, Vector3 position)
    {
        if (_sfxDic.ContainsKey(clipName))
        {
            AudioSource audioSource = GetSfxSource();
            audioSource.transform.position = position;
            audioSource.volume = _curSFXVolume;
            audioSource.spatialBlend = 1f;
            audioSource.PlayOneShot(_sfxDic[clipName]);
            StartCoroutine(WaitSFX(_sfxDic[clipName].length, audioSource));
        }
    }

    public void PlayUISFX(AudioClip audioClip)
    {
        if (audioClip != null)
        {
            AudioSource audioSource = GetUISfxSource();
            audioSource.volume = _curSFXVolume;
            audioSource.spatialBlend = 0f;
            audioSource.PlayOneShot(audioClip);
            StartCoroutine(WaitSFX(audioClip.length, audioSource, true));
        }
    }     

    // BGM 자동 재생 
    public void PlayBgms(bool bgmPlay)
    {
        if (_bgmSource == null) return;

        if (_bgmSource.isPlaying == false && _bgmQueue.Count > 0)
        {
            PlayNextBGMInQueue();
        }
    }
profile
뚠뚠뚠뚠

0개의 댓글