지난번에 이어 게임을 만들어보자.
이번에 참고할 강의는 아래 강의이다.
https://www.youtube.com/watch?v=EA-tBcTxE8M&list=PLFt_AvWsXl0ctd4dgE1F8g3uec4zKNRV0&index=25
이번 강의에서는 메뉴 UI 를 추가해본다.
시작해보자.
게임 메뉴를 추가해보자.
새 씬 Menu 를 만들어 저장하고, 새 캔버스 오브젝트를 생성하고 2D 모드로 들어가자.
UI 스케일 모드를 화면 크기에 따라 스케일하도록 설정,
해상도를 설정해준다.
게임 타이틀을 작성 할 텍스트(TMP) 오브젝트를 생성하고,
텍스트, 폰트, 폰트 사이즈를 설정한다.
버튼 오브젝트를 추가하고 텍스트, 사이즈 등을 설정해
게임 시작, 설정, 종료 버튼을 추가해준다.
위의 버튼들을 Main Menu 라는 빈 오브젝트를 생성해 하위 오브젝트로 넣어서 구분해두자.
비활성화 한 후 Option Menu 라는 빈 오브젝트를 만들고 설정 메뉴에 들어갈 오브젝트들을 만들자.
Option Menu 에는 볼륨 조절을 위한 슬라이더와, 어떤것을 조절하는지 표시할 텍스트를 추가해준다.
화면 해상도를 설정하기 위한 토글 버튼도 추가해준다.
해상도 선택 시 하나만 선택되도록 하기 위해 Option Menu 오브젝트에 가서 토글 그룹을 추가해준다.
토글 그룹에 해상도 토글 버튼 세 개를 할당해준다.
그리고 메인 메뉴로 돌아가기 위한 뒤로가기 버튼도 만들어주자.
이제 메뉴의 버튼, 토글 버튼, 슬라이더들이 작동할 수 있도록
빈 오브젝트 Menu Manager 를 만들고 Menu 스크립트를 만들어 적용한 뒤 작성하자.
| Menu 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Menu : MonoBehaviour {
public GameObject mainMenuHolder; // 메인 메뉴 오브젝트
public GameObject optionMenuHolder; // 설정 메뉴 오브젝트
public Slider[] volumeSliders; // 볼륨 슬라이더 배열
public Toggle[] resolutionToggles; // 해상도 토글 버튼 배열
public int[] screenWidths; // 해상도 배열
public Toggle fullScreenToggle; // 전체화면 토글 버튼
int activeScreenResIndex; // 활성화 중인 해상도 인덱스
void Start() {
activeScreenResIndex = PlayerPrefs.GetInt("screen res index"); // 활성회 해상도 인덱스 가져오기
bool isFullScreen = PlayerPrefs.GetInt("fullScreen") == 1 ? true : false; // 전체화면 여부 가져오기
volumeSliders[0].value = AudioManager.instance.masterVolumePercent; // 마스터 볼륨 슬라이더 값 설정
volumeSliders[1].value = AudioManager.instance.musicVolumePercent; // 배경음악 볼륨 슬라이더 값 설정
volumeSliders[2].value = AudioManager.instance.sfxVolumePercent; // 효과음 볼륨 슬라이더 값 설정
for(int i = 0; i < resolutionToggles.Length; i++) {
resolutionToggles[i].isOn = i == activeScreenResIndex; // 화면 해상도 토글 버튼 설정
}
fullScreenToggle.isOn = isFullScreen; // 전체화면 설정
}
// ■ 게임 시작 메소드
public void Play() {
SceneManager.LoadScene("Game"); // 게임 씬 로드
}
// ■ 게임 종료 메소드
public void Quit() {
Application.Quit(); // 애플리케이션 종료
}
// ■ 설정 메뉴 이동 메소드
public void OptionMenu() {
mainMenuHolder.SetActive(false); // 메인 메뉴 오브젝트 비활성화
optionMenuHolder.SetActive(true); // 설정 메뉴 오브젝트 활성화
}
// ■ 메인 메뉴 이동 메소드
public void MainMenu() {
mainMenuHolder.SetActive(true); // 메인 메뉴 오브젝트 활성화
optionMenuHolder.SetActive(false); // 설정 메뉴 오브젝트 비활성화
}
// ■ 해상도 설정 메소드
public void SetScreenResolution(int i) {
if (resolutionToggles[i].isOn) { // 해당 토글 버튼 활성화 시
activeScreenResIndex = i; // 활성화 해상도 인덱스 설정
float aspectRatio = 16 / 9f; // 세로 비율 설정
Screen.SetResolution(screenWidths[i], (int)(screenWidths[i] / aspectRatio), false); // 화면 비율 설정
PlayerPrefs.SetInt("screen res index", activeScreenResIndex); // 활성화 해상도 인덱스 저장
PlayerPrefs.Save(); // 저장
}
}
// ■ 전체화면 설정 메소드
public void SetFullScreen(bool isFullScreen) {
for(int i = 0; i < resolutionToggles.Length; i++) { // 해상도 토글 버튼 수만큼 반복
resolutionToggles[i].interactable = !isFullScreen; // 해상도 토글 버튼 비활성화
}
if (isFullScreen) { // 전체화면 설정 체크 시
Resolution[] allResolutions = Screen.resolutions; // 해상도 배열 생성(모든 해상도가 저장됨)
Resolution maxResolution = allResolutions[allResolutions.Length - 1]; // 최대 해상도 저장
Screen.SetResolution(maxResolution.width, maxResolution.height, true); // 해상도 설정, 최대 해상도와 전체화면으로
}
else { // 전체화면 설정 체크 안된 경우
SetScreenResolution(activeScreenResIndex); // 활성화 해두었던 해상도로 재설정
}
PlayerPrefs.SetInt("fullScreen", ((isFullScreen) ? 1 : 0)); // 전체화면 여부 저장
PlayerPrefs.Save(); // 저장
}
// ■ 마스터 볼륨 설정 메소드
public void SetMasterVolume(float value) {
AudioManager.instance.SetVolume(value, AudioManager.AudioChannel.Master); // 마스터 볼륨 설정
}
// ■ 음악 볼륨 설정 메소드
public void SetMusicVolume(float value) {
AudioManager.instance.SetVolume(value, AudioManager.AudioChannel.Music); // 배경음악 볼륨 설정
}
// ■ 효과음 볼륨 설정 메소드
public void SetSfxVolume(float value) {
AudioManager.instance.SetVolume(value, AudioManager.AudioChannel.Sfx); // 효과음 볼륨 설정
}
}
이제 스크립트의 MainMenuHolder, OptionMenuHolder 를 할당해주고,
만들어둔 버튼, 토글 버튼, 슬라이더들에 각각의 기능을 하는 메소드를 할당해준다.
메인, 설정 메뉴 오브젝트 할당
전체화면 토글 버튼 오브젝트 할당
시작 버튼 Play() 메소드 할당
설정 버튼 OptionMenu() 메소드 할당
종료 버튼 Quit() 메소드 할당
뒤로가기 버튼 MainMenu() 메소드 할당
마스터 볼륨 슬라이더 SetMasterVolume() 메소드 할당
배경음악 볼륨 슬라이더 SetMusicVolume() 메소드 할당
효과음 볼륨 슬라이더 SetSfxVolume() 메소드 할당
960 X 540 해상도 설정 토글 버튼 SetScreenResolution() 메소드 할당, 인덱스 0으로 설정
1280 X 720 해상도 설정 토글 버튼 SetScreenResolution() 메소드 할당, 인덱스 1로 설정
1920 X 1080 해상도 설정 토글 버튼 SetScreenResolution() 메소드 할당, 인덱스 2로 설정
전체화면 설정 토글 버튼 SetFullScreen() 메소드 할당
Menu 스크립트에서 volumeSliders, resolutionToggles, screenWidths 를 설정해주자.
잠시 Game 씬으로 가서 Audio Manager 오브젝트를 프리팹으로 만들고
다시 Menu 씬으로 돌아와 씬에 추가해준다.
AudioManager 스크립트도 수정한다.
| AudioManager 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour {
public enum AudioChannel {Master, Sfx, Music}; // 오디오 채널
// 값을 참조하는 권한은 public 이지만 값을 설정하는 권한은 private 로
public float masterVolumePercent { get; private set; } // 마스터 볼륨
public float sfxVolumePercent { get; private set; } // 효과음 볼륨
public float musicVolumePercent { get; private set; } // 음악 볼륨
AudioSource sfx2DSource; // 2D 효과음 오디오소스 레퍼런스
AudioSource[] musicSources; // 음악을 가져올 오디오소스 레퍼런스 배열
int activeMusicSourceIntdex; // 재생중인 음악 인덱스
public static AudioManager instance; // 싱글톤 패턴
Transform audioListener; // 오디오 리스너 위치 레퍼런스
Transform playerT; // 플레이어 위치 레퍼런스
SoundLibrary library; // 사운드 라이브러리 레퍼런스
void Awake() {
if(instance != null) { // 인스턴스가 생성되어 있다면
Destroy(gameObject); // 현재 게임 오브젝트 파괴
}
else {
instance = this; // 인스턴스 설정
DontDestroyOnLoad(gameObject); // 게임 로드 시 이 게임 오브젝트가 파괴되지 않도록 한다
library = GetComponent<SoundLibrary>(); // 사운드 라이브러리 할당
musicSources = new AudioSource[2]; // 크기 설정하여 할당
for(int i = 0; i < 2; i++) {
GameObject newMusicSource = new GameObject("Music Source " + (i + 1)); // 오디오소스를 가질 오브젝트 생성
musicSources[i] = newMusicSource.AddComponent<AudioSource>(); // 오디오소스 할당
newMusicSource.transform.parent = transform; // 부모 오브젝트 설정
}
GameObject newSfxSource = new GameObject("2D sfx Source"); // 2D 오디오소스를 가질 오브젝트 생성
sfx2DSource = newSfxSource.AddComponent<AudioSource>(); // 오디오소스 할당
newSfxSource.transform.parent = transform; // 부모 오브젝트 설정
audioListener = FindObjectOfType<AudioListener>().transform; // 오디오 리스너가 있는 오브젝트의 위치를 저장
if(FindObjectOfType<Player>() != null) { // 플레이어 오브젝트가 있는 경우
playerT = FindObjectOfType<Player>().transform; // 플레이어 오브젝트의 위치 저장
}
// PlayerPrefs 를 사용해 저장한 볼륨을 불러와 게임에 적용한다.
masterVolumePercent = PlayerPrefs.GetFloat("master vol", 1);
sfxVolumePercent = PlayerPrefs.GetFloat("sfx vol", 1);
musicVolumePercent = PlayerPrefs.GetFloat("music vol", 1);
}
}
void Update() {
if(playerT != null) { // 플레이어가 있는 경우
audioListener.position = playerT.position; // 오디오 리스너가 있는 오브젝트 위치를 플레이어 위치로 설정
}
}
// ■ 사운드 볼륨 조절 메소드
public void SetVolume(float volumePercent, AudioChannel channel) {
switch(channel) {
case AudioChannel.Master: // 마스터 볼륨 설정
masterVolumePercent = volumePercent;
break;
case AudioChannel.Sfx: // 효과음 볼륨 설정
sfxVolumePercent = volumePercent;
break;
case AudioChannel.Music: // 음악 볼륨 설정
musicVolumePercent = volumePercent;
break;
}
musicSources[0].volume = musicVolumePercent * masterVolumePercent; // 설정한 첫 번째 음악의 볼륨 설정
musicSources[1].volume = musicVolumePercent * masterVolumePercent; // 설정한 두 번째 음악의 볼륨 설정
// PlayerPrefs 를 사용해 볼륨을 저장하고 다음번에 게임을 실행할 때도 적용되도록 한다.
PlayerPrefs.SetFloat("master vol", masterVolumePercent);
PlayerPrefs.SetFloat("sfx vol", sfxVolumePercent);
PlayerPrefs.SetFloat("music vol", musicVolumePercent);
PlayerPrefs.Save();
}
// ■ 음악 재생 메소드
public void PlayMusic(AudioClip clip, int fadeDuration = 1) {
activeMusicSourceIntdex = 1 - activeMusicSourceIntdex; // 재생할 음악 인덱스 설정
musicSources[activeMusicSourceIntdex].clip = clip; // 재생할 음악 클립 설정
musicSources[activeMusicSourceIntdex].Play(); // 음악 재생
StartCoroutine(AnimateMusicCrossFade(fadeDuration)); // 음악 크로스페이드 코루틴 실행
}
// ■ 오디오 클립과 위치를 받아 해당 위치에 사운드를 재생하는 메소드
public void PlaySound(AudioClip clip, Vector3 pos) {
if(clip != null) { // 오디오 클립이 있는 경우
AudioSource.PlayClipAtPoint(clip, pos, sfxVolumePercent * masterVolumePercent);
// pos 위치에 효과음 * 마스터 볼륨 크기로 clip 재생
}
}
// ■ 오디오 이름과 위치를 받아 해당 위치레 사운드를 재생하는 메소드
public void PlaySound(string soundName, Vector3 pos) {
PlaySound(library.GetClipFromName(soundName), pos); // 사운드 재생 메소드 호출
}
// ■ 오디오 이름과 위치를 받아 2D 기준 사운드를 재생하는 메소드
public void PlaySound2D(string soundName) {
sfx2DSource.PlayOneShot(library.GetClipFromName(soundName), sfxVolumePercent * masterVolumePercent);
// 효과음을 2D 사운드로 출력
}
// ■ 음악을 크로스페이드(부드럽게 변환) 코루틴
IEnumerator AnimateMusicCrossFade(int duration) {
float percent = 0; // 음악 퍼센트
while(percent < 1) {
percent += Time.deltaTime * 1 / duration; // 퍼센트 계산
musicSources[activeMusicSourceIntdex].volume = Mathf.Lerp(0, musicVolumePercent * masterVolumePercent, percent);
musicSources[1 - activeMusicSourceIntdex].volume = Mathf.Lerp(musicVolumePercent * masterVolumePercent, 0, percent);
// 음악 볼륨 설정, Lerp 를 통해 활성화하는 음악은 점점 커지게, 비활성와 음악은 점점 작아지게 설정.
yield return null; // 다음 프레임으로 스킵
}
}
}
빌드 설정을 하고 빌드한 뒤 실행해보자.
설정하는 값들이 게임을 껐다 켜도 다시 잘 적용된다.
(녹화는 못했지만 시작 버튼 클릭 시 게임도 원활하게 플레이 가능하다.)
그리고 여기서 유의할 점이 있다.
지금 보면 해상도 토글 버튼의 텍스트가 깨지는 것을 볼 수 있는데, 이는
토글 버튼 옆의 텍스트가 TMP(텍스트 메쉬 프로)가 아닌 일반 텍스트 오브젝트이기 때문이다.
반면 TMP 오브젝트를 사용한 텍스트는 깨지지 않는다.
다음 강의에서 탑다운 슈팅 게임 제작을 마무리해보자.
'Unity > 게임개발' 카테고리의 다른 글
[Unity] 게임 개발 일지 | 탑다운 슈팅 25 (0) | 2024.02.22 |
---|---|
[Unity] 게임 개발 일지 | 탑다운 슈팅 23 (0) | 2023.12.15 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 22 (0) | 2023.12.14 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 21 (0) | 2023.12.13 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 20 (1) | 2023.12.12 |