게임은 정말 많은 요소를 포함하고 있다. 화면을 눈으로 보는 것, 귀로 듣는 것, 컨트롤러의 진동을 통해 촉각적으로 느끼는 것. 완전 종합 예술이 따로 없다. 이렇게 많은 기능을 포함하고 있다는 건, 그만큼 누군가가 갈려나갔다는 뜻이겠다.
아무튼 게임에는 많은 오디오가 사용된다. 알게 모르게 뒤에서 재생되고 있는 BGM, 반응성을 위해 입력의 피드백을 제공하는 효과음, 또 RPG면 스킬이며 적이 내는 소리며 관리해야 할 오디오가 한둘이 아니다. 근데 이렇게 많은 소리를 어떻게 관리하냐? 막막하다.
많은 오디오를 효과적으로 관리하기 위한 시스템에 대해 공부해봤다. 적절한 시점에 적절한 오디오를 실행시켜주면 되니 싱글톤으로 오디오 매니저를 만들어 필요한 곳에서 가져다 쓰면 되지 않을까? 또 각 오디오를 열거형으로 해시맵에 매핑해 외부에서도 실수로 잘못쓰는 일이 없도록 하면 좋을 것 같다.
public enum BGM
{
lobby,
Count
}
public enum SFX
{
buttonClick,
Count
}
일단 열거형으로 사용할 오디오를 정해놨다. 필요한 시스템 오디오의 형태를 BGM
/ SFX
로 나누어 관리하기로 했다. 마지막 요소는 실제 필요한 내용이 아닌 Count로 해놨는데, 열거형은 0부터 시작하기 때문에 이러면 for
문을 돌 때 배열을 순회하는 것처럼 코드 작성이 가능하다.
for(int i = 0; i < (int)BGM.Count; i++)
{
//~~어쩌구저쩌구
}
이러면 BGM.Count
의 int
값은 1이니까 반복문이 1회 실행한다. 완전 배열 순회랑 똑같잖아?
근데 사실 전부 순회해야 한다면 Count
없이 Enum
에서 제공하는 기능으로도 쉽게 할 수 있다. 이건 선택의 영역임.
foreach (BGM bgm in Enum.GetValues(typeof(BGM)))
{
//~~어쩌구 저쩌구
}
각각 오브젝트의 고유한 오디오가 아닌 시스템에서 사용되는 오디오는 재사용되는 경우가 많다. 버튼 클릭할 때마다 매번 다른 소리가 나진 않을 테니까. 그러니까 자주 사용되는 오디오들은 미리 AudioSouce
를 만들어놓고 재생만 하면 됨.
using UnityEngine;
public class AudioManager : Singleton<AudioManager>
{
private const string audioPath = "Audio"; // 오디오가 담길 폴더 경로를 상수로 정해놓자
// 열거형과 실제 오디오 소스를 매핑할 딕셔너리
private Dictionary<BGM, AudioSource> BGMDic = new Dictionary<BGM, AudioSource>();
private Dictionary<SFX, AudioSource> SFXDic = new Dictionary<SFX, AudioSource>();
private void LoadBGM()
{
for(int i = 0; i < (int)BGM.Count; i++)
{
string audioName = ((BGM)i).ToString(); // 열거형과 같은 이름의 오디오 파일명
string filePath = $"{audioPath}/{audioName}"; // 파일 경로
var audioClip = Resources.Load(filePath, typeof(AudioClip)) as AudioClip; // 파일 로드
GameObject go = new GameObject(audioName); // 오디오와 이름이 같은 게임 오브젝트를 만들어준다
AudioSource audioSource = go.AddComponent<AudioSource>(); // 오디오소스 추가
audioSource.clip = audioClip; //로드한 오디오파일을 클립으로 쓴다
BGMDic[(BGM)i] = audioSource; // 매핑까지 해주면 끝
}
}
}
BGM
개수가 늘어나면 열거형 BGM
의 Count
이전에 새로운 요소를 추가하면 된다. 물론 오디오 파일명은 열거형 내부에 값이랑 동일하게 관리하셔야겠다. SFX
도 로드하는 방식은 동일함.
auduioSource
에서 제공하는 loop
나 playOnAwake
를 통해 미리 세부설정을 해놓을 수도 있다. 플레이 시점을 자유롭게 설정 가능하도록 playOnAwake
는 꺼놓고, BGM은 반복되야하니 loop
는 켜놓으면 될 듯.
근데 이러면 하이라키에 엄청나게 많은 오디오소스를 달고 있는 게임 오브젝트들이 생겨 정신을 혼미하게 만들텐데, 각각 형태에 맞는 부모 오브젝트를 만들어서 그 자식으로 만든다면 관리도 편하고 보기도 편하게 되겠지.
//멤버 변수로 게임 오브젝트 하나 저장해놓기
private Transform BGMparent;
//그리고 로드 함수 안에 이 코드를 추가하면 됨
go.transform.parent = BGMparent;
이제 오디오 매니저 내부에 각 오디오 소스를 호출하는 메서드들을 만들고, 프로젝트를 진행하며 적절한 시점에 오디오매니저를 불러와 소리를 재생시키면 되겠다. 근데 BGM은 보통 음원의 길이가 길고 하나만 틀지 않으면 BGM이 겹쳐서 들리는 경우가 생길 수도 있으니 현재 재생중인 AudioSource
를 매니저 내부에서 멤버로 가지고 있으면 관리가 쉽다.
private AudioSource currentBGM; //현재 재생중인 BGM을 담을 변수
public void PlayBGM(BGM bgm)
{
if(currentBGM) //현재 재생중인 BGM이 있다
{
currentBGM.Stop();
currentBGM = null; //멈추고 없애주기
}
currentBGM = BGMDic[bgm];
currentBGM.Play();
}
이러면 현재 재생중인 BGM은 항상 단 하나로 유지할 수 있을듯
이런식으로 어디서든 오디오매니저를 불러 필요한 오디오를 재생할 수 있다. 열거형으로 미리 매핑해놨으니까 이미 있는 BGM
의 범위 내에서만 사용이 가능할거다. 당연히 Count
호출하면 그런 값은 없다는 오류를 뱉을거고. 나머지는 정상적으로 작동하니까 잘 사용해보도록 하자.