예전에 이터널 리턴이라는 게임을 하다가 든 생각이 있다.
나만의 상점이라는 페이지가 있는데 해당 페이지에서 마우스를 빠르게 좌우로 움직이면서
오브젝트에 커서를 댔다 놨다를 반복하면 소리가 점점 엹어지다가 들리지 않게 되는데
이게 왜 그런걸까에 대해 생각을 해보니 효과음을 내는 여러칸의 사운드 소스가 있고
모든 사운드 소스가 꽉찬 상태에서 해당 효과음이 새로 추가되면서
이전 사운드 소스를 덮어 씌우는 것 같았다.
해당 방법의 장점으로는 여러가지 효과음을 여러개의 소스에서 처리하다 보니
들려야 할 사운드는 확실히 들을 수 있다는 것이고
단점은 미리 사운드를 재생할 오브젝트를 생성해 두었기 때문에
메모리에 공간을 차지할 것으로 예상되는 부분이다.
전체 스크립트
public class SoundManager : MonoBehaviour
{
[SerializeField] private AudioSource _bgmPlayer; //배경음악
public Queue<GameObject> _sefQueue;
[SerializeField] private AudioClip[] _bgm;
[SerializeField] private AudioClip[] _sef;
private static SoundManager instance;
public static SoundManager Instance
{
get
{
if(instance == null)
{
instance.GetComponent<SoundManager>();
if (instance == null)
{
GameObject obj = new GameObject();
obj.name = typeof(SoundManager).Name;
instance = obj.AddComponent<SoundManager>();
}
}
DontDestroyOnLoad(instance);
return instance;
}
}
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(instance);
}
_sefQueue = new Queue<GameObject>();
for (int i = 0; i < 5; i++)
{
GameObject obj = new GameObject();
obj.AddComponent<AudioSource>();
_sefQueue.Enqueue(obj);
DontDestroyOnLoad(obj);
}
}
private void Start()
{
PlayBgm("OtherSceneBGM");
}
public void PlayBgm(string soundName)
{
for (int i = 0; i < _bgm.Length; i++)
{
if (_bgm[i].name == soundName)
{
_bgmPlayer.clip = _bgm[i];
_bgmPlayer.Play();
return;
}
}
}
public void StopBgm()
{
_bgmPlayer.Stop();
}
public void PlaySef(string soundName)
{
for (int i = 0; i < _sef.Length; i++)
{
if (_sef[i].name == soundName)
{
PlaySefQueue(_sefQueue, _sef[i]);
return;
}
}
}
private void PlaySefQueue(Queue<GameObject> sefQueue, AudioClip audioClip)
{
GameObject obj = sefQueue.Dequeue();
obj.GetComponent<AudioSource>().clip = audioClip;
obj.GetComponent<AudioSource>().Play();
_sefQueue.Enqueue(obj);
}
}
우선 저번에 업로드 했던 사운드 매니저를 조금 손 본 것이다.
그 때는 두개의 SoundSource를 통해 효과음과 배경음을 따로 재생하여
서로 소리를 잡아먹지 않도록 하는 것에 중점을 두었고
이번에는 효과음이 효과음을 잡아먹지 않게 하기위해 설계를 했다.
차례대로 물고 씹고 뜯어보자.
private void Awake()
{
_sefQueue = new Queue<GameObject>();
for (int i = 0; i < 5; i++)
{
GameObject obj = new GameObject();
obj.AddComponent<AudioSource>();
_sefQueue.Enqueue(obj);
DontDestroyOnLoad(obj);
}
}
주제와 관련없는 내용은 삭제했다.
제일 처음에는 _sefQueue를 <AudioSource>로 생성했으나 AudioSource는 컴포넌트이지 오브젝트가 아니다.
그래서 _sefQueue를 Queue<GameObject>로 생성한 후
각각의 오브젝트에 AddComponent를 통해 AudioSource를 추가해주고
그 오브젝트를 _sefQueue에 담아주고(Enqueue) 그 후에 DontDestroyOnLoad를 적용해 주었다.
DontDestroyOnLoad를 적용한 이유는 해당 클래스가 SoundManager이고 배경음악을 재생하기 위해
가장 처음의 씬부터 적용되어야 하기 때문이다.
전 날에 작성한 TIL을 보면 씬이 이동되면 기존 씬에 있던 모든 오브젝트는 Destroy판정을 받기 때문에
DontDestroyOnLoad를 통해 다음 씬에서도 이어질 수 있도록 해주었다.
다음 구문을 보면
public void PlaySef(string soundName)
{
for (int i = 0; i < _sef.Length; i++)
{
if (_sef[i].name == soundName)
{
PlaySefQueue(_sefQueue, _sef[i]);
return;
}
}
}
private void PlaySefQueue(Queue<GameObject> sefQueue, AudioClip audioClip)
{
GameObject obj = sefQueue.Dequeue();
obj.GetComponent<AudioSource>().clip = audioClip;
obj.GetComponent<AudioSource>().Play();
_sefQueue.Enqueue(obj);
}
이 부분인데
string으로 넘어온 음악의 파일명을 받아서 미리 배열에 추가해둔 파일의 이름과 같다면
해당 파일을 실행시키는 역할을 진행한다.
PlaySefQueue 메소드를 보면
해당 파일을 Queue에서 제거된(Dequeue) 오브젝트에 추가하고 그것을 실행시키는 것으로 진행된다.
어디서 본 것 같은 느낌이다.
바로 오브젝트 풀링이다.
이전에 게시했던 오브젝트 풀링과 비슷한 방법을 사용한다.
장점으로는 여러개의 효과음이 서로 묻히지 않고
단점으로는 5개의 효과음 이상이 동시에 발생하는 경우
가장 먼저 생성된 순서대로 효과음이 묻힌다.
이는 맨 처음에 설명했던 이터널 리턴과 비슷한 부분이 있다.
개인적인 생각으로는 이런 식으로 효과음을 재생시키지 않았나 생각이 들기도 한다.
그럼 TTE
1107 TIL - 팀 프로젝트 완료 (0) | 2024.11.07 |
---|---|
1030 TIL - C# 제네릭 싱글톤 (0) | 2024.10.30 |
1017 TIL - 씬 전환 시 정보 이동 (0) | 2024.10.17 |
1016 TIL - Unity InputSystem 심화 (5) | 2024.10.16 |
1015 TIL - 유니티 입문 팀 프로젝트 주차 돌입 (3) | 2024.10.15 |