Chapter 3-2. 파괴 가능한 환경들 : Sound Manager, 싱글톤
카테고리: Unity Lesson 3
인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 3. 파괴 가능한 환경들
Sound Manager
사운드를 각각의 오브젝트 내에서 관리하는게 아니라 📜SoundManager.cs 한 곳에서 효율적으로 총괄 관리하게끔 한다!
🚖 SoundManager
Sound Manager
이름의 빈 오브젝트 생성
싱글톤
싱글톤은 씬이 전환되어도 유지된다.
현재 Sound Manager
오브젝트에 붙어있는 📜SoundManager.cs 를 게임 월드 내에 단 한 개만 유지하고 이를 접근하여 공유하며 사용하려 할 땐 📜SoundManager.cs 을 싱글톤으로 만들면 된다.
- 만약 📜SoundManager.cs가 싱글톤이 아니였다면
- A,B,C 오브젝트에서 📜SoundManager.cs를 사용한다면 A, B, C 오프젝트들에게 📜SoundManager.cs를 각각 직접 붙여주어야 한다.
- 또한 각각 공유하지 않고 독립적인 부품으로서 움직인다.
- A, B, C 만의 부품이 됨
- 📜SoundManager.cs를 싱글톤으로 만들면
- A, B, C 오프젝트들에게 📜SoundManager.cs를 컴포넌트로서 붙여줄 필요가 없다.
- 📜SoundManager.cs가 붙어있는 오브젝트 인스턴스를
static
으로 선언하여 여러 곳에서 공유할 수 있게 하고 메모리를 유지하게 하고 단 하나만 유지할 수 있도록 보장해주면 된다.
static public SoundManager instance; // 자기 자신을 공유 자원으로. static은 씬이 바뀌어도 유지된다.
private void Awake() // 객체 생성시 최초 실행 (그래서 싱글톤을 여기서 생성)
{
if (instance == null) // 최초 생성
{
instance = this; // 현재의 자기 자신(인스턴스)를 할당
DontDestroyOnLoad(gameObject); // 씬 전환되도 자기 자신이 파괴되지 않고 유지되도록
}
else // 단 하나만 존재하게끔 새로 생긴 Sound Manager 오브젝트 인스턴스일 경우엔 파괴
Destroy(this.gameObject);
}
instance
👉 자기 자신을 할당할 멤버. 자기 자신을 공유 자원으로- 유니티에서의 static 특징
- 게임 내내 메모리가 유지된다.
- 따라서 씬이 전환되어도 살아서 그 값이 유지된다.
- 유니티에선 다른 씬으로 전환되면 기존 씬의 오브젝트들을 모두 파괴한다. 그리고 다시 원래 씬으로 돌아오면 기존 씬의 초기화 상태로 오브젝트들을 새롭게 다시 생성한다.
- 그러나 씬이 바뀌어 오브젝트가 파괴되더라도
static
멤버 값 메모리는 그대로 유지가 된다. 나중에 원래 씬으로 돌아와 오브젝트를 새롭게 만들더라도static
멤버 메모리는 유지되고 있는 것을 확인할 수 있다.- 씬이 바뀌어도 오브젝트를 파괴하지 않고 유지시키고 싶으면 DontDestroyOnLoad(gameObject) 함수를 실행하여 씬이 바뀌어도 이 오브젝트는 파괴되지 않고 모든 멤버 값을 유지한다는것을 보장해주면 된다.
- 따라서 씬이 전환되어도 살아서 그 값이 유지된다.
- 게임 내내 메모리가 유지된다.
- 유니티에서의 static 특징
- Awake() 👉 오브젝트가 생성될 때 호출된다.
- 오브젝트 생성시에만 최초로 실행 된다.
- 따라서 싱글톤은 Awake() 안에서 구현하는 것이 좋다.
- 보통 초기화 작업들을 넣는 Start()보다 먼저 호출되기 때문이다.
- 씬이 전환되면 기존 씬의 오브젝트들은 삭제되고 다시 원래 씬으로 돌아오면 새롭게 오브젝트들을 생성하므로, 오브젝트가 생성될 때 마다, 씬이 바뀔 때마다 Awake()가 호출된다.
- 오브젝트 생성시에만 최초로 실행 된다.
instance
에 현재 아무 것도 할당 되어 있지 않을 때 👉instance
에 자기 자신인 📜SoundManager.cs 최초 할당if (instance == null)
instance
에 자기 자신인Sound Manager
오브젝트를 할당한 후instance = this;
- 씬이 바뀔 때 자기 자신(
Sound Manager
)이 파괴되는 것을 방지한다.DontDestroyOnLoad(gameObject);
- 📜SoundManager.cs 에서 static 멤버인
instance
에 자기 자신(Sound Manager
)을 담고 있기 때문에Sound Manager
은 씬이 전환되도 메모리에 보존 되고 파괴되지는 않는다. 다만 static 멤버를 제외한 📜SoundManager.cs의 다른 멤버들은 씬이 바뀌면 값이 모조리 날라가 버린다.
- 📜SoundManager.cs 에서 static 멤버인
instance
가 null이 아니라면 👉 기존에instance
에Sound Manager
인스턴스를 할당을 해놔서 그 메모리를 보존하고 있다는 뜻Sound Manager
인스턴스는 단 하나만 존재해야 하므로 새롭게 생성된Sound Manager
는 파괴해주어야 한다.- 다른 씬 됐다가 원래의 씬으로 다시 전환 되면 유니티가 오브젝트들을 새롭게 다시 생성하므로
Sound Manager
오브젝트도 생성한다. - 그러나 씬전환 전에 최초로
Sound Manager
오브젝트를 생성했었을 때instance
에 그때의Sound Manager
오브젝트를 할당했고 그게 메모리에 계속 유지가 되왔으므로 다른 씬 됐다가 원래의 씬으로 다시 전환 되서 또 새롭게 만들어진Sound Manager
의 📜SoundManager.cs 에선 Awake()를 호출하고 이어서 else 에 걸리게 된다.- 씬이 재로드 되면서 새롭게 만들어진
Sound Manager
인스턴스를 파괴해준다. (새롭게 만들어진Sound Manager
입장에서의 자기 자신을 파괴)else Destroy(this.gameObject);
- 이 과정을 통해
Sound Manager
오브젝트 인스턴스는 동일한 그 인스턴스가 게임 내내 단 하나만 유지된다. 씬 전환으로 새롭게 생긴Sound Manager
인스턴스는 파괴되므로! - 단 하나만 존재하게끔 다른 씬 갔다가 다시 돌아오면 오브젝트가 다시 생성되서 사운드 매니저 또 생기는 것을 파괴해 줌
- 씬이 재로드 되면서 새롭게 만들어진
- 기존
instance
에 보관되어 있는 기존의 그Sound Manager
인스턴스는 다른 씬 갔다가 돌아와도 Awake()가 실행되지 않는다. 여태 계속 살아 있었고 새로 다시 태어나는게 아니니까 !
- 다른 씬 됐다가 원래의 씬으로 다시 전환 되면 유니티가 오브젝트들을 새롭게 다시 생성하므로
- DontDestroyOnLoad(gameObject)
- 인수로 넘긴 오브젝트가 씬이 전환되더라도 파괴하지 않고 유지할 수 있게 해주는 함수다.
- 이 함수를 해주지 않으면
Sound Manager
오브젝트는 사라진다.- 물론 static인
instance
에 기존 인스턴스를 담아 두어 메모리에 계속 보관되긴 하지만 그래도 게임 씬 상에 존재하는 유니티 오브젝트로서는 사라지니까 📜SoundManager.cs 또한 실행시킬 수 없음. 따라서 오브젝트가 씬 전환 되어도 파괴안되게 막아 주어야 함.
- 물론 static인
📜SoundManager.cs
Sound Manager
오브젝트에 붙이기
- 여러 오브젝트들의 사운드를 총괄 담당하는 스크립트인 만큼 여러 곳에서 접근 및 공유가 되야 하므로 싱글톤으로 구현하기
- 단 하나로 게임 내내 살아서 유지가 되야 함. (
Sound Manager
오브젝트도 내내 활성화 되있어야 한다는 소리)- 여러 곳에서 접근해야 하니까 파괴되지 않도록.
- 단 하나를 유지하도록 새로 생성된건 파괴해준다.
- 공유가 되도록
static
인스턴스로.
- 여러 곳에서 접근해야 하니까 파괴되지 않도록.
- 단 하나로 게임 내내 살아서 유지가 되야 함. (
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Sound // 컴포넌트 추가 불가능. MonoBehaviour 상속 안 받아서. 그냥 C# 클래스.
{
public string name; // 곡 이름
public AudioClip clip; // 곡
}
public class SoundManager : MonoBehaviour
{
#region singleton
static public SoundManager instance; // 자기 자신을 공유 자원으로. static은 씬이 바뀌어도 유지된다.
private void Awake() // 객체 생성시 최초 실행 (그래서 싱글톤을 여기서 생성)
{
if (instance == null) // 단 하나만 존재하게끔
{
instance = this; // 객체 생성시 instance에 자기 자신을 넣어줌
DontDestroyOnLoad(gameObject); // 씬 바뀔 때 자기 자신 파괴 방지
}
else
Destroy(this.gameObject);
}
#endregion singleton
public Sound[] effectSounds; // 효과음 오디오 클립들
public Sound[] bgmSounds; // BGM 오디오 클립들
public AudioSource audioSourceBFM; // BGM 재생기. BGM 재생은 한 군데서만 이루어지면 되므로 배열 X
public AudioSource[] audioSourceEffects; // 효과음들은 동시에 여러개가 재생될 수 있으므로 'mp3 재생기'를 배열로 선언
public string[] playSoundName; // 재생 중인 효과음 사운드 이름 배열
void Start()
{
playSoundName = new string[audioSourceEffects.Length];
}
public void PlaySE(string _name)
{
for (int i = 0; i < effectSounds.Length; i++)
{
if(_name == effectSounds[i].name)
{
for (int j = 0; j < audioSourceEffects.Length; j++)
{
if(!audioSourceEffects[j].isPlaying)
{
audioSourceEffects[j].clip = effectSounds[i].clip;
audioSourceEffects[j].Play();
playSoundName[j] = effectSounds[i].name;
return;
}
}
Debug.Log("모든 가용 AudioSource가 사용 중입니다.");
return;
}
}
Debug.Log(_name + "사운드가 SoundManager에 등록되지 않았습니다.");
}
public void PlayBGM(string _name)
{
for (int i = 0; i < bgmSounds.Length; i++)
{
if (_name == bgmSounds[i].name)
{
audioSourceBFM.clip = bgmSounds[i].clip;
audioSourceBFM.Play();
return;
}
}
Debug.Log(_name + "사운드가 SoundManager에 등록되지 않았습니다.");
}
public void StopAllSE()
{
for (int i = 0; i < audioSourceEffects.Length; i++)
{
audioSourceEffects[i].Stop();
}
}
public void StopSE(string _name)
{
for (int i = 0; i < audioSourceEffects.Length; i++)
{
if(playSoundName[i] == _name)
{
audioSourceEffects[i].Stop();
break;
}
}
Debug.Log("재생 중인" + _name + "사운드가 없습니다. ");
}
}
효과음 vs BGM
- Sound 클래스 👉 일반 C# 클래스. MonoBehaviour 상속 안 받아서 컴포넌트 추가 불가능
- [System.Serializable] 을 붙여주면 클래스의 모든 멤버 변수에게 [SerializeField] 을 한꺼번에 적용시키는 것과 같다.
name
곡 이름clip
오디오 클립- 위 2 개를 묶어서 관리
- 이 Sound 타입의 배열 멤버
effectSounds
👉 효과음 배열. Sound 타입이므로 원소 하나당 곡 이름과 클립 정보 有bgmSounds
👉 BGM 배열. Sound 타입이므로 원소 하나당 곡 이름과 클립 정보 有- BGM도 여러개 있을 수 있음. 단 효과음처럼 재생될 땐 동시에 여러 BGM 이 재생되지는 않다.
- AudioSource 👉 재생기!! MP3 플레이어 같은 것!
audioSourceBFM
- BGM을 재생시킬 Audio Source 컴포넌트
audioSourceEffects
- 효과음들을 재생시킬 Audio Source 컴포넌트
- 효과음은 동시에 여러개 재생될 수 있으므로 재생기가 여러개여야 한다. 따라서 배열로 선언.
playSoundName
👉 문자열 배열. 재생 중인 ‘효과음’들의 이름이 게임 중 실시간으로 원소로 들어간다.- Start()
Sound Manager
오브젝트가 활성화 되자마자,playSoundName
배열의 크기를audioSourceEffects
재생기 개수와 일치시킨다.- 사용할 효과음 개수만큼 효과음을 재생기들을 똑같은 개수로 생성해주었었기 때문에
- PlaySE(string _name)
- 효과음 재생 함수
- 효과음 배열에서 똑같은 이름의 곡이 있는지를 찾아 검사하고
// for문 if(_name == effectSounds[i].name)
- 해당 곡이 있다면 현재 재생중이지 않고 놀고 있는 재생기를 찾는다. 이 효과음을 재생시킬 수 있는 AudioSource 를 찾는 것이다. 재생 중이지 않은 재생기를 찾으면 해당 효과음을 재생 시키고
playSoundName
에 효과음 이름을 원소로 추가해주고 바로 함수를 종료시킨다.for (int j = 0; j < audioSourceEffects.Length; j++) { if(!audioSourceEffects[j].isPlaying) // 재생중이지 않고 놀고 있는 재생기 발견 { audioSourceEffects[j].clip = effectSounds[i].clip; audioSourceEffects[j].Play(); // 재생시키고 playSoundName[j] = effectSounds[i].name; // `playSoundName` 원소 추가 return; } }
- 효과음 배열에서 똑같은 이름의 곡이 있는지를 찾아 검사하고
- 효과음 재생 함수
- PlayBGM(string _name)
- BGM 재생 함수
- BGM 배열에서 똑같은 이름의 곡이 있는지를 찾아 검사한다.
- 어차피 BGM 재생기는 하나이므로 해당 곡을 찾으면 바로 재생시키고 함수 종료시키면 된다.
- BGM 재생 함수
- StopAllSE()
- 모든 효과음 재생을 멈춤
- StopSE(string _name)
- 특정 효과음의 재생을 멈춤
playSoundName
배열을 검사하여 인수로 들어온 그 특정 효과음 원소(이름)을 찾는다. 있다면 그 효과음을 재생하고 있는 AudioSource를 멈춘다.- 이게 가능한 이유 + 그리고
playSoundName
배열을 만든 이유.playSoundName
의 인덱스와audioSourceEffects
의 인덱스는 서로 대응한다. 따라서 현재 재생중인 효과음을 이름(string)으로playSoundName
에서 찾아, 동일한 인덱스의audioSourceEffects
원소(AudioSource)에서 재생 중이므로 해당 효과음을 현재 재생중인 재생기를 쉽게 알 수 있다.
- 특정 효과음의 재생을 멈춤
- Audio Source 컴포넌트를 7 개 추가해주었다. 그리고 각각의 멤버에 할당해주었다.
audioSourceBFM
하나는 BGM 재생기audioSourceEffects
6 개는 효과음 재생기 (효과음을 6 개 쓸거라서)
PlaySoundName
배열은 게임이 시작되면 Start() 에 의해서 배열의 크기가 6 으로 설정 될 것이다.- 효과음 2 개만
effectSounds
원소로 추가해보았다.- 이름과 함께 오디오 클립 할당
📜Rock.cs
📜SoundManager.cs 사용하여 재생하도록 스크립트 수정
// 필요한 사운드 이름
[SerializeField]
private string strike_Sound;
[SerializeField]
private string destroy_Sound;
public void Mining()
{
SoundManager.instance.PlaySE(strike_Sound);
GameObject clone = Instantiate(go_effect_prefabs, col.bounds.center, Quaternion.identity);
Destroy(clone, destroyTime);
hp--;
if (hp <= 0)
Destruction();
}
private void Destruction()
{
SoundManager.instance.PlaySE(destroy_Sound);
col.enabled = false;
Destroy(go_rock);
go_debris.SetActive(true);
Destroy(go_debris, destroyTime);
}
SoundManager.instance.PlaySE(strike_Sound); // 곡괭이로 바위 때릴시 효과음 재생
SoundManager.instance.PlaySE(destroy_Sound); // 바위가 파괴될 때 효과음 재생
직접 📜Rock.cs 에서 Audio Source
컴포넌트를 붙이고 멤버로 가져오고 했던 과정을 지우고 📜SoundManager.cs 싱글톤을 사용해 재생해주는 방식으로 코드를 수정했다. 📜SoundManager.cs의 PlaySE 함수의 인수로 넘겨줄 곡 이름 (string)만 있으면 된다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기