이번글은 유니티 사운드를 코드로 어떻게 불러오고 어떻게 재생시키는지 알아보도록하자.
먼저 유니티의 사운드는 크게
세가지로 분류가 된다.
시작하기에 앞서 모든 사운드를 관리해줄 Sound Manager 라는 c#스크립트 파일을 만들어준다.
그리고 Sound의 타입을 관리해줄 Define 스크립트도 미리 만들어둔다.
Define스크립트에서는 사운드 뿐만 아니라 여러가지 미리 정의해둘 필요가 있는 것들을 모아 놓는 용도로 사용하자.(사운드의 종류에 Bgm, Effect 등등)
사운드가 어떻게 먼저 나는지 TestSound에서 코드를 작성하여 먼저 알아보도록하겠다.
먼저 start(), update() 함수는 작성해주지 않고 밑에
충돌이 일어났을때 충동한 물체의 Audio Source를 뽑아오는 코드를 먼저 테스트 해보도록하자.
public AudioClip audioClip1;
private void OnTriggerEnter(Collider other)
{
AudioSource audio = GetComponent<AudioSource>();
audio.PlayOneShot(audioclip1);
}
먼저 public으로 audipClip1을 선언해준다.
기본적으로 default값이 private이기 때문에, public으로 열어줘야지만 유니티엔진에서 사용(드래그드롭같은 기능들을)가능하다.
그리고 유니티엔진에서 오브젝트(큐브든 뭐든 상관없음)를 만들어주고 TestSound를 컴포넌트로 붙여준다.
OnTriggerEnter는 자동완성이다.
유니티엔진으로 돌아와서 오른쪽에 TestSound2를 붙이면 밑에 public으로 열어둔 audioClip1이 있다.
여기에 자기가 실행되기를 원하는 audioclip을 드래그드롭으로 넣어주고 실행을 해보면 우리 캐릭터가 cube를 지나갈때 마다 해당 오디오 클립이 재생이 된다.
현재는 캐릭터가 큐브를 바로통과한다.(트리거를 켜놓은 상태이기 때문에) 캐릭터와 물체 둘다 충돌하는 것을 구현을 하려면 플레이어와 물체 둘다 RigidBody를 가지고 잇으되 isTrigger를 꺼놓아야한다.
이제 사운드와 물체의 충돌까지 알아봤으니
본격적으로 Sound Manager에서 sound를 관리하는 방법을 알아보자.
Sound Manager를 따로 뺴놔서 sound를 관리하는 이유는
나중에 게임이 커지거나(커지지 않더라도)
객체 하나하나에 사운드를 드래그 드롭해서 관리할 수도 없고 규모가 큰 경우에는 사운드의 종류가 엄청나게 많을 것이기 때문이다.
먼저 최종 코드부터 보도록하겠다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SoundManager
{
AudioSource[] _audioSources = new AudioSource[(int)Define.Sound.MaxCount]; // 용도를 나누어서 만들어 놓자.
// mp3 audiosource를 담을 배열 선언
// 캐싱 역할을 할 _audioClips 딕션어리
// 캐싱은 사운드를 재생할때마다 계속 path를 입력받아 사운드를 찾는 방식이 아닌 한번 사용한 사운드는 딕셔너리로 관리하여 보다 빠르게 처리하기 위해서 캐싱용 딕션너리를 사용하는 것이다.
Dictionary<string, AudioClip> _audioClips = new Dictionary<string, AudioClip>();
public void init()
{
GameObject root = GameObject.Find("@Sound");
if(root == null)
{
root = new GameObject { name = "@Sound" };
Object.DontDestroyOnLoad(root);
string[] soundNames = System.Enum.GetNames(typeof(Define.Sound));
for(int i = 0; i < soundNames.Length - 1; i++)
{
GameObject go = new GameObject { name = soundNames[i] };
_audioSources[i] = go.AddComponent<AudioSource>(); // 위에서 만든 _audioSources에 넣어준다.
go.transform.parent = root.transform;
}
// soundName을 돌면서 새로운 GameObject를 만들어준다.
_audioSources[(int)Define.Sound.Bgm].loop = true; // Bgm같은 경우에는 루프로 계속 사운드가 나도록 해준다.
}
}
public void Clear()
{
foreach(AudioSource audioSource in _audioSources)
{
audioSource.clip = null;
audioSource.Stop();
}
_audioClips.Clear();
}
public void Play(string path, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f) // path로 경로를 받아주고 pitch = 소리 속도 조절
{
AudioClip audioclip = GetOrAddAudioClip(path, type);
Play(audioclip, type, pitch);
}
public void Play(AudioClip audioClip, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f) // path로 경로를 받아주고 pitch = 소리 속도 조절
{
if (audioClip == null)
{
return;
}
if (type == Define.Sound.Bgm)
{
AudioSource audioSource = _audioSources[(int)Define.Sound.Bgm];
if (audioSource.isPlaying)
audioSource.Stop();
audioSource.pitch = pitch;
audioSource.clip = audioClip;
audioSource.Play();
}
else // (type == Define.Sound.Effect)
{
AudioSource audioSource = _audioSources[(int)Define.Sound.Effect];
audioSource.pitch = pitch;
audioSource.clip = audioClip;
audioSource.PlayOneShot(audioClip);
}
}
AudioClip GetOrAddAudioClip(string path, Define.Sound type = Define.Sound.Effect) // audioClip 반환하는 함수(위에 Dictionary만든 부분에서)
{
if (path.Contains("Sounds/") == false)
{
path = $"Sounds/{path}";
}
AudioClip audioClip = null;
if (type == Define.Sound.Bgm)
{
audioClip = Managers.Resource.Load<AudioClip>(path);
}
else
{
if (_audioClips.TryGetValue(path, out audioClip) == false) // 있으면 이렇게 값을 뱉어주고
{
audioClip = Managers.Resource.Load<AudioClip>(path);
_audioClips.Add(path, audioClip);
}
}
if (audioClip == null)
{
Debug.Log($"AudioClip Missing ! {path}");
}
return audioClip;
}
}
안에 코드를 보자면
init함수, Clear함수, Play, Play 함수, GetOrAddAudioClip함수 5가지가 있다.
init함수는
유니티 엔진을 시작할 때, root라는 게임오젝트를 찾아준다(없다면 만들어준다.)
root를 DontDestroyOnLoad로 삭제가 불가능하게 만들고
Define에 정의되어있는 Sound의 크기만큼의 사운드이름들을 담을 string[] SoundName = System.Enum.GetNAmes(tyupeof(Define.Sound))로 만들어 준다.
이후 for문을 돌면서 soundNames에있는 아이들을 차례대로 _audioSource안에 넣어주고 @Sound를 부모로 설정해주는 부분이다.
즉, AudioSource(Player)부분을 Define.Sound에 들어있는 애들만큼 만들어 준다는 것이다.
Clearg함수는
현재 DontDestrotyOnLoad로 root를 만들어 준다음 그안에서 _audioSource[]안에 계속 집어 넣는 식인데, Scene이 이동하거나 그러면 사운드도 다 달라질것이다.
그러면 배열안에는 수만가지 sound가 쌓이게 될것이고 => 과부화가 일어난다.
이것을 방지하기 위해서 Scene이 이동하면 배열안을 깨끗하게 만들어주는 함수가 필요하다.
이것이 현재 Clear함수이다.
Play 함수
현재 Play함수가 두가지 버젼이 있는데
하나는string path를 받는 버젼과
AudioClip audioClip을 받는 버젼 두가지로 나눠져있다.
먼저 Play함수 두가지 버젼을 설명하기전에
GetOrAddAudioClip함수부터 설명하겠다.
AudioClip GetOrAddAudioClip(string path, Define.Sound type = Define.Sound.Effect) // audioClip 반환하는 함수(위에 Dictionary만든 부분에서)
{
if (path.Contains("Sounds/") == false)
{
path = $"Sounds/{path}";
}
// 혹시나 뺴먹고 Sound를 안붙여 줬을때를 대비한 if문
AudioClip audioClip = null;
if (type == Define.Sound.Bgm)
{
audioClip = Managers.Resource.Load<AudioClip>(path);
}
// Bgm이라면 그대로 path를 통해 클립을 가져온다.
else // effect나 다른 사운드의 경우
{
if (_audioClips.TryGetValue(path, out audioClip) == false)
// _audioClips에 TryGetValue로 해당하는 값이 있는지 없는지 확인해주고
// 없다면 밑에 if문 코드를 실행한다.
{
audioClip = Managers.Resource.Load<AudioClip>(path);
_audioClips.Add(path, audioClip); // 없었던 경우니까 캐싱을 하기 위해서 _audioClips에 Add를 통해서 넣어주자.
}
}
if (audioClip == null)
{
Debug.Log($"AudioClip Missing ! {path}");
}
return audioClip;
}
GetOrAddAudioClip은
path, sound type == Effect인 경우를 입력을 받아
Bgm인경우와 Effect인 경우의 audioClip을 Load해서
audioClip을 return 해주는 함수이다.
따라서
public void Play(string path, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f)
{
AudioClip audioclip = GetOrAddAudioClip(path, type);
Play(audioclip, type, pitch);
}
에서는 path를 입력을 받아 GetOrAddAudioClip함수에 path와 type을 넘겨주어서 audioclip에 할당해서
audioclip을 받는 Play함수를 호출한다.
audioclip을받는 Play함수는
public void Play(AudioClip audioClip, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f) // path로 경로를 받아주고 pitch = 소리 속도 조절
{
if (audioClip == null)
{
return;
}
if (type == Define.Sound.Bgm)
{
AudioSource audioSource = _audioSources[(int)Define.Sound.Bgm];
if (audioSource.isPlaying)
audioSource.Stop();
audioSource.pitch = pitch;
audioSource.clip = audioClip;
audioSource.Play();
}
else // (type == Define.Sound.Effect)
{
AudioSource audioSource = _audioSources[(int)Define.Sound.Effect];
audioSource.pitch = pitch;
audioSource.clip = audioClip;
audioSource.PlayOneShot(audioClip);
}
}
인자로 넣어진 audioclip을 어떤 방식으로 재생 시킬지 play하는 함수이다.
Bgm이라면 현재 실행되고있는 Bgm를 멈추고
해당 Bgm을 실행시켜주고
audioClip이 Effect라면 PlayOneshot으로 한번만 실행시켜주도록하자.
이로써 두서없는 Unity 사운드 관리하는 방법이 끝났습니다.
감사합니다.