설정 및 사운드 시스템
설정 시스템은 플레이어가 마우스 감도와 게임 사운드(BGM, SFX)를 조정할 수 있는 환경 설정 기능을 제공하는 시스템이다.
슬라이더 UI를 통해 마우스 감도와 사운드 볼륨을 실시간으로 변경할 수 있으며, BGM과 효과음은 각각 On/Off 버튼을 통해 음소거를 제어할 수 있다.
사운드 시스템은 싱글톤 구조의 SoundManager를 통해 게임 전체의 오디오를 관리하며, 배경음은 하나의 AudioSource로 지속적으로 재생되고 효과음은 필요할 때마다 동적으로 AudioSource를 생성하여 재생된다.
목차
1. 유니티 구현
1.1. NGUI 기반 UI 구조
1.2. NGUI Slider
1.3. NGUI Button
1.4. AudioListener
2. 전체 코드
유니티 구현
1.1. NGUI 기반 UI 구조
설정 UI는 NGUI 시스템을 이용해 구성하였다.
Setting UI는 UIManager의 자식 객체로 배치되어있다.
UIManager는 전체 UI를 관리하는 루트 오브젝트이며, SettingUI는 설정 창을 구성하는 UI 오브젝트이다.

UIManager 오브젝트에는 Root, Panel, Rigidbody 컴포넌트가 추가되어 있다.
UIRoot는 NGUI에서 UI 시스템의 기준이 되는 루트 컴포넌트로, 모든 UI 요소의 해상도 스케일링과 좌표 기준을 관리하는 역할을 한다.
UIRoot가 존재하면 화면 해상도가 변경되더라도 UI가 일정한 비율을 유지하도록 자동으로 스케일을 조정한다.
따라서 NGUI 기반 UI에서는 모든 UI 오브젝트가 UIRoot 아래에 배치되는 구조를 사용한다.
UIPanel은 NGUI에서 UI 요소들을 하나의 그룹으로 관리하는 컨테이너 역할을 한다.
패널 내부의 UI 요소들은 동일한 렌더링 그룹으로 처리된다.
UIPanel은 UI 렌더링 순서 관리, UI 클리핑 영역 설정, UI 그룹 단위 관리와 같은 기능을 담당한다.
NGUI에서는 Collider를 이용해 UI 입력을 처리하기 때문에, 물리 엔진이 해당 UI 오브젝트를 충돌 가능한 객체로 인식해야 한다.
이를 위해 UI 루트 오브젝트에 Rigidbody를 추가하였다.
이 Rigidbody는 실제 물리 연산을 수행하기 위한 것이 아니라 UI 입력 이벤트 처리를 위한 구성 요소이다.
이때, Rigidbody는 Use Gravity는 off, Is Kinematic은 on으로 설정한다.
이렇게 설정하면 물리 계산은 수행하지 않으면서도 Collider 기반 입력 이벤트가 정상적으로 동작하게 된다.
1.2. NGUI Slider
설정 UI에서 사용하는 Slider는 NGUI Slider 컴포넌트를 사용하였다.
NGUI Slider는 하나의 UI 요소처럼 보이지만 실제로는 여러 오브젝트가 결합된 구조로 이루어져 있다.
Slider의 구조는 다음과 같다.
Slider
|- Background
|- Foreground
|- ThumbNGUI Slider는 단일 UI 요소가 아니라 Background, Foreground, Thumb로 구성된 구조이며, Foreground를 통해 현재 값이 시각적으로 표현되고 Thumb를 드래그하여 값을 변경할 수 있다.

NGUI는 Collider 기반 입력 시스템을 사용하기 때문에 Slider 오브젝트에는 BoxCollider를 추가하여 마우스 입력을 감지하도록 구성하였다.
BoxCollider는 UI 입력을 감지하는 영역으로 사용되며, 사용자가 마우스로 해당 영역을 클릭하거나 드래그하면 NGUI 이벤트 시스템이 이를 감지하여 Slider 값이 변경된다.
이때, BoxCollider는 Is Trigger은 true로 설정한다.
NGUI Slider는 사용자가 드래그하여 값을 변경할 수 있는 UI 컴포넌트로, Slider 값은 0~1 범위의 float 값으로 출력된다.
각 Slider는 플레이어가 드래그하여 값을 변경하면 해당 값을 읽어 마우스 감도 및 사운드 볼륨 설정에 반영하도록 구성하였다.
Background는 Slider의 기본 바 영역을 표시하는 UI 요소이다.
이 오브젝트에는 Sprite 컴포넌트만 추가되어 있으며, Slider 전체의 배경 이미지를 표시하는 역할을 한다.
Background는 사용자가 조작하는 요소가 아니라 단순히 Slider의 기준 영역을 표시하는 시각적 요소로 사용된다.
Foreground는 Slider 값에 따라 채워지는 영역을 표현하는 UI 요소이다.
이 오브젝트 역시 Sprite 컴포넌트로 구성되어 있으며, Slider 값이 변경될 때 Foreground의 길이가 함께 변경되면서 현재 Slider 값이 시각적으로 표현된다.
즉 Foreground는 Slider의 현재 값을 표시하는 진행 영역 역할을 한다.

Thumb는 사용자가 직접 드래그하여 Slider 값을 조절하는 핸들 역할의 UI 요소이다.
Thumb 오브젝트에는 Sprite, BoxCollider, Button Color, Button Scale 컴포넌트가 추가되어 있다.
Sprite는 Thumb의 기본 이미지를 표시하는 컴포넌트로, Slider 핸들의 시각적인 형태를 표현한다.
NGUI는 Collider 기반 입력 시스템을 사용하기 때문에 Slider의 Thumb 영역에 BoxCollider를 추가하여 마우스 입력을 감지하도록 구성하였다.
사용자가 Thumb 영역을 클릭하거나 드래그하면 이 Collider를 통해 입력 이벤트가 전달되어 Slider 값이 변경된다.
Button Color 컴포넌트는 마우스 상태에 따라 Thumb의 색상이 변경되도록 하는 효과를 제공한다.
Normal, Hover, Pressed, Disabled 상태에 따라 색상을 다르게 설정하여 사용자가 Slider를 조작할 때 시각적인 피드백을 제공하도록 구성하였다.
Button Scale 컴포넌트는 마우스 이벤트에 따라 Thumb 크기가 변화하는 애니메이션 효과를 제공한다.
Hover 상태면 약간 확대, Pressed 상태면 약간 축소 와 같은 방식으로 설정하여 Slider를 조작할 때 인터랙션 피드백을 강화하였다.
1.3. NGUI Button
사운드 음소거 기능은 NGUI Button을 이용하여 구현하였다.
BGM과 SFX 각각에 대해 On / Off 상태를 표현하는 두 개의 버튼 오브젝트를 구성하였다.
사운드 상태는 하나의 버튼으로 토글하는 방식 대신 On/Off 버튼을 분리하고 활성화 상태를 전환하는 방식으로 구현하였다.
이를 통해 현재 사운드 상태를 색상과 버튼 상태로 직관적으로 표현할 수 있도록 구성하였다.
버튼 구조는 다음과 같다.
OffBtn (BoxCollider, NGUI Button)
├ OffBG (Sprite)
│ └ Label (끄기)
└ OnBG (Sprite)
└ Label (키기)
OnBtn (BoxCollider, NGUI Button)
├ OnBG (Sprite)
│ └ Label (키기)
└ OffBG (Sprite)
└ Label (끄기)각 버튼은 BoxCollider와 NGUI Button 컴포넌트를 가지고 있으며, Collider 영역을 통해 클릭 입력을 감지한다.
사운드 상태는 두 개의 버튼 활성화 상태를 전환하는 방식으로 표현하였다.
사운드가 켜져있을 때에는 UI가 다음과 같은 상태로 설정된다.
OffBtn : 활성화
|- OffBG → 민트 색 (비활성화 느낌)
|- OnBG → 짙은 색 (활성화 느낌)
OnBtn : 비활성화사운드가 꺼져 있을 때 (Mute 상태)에는 UI가 다음과 같은 상태로 설정된다.
OffBtn : 비활성화
OnBtn : 활성화
|-OnBG → 짙은 색 (활성화 느낌)
|-OffBG → 민트 색 (비활성화 느낌)이 방식은 현재 사운드 상태를 색상과 버튼 활성화 상태로, 직관적으로 표현하기 위해 사용하였다.
현재 상태를 색상으로 구분하고 활성화된 버튼만 클릭할 수 있도록 구성하여 잘못된 상태 변경을 방지하였다.
또한 BGM과 SFX 모두 동일한 구조를 사용하여 UI 일관성을 유지하도록 구성하였다.
1.4. AudioListener
Audio Listener는 씬에서 재생되는 사운드를 수신하는 기준 컴포넌트로, Unity에서는 하나의 씬에 하나만 존재해야 한다.
Audio Listener는 실제 세계에서의 귀(Ear) 역할을 하는 컴포넌트로, 씬에서 재생되는 AudioSource의 사운드를 수신하여 플레이어에게 전달한다.
Unity에서는 일반적으로 플레이어 카메라 또는 플레이어 오브젝트에 하나의 Audio Listener만 존재해야 한다.
본 프로젝트에서는 플레이어 카메라와 UI 카메라가 분리되어 있기 때문에, Audio Listener를 카메라에 두지 않고 SoundManager 오브젝트에 배치하여 사운드 시스템을 독립적으로 관리하도록 구성하였다.
Audio Listener는 씬의 AudioSource와 거리 및 위치 관계를 기준으로 사운드를 출력하기 때문에, Listener의 위치는 실제 플레이어가 사운드를 듣는 기준 위치가 된다.

전체 코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SettingUi_ : MonoBehaviour
{
public static SettingUi_ instance;
// 마우스 감도, 배경음, 효과음 크기 조절 슬라이더
UISlider mouseSlider;
UISlider bgmSlider;
UISlider sfxSlider;
// 마우스 감도, 배경음, 효과음 크기 변수
float mouseSensitivity = 0;
float bgmSound = 0;
float sfxSound = 0;
// 배경음 버튼 오브젝트들
public GameObject bgmOnBtn;
public GameObject bgmOffBtn;
// 효과음 버튼 오브젝트들
public GameObject sfxOnBtn;
public GameObject sfxOffBtn;
// ?키 눌렀을 때
public GameObject showKey;
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(this.gameObject);
}
else Destroy(this.gameObject);
LoadResource();
}
void LoadResource()
{
mouseSlider = GameObject.Find("MouseSlider").GetComponent<UISlider>();
bgmSlider = GameObject.Find("BgmSlider").GetComponent<UISlider>();
sfxSlider = GameObject.Find("SfxSlider").GetComponent<UISlider>();
}
public void Show()
{
if (showKey.activeSelf) showKey.SetActive(false);
else showKey.SetActive(true);
}
public float MouseSensitivity()
{
mouseSensitivity = Mathf.Lerp(10f, 600f, mouseSlider.value);
return mouseSensitivity;
}
public float BgmSoundControl()
{
bgmSound = Mathf.Lerp(0f, 1f, bgmSlider.value);
return bgmSound;
}
public float SfxSoundControl()
{
sfxSound = Mathf.Lerp(0f, 1f, sfxSlider.value);
return sfxSound;
}
public void BgmOffClick()
{
SoundManager.instance.isBgUnMute = false;
bgmOnBtn.SetActive(true);
bgmOffBtn.SetActive(false);
}
public void BgmOnClick()
{
SoundManager.instance.isBgUnMute = true;
bgmOnBtn.SetActive(false);
bgmOffBtn.SetActive(true);
}
public void SfxOffClick()
{
SoundManager.instance.isSfxMute = true;
sfxOnBtn.SetActive(true);
sfxOffBtn.SetActive(false);
}
public void SfxOnClick()
{
SoundManager.instance.isSfxMute = false;
sfxOnBtn.SetActive(false);
sfxOffBtn.SetActive(true);
}
public void StartScene()
{
SceneChg.instance.StartScene();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SoundManager : MonoBehaviour
{
public static SoundManager instance;
// sound는 1이 최대 값
public float sfxVolum = 1f;
public float bgmVolum = 1f;
public bool isSfxMute = false; // 효과음 음소거 상태
public bool isBgUnMute; // 배경음 음소거 상태
AudioSource bgmSource;
public AudioClip mainBgm; // 메인 브금
[Header("발걸음 소리")]
public AudioClip[] grassClips;
public AudioClip[] forestClips;
public AudioClip[] sandClips;
public AudioClip[] caveClips;
[Header("부스터 소리")]
public AudioClip[] boostClips;
[Header("곡괭이 소리")]
public AudioClip[] miningClips;
[Header("도끼 소리")]
public AudioClip[] loggingClips;
[Header("UI")]
public AudioClip onUI;
public AudioClip clickUI;
private void Awake()
{
// 싱글톤 생성
if (instance == null)
{
instance = this;
DontDestroyOnLoad(this.gameObject);
}
else
{
Destroy(this.gameObject);
}
// 배경음 재생 상태로 초기화
isBgUnMute = true;
}
private void Start()
{
// 배경음 재생
PlayBGM();
}
private void Update()
{
// SettingUi가 존재하면
if (SettingUi_.instance != null)
{
bgmVolum = SettingUi_.instance.BgmSoundControl(); // 배경음 크기를 설정창에서 반환된 배경음 소리 크기로 설정
sfxVolum = SettingUi_.instance.SfxSoundControl(); // 효과음 크기를 설정창에서 반환된 효과음 소리 크기로 설정
}
if (GameObject.Find("BGM") != null) //만약 BGM이 생성되었으면,
{
GameObject.Find("BGM").GetComponent<AudioSource>().volume = bgmVolum; // BGM의 소리 크기를 배경음 크기 변수만큼으로 설정
GameObject.Find("BGM").GetComponent<AudioSource>().enabled = isBgUnMute; // mute기능은 중간에 mute를 했다 풀어도 처음부터 재생 x => 따라서 완전히 enabled로 제어해야 함.
}
}
// 효과음을 동적 생성을 해서 하기
public void PlaySfx(Vector3 pos, AudioClip sfx, float delayed, float volum)
{
if (isSfxMute || sfx == null) return;
StartCoroutine(PlaySfxIE(pos, sfx, delayed, volum)); // 효과음 재생 ( 효과음 소리 위치, 효과음, 딜레이, 볼륨 크기)
}
// 효과음 재생 코루틴
IEnumerator PlaySfxIE(Vector3 pos, AudioClip sfx, float delayed, float volum)
{
yield return new WaitForSeconds(delayed);
GameObject sfxObj = new GameObject("Sfx");
AudioSource aud = sfxObj.AddComponent<AudioSource>(); // 인스펙터에서 추가하는 것을 이 두 줄로 끝냄
sfxObj.transform.position = pos;
aud.clip = sfx;
aud.minDistance = 5.0f; // 5m 까지 들림
aud.maxDistance = 10.0f;
aud.volume = volum;
aud.Play();
Destroy(sfxObj, sfx.length); // sfx의 사운드 길이를 받아와서 사운드를 다 재생하면 삭제되게 함.
}
// 배경음 관련 재생
public void PlayBGM() => StartCoroutine(PlayBGMIE(mainBgm, 0f, true));
IEnumerator PlayBGMIE(AudioClip bgm, float delayed, bool loop)
{
yield return new WaitForSeconds(delayed);
GameObject bgmObj = new GameObject("BGM");
bgmSource = bgmObj.AddComponent<AudioSource>();
bgmSource.clip = bgm;
bgmSource.loop = loop;
bgmSource.Play();
}
}
