-
(38) 오디오 제어 & 최종보스 제작Galaxy Ball/2. 싱글플레이 - 스토리모드 2024. 7. 30. 04:04
오늘은 환경설정으로 오디오가 제어되도록 해보겠다
사실 하면서 복잡하고 생각보다 잘 안풀리길래 그냥 환경설정 자체를 빼버릴까도 고민이 많았지만...
그래도 한번 해보자!
예전에 만들어놓은 환경설정창이다. 하지만 아직 BGM, Sound Effent On/Off 기능이 구현되어 있지 않다
using System; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; public class SettingButtonManager : MonoBehaviour { public Button BGM_ON; public Button BGM_OFF; public Button Sound_Effect_ON; public Button Sound_Effect_OFF; public Button Credit; public Button Back; public AudioSource ButtonAudio; private BGMControl bGMControl; private void Start() { bGMControl = FindObjectOfType<BGMControl>(); LoadButtonStates(); SetupButton(BGM_ON, BGM_OFF, () => { bGMControl.BGMSwitch = false; bGMControl.SaveAudioSettings(); // 상태 저장 }); SetupButton(BGM_OFF, BGM_ON, () => { bGMControl.BGMSwitch = true; bGMControl.SaveAudioSettings(); // 상태 저장 }); SetupButton(Sound_Effect_ON, Sound_Effect_OFF, () => { bGMControl.SoundEffectSwitch = false; bGMControl.SaveAudioSettings(); // 상태 저장 }); SetupButton(Sound_Effect_OFF, Sound_Effect_ON, () => { bGMControl.SoundEffectSwitch = true; bGMControl.SaveAudioSettings(); // 상태 저장 }); Credit.onClick.AddListener(() => { ButtonAudio.Play(); SceneManager.LoadScene("Credit Scene"); }); Back.onClick.AddListener(() => { ButtonAudio.Play(); SceneManager.LoadScene("Start Scene"); }); } private void SetupButton(Button onButton, Button offButton, Action action) { onButton.onClick.AddListener(() => { action(); ButtonAudio.Play(); onButton.gameObject.SetActive(false); offButton.gameObject.SetActive(true); SaveButtonStates(); }); } private void SaveButtonStates() { PlayerPrefs.SetInt("BGM_ON", BGM_ON.gameObject.activeSelf ? 1 : 0); PlayerPrefs.SetInt("BGM_OFF", BGM_OFF.gameObject.activeSelf ? 1 : 0); PlayerPrefs.SetInt("Sound_Effect_ON", Sound_Effect_ON.gameObject.activeSelf ? 1 : 0); PlayerPrefs.SetInt("Sound_Effect_OFF", Sound_Effect_OFF.gameObject.activeSelf ? 1 : 0); PlayerPrefs.Save(); } private void LoadButtonStates() { BGM_ON.gameObject.SetActive(PlayerPrefs.GetInt("BGM_ON", 1) == 1); BGM_OFF.gameObject.SetActive(PlayerPrefs.GetInt("BGM_OFF", 0) == 1); Sound_Effect_ON.gameObject.SetActive(PlayerPrefs.GetInt("Sound_Effect_ON", 1) == 1); Sound_Effect_OFF.gameObject.SetActive(PlayerPrefs.GetInt("Sound_Effect_OFF", 0) == 1); } }
우선 위 사진에 보이는 버튼들을 컨트롤하는 스크립트이다.
On/Off 버튼을 누를때마다 BgmControl 스크립트에 있는 스위치에 값을 주는것을 확인할 수 있다
using UnityEngine.SceneManagement; using UnityEngine; public class BGMControl : MonoBehaviour { public AudioSource[] SoundEffect; public bool BGMSwitch = true; public bool SoundEffectSwitch = true; private void Awake() { // 게임 시작 시 저장된 상태를 불러오기 LoadAudioSettings(); } void Update() { UpdateAudioSources(); } void UpdateAudioSources() { ToggleAudioSources(SoundEffect, SoundEffectSwitch); } void ToggleAudioSources(AudioSource[] sources, bool isEnabled) { foreach (var source in sources) { if (!isEnabled && source.isPlaying) { source.Stop(); } source.enabled = isEnabled; } } private void StopAllAudio(AudioSource[] sources) { foreach (var source in sources) { if (source.isPlaying) { source.Stop(); } } } // 게임 시작 시 저장된 상태를 불러오기 private void LoadAudioSettings() { BGMSwitch = PlayerPrefs.GetInt("BGMSwitch", 1) == 1; // 기본값은 true SoundEffectSwitch = PlayerPrefs.GetInt("SoundEffectSwitch", 1) == 1; // 기본값은 true } // 상태가 변경될 때 저장하기 public void SaveAudioSettings() { PlayerPrefs.SetInt("BGMSwitch", BGMSwitch ? 1 : 0); PlayerPrefs.SetInt("SoundEffectSwitch", SoundEffectSwitch ? 1 : 0); PlayerPrefs.Save(); } // 새로운 메서드 추가 public void SoundEffectPlay(int index) { if (index >= 0 && index < SoundEffect.Length) { SoundEffect[index].Play(); } else { Debug.LogWarning("SoundEffectPlay: Index out of range."); } } }
그리고 이게 BGMControl이다. 어느 씬으로 이동하던 스위치 on/off 여부는 저장되어야 하고
한가지 더 추가해 이 여부는 게임을 종료해도 저장되어 있어야 한다
그렇기 때문에 어느씬에 가던 삭제되지 않는 Start 씬의 GameManager에 BGM Control을 넣어준것이다
이제 SoundEffect 배열에 오디오소스 5개를 넣어준다
이 5개의 소스들은 게임에 사용될 총 5가지의 사운드이펙트이다
그래서 사운드 이펙트가 재생될 일이 생길때마다 bgmControl.SoundEffectPlay에서 골라와 사운드를 재생하는것이다
물론 BGMControl에 있는 SoundEffectPlay 메서드는 스위치가 켜져있을 때만 재생이 될것이다
위 사진에 보이는 코드처럼 스위치가 false라면 곧바로 사운드 재생이 정지되도록 걸어놨기 때문
이 과정을 거치면 환경설정에서 설정한것에 따라 사운드가 재생되기도, 묵음처리가 되기도 한다
BGM 재생에 관해서는 IsPlayBGM이라는 스크립트를 하나 만들어 인게임씬에 넣어주었다
배열에는 총 3개의 오디오소스가 들어가있는데
각각 기본 인게임 bgm, 중간보스용, 최종보스용 bgm이 들어가있다
물론 StageClearID를 사용하여 각각 25,45,65일때 원하는걸 뽑아 재생하도록 하였다
using System.Collections; using System.Collections.Generic; using UnityEngine; public class IsPlayBGM : MonoBehaviour { public AudioSource[] BGM; // 오디오 소스를 배열로 변경 private BGMControl bGMControl; void Start() { bGMControl = FindObjectOfType<BGMControl>(); if (bGMControl != null) { if (bGMControl.BGMSwitch) { // StageClearID 값에 따라 다른 오디오 소스를 재생 if (StageState.chooseStage == 25 || StageState.chooseStage == 45) { if (BGM.Length > 1 && BGM[1] != null) // 두 번째 오디오 소스가 존재하는지 확인 { BGM[1].Play(); } } else if(StageState.chooseStage == 65) { BGM[2].Play(); } else { if (BGM.Length > 0 && BGM[0] != null) // 첫 번째 오디오 소스가 존재하는지 확인 { BGM[0].Play(); } else { Debug.LogWarning("First audio source is missing or not assigned."); } } } else { // 모든 오디오 소스를 멈춤 foreach (var audioSource in BGM) { if (audioSource != null) { audioSource.Stop(); } } } } else { Debug.LogError("BGMControl object not found in the scene."); } } }
이건 뭐 간단하다. BGM 스위치가 켜져 있으면 재생, 없으면 재생하지 않는것이다
---------------------------------------
그 다음은 최종 보스
원래는 진짜 센걸로 하나 만들지, 아니면 오히려 기믹적인 요소로 심플하게 디자인할지 고민하다 심플한것을 택했다
디자인은 기본 적 유닛에 완벽한 검은색으로 크게 다른건 없다
대신 기본구체 대신 내구도가 없는 새로운 형태의 총알을 발사한다
이건 사실 예전에 만들어놓은 블랙홀 아이템으로, 여기서 사용하게 되었다
이대로만 끝나면 최종 보스 난이도가 아무 의미 없어지므로 한가지 더 추가한것이 있다
using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; public class BossCenter : MonoBehaviour { ............ private IEnumerator Attack1() { yield return new WaitForSeconds(Random.Range(MinFireTime, MaxFireTime)); float targetAngle = Random.Range(MinAngle, MaxAngle); float currentAngle = transform.eulerAngles.z; float rotationTime = 1f; // ȸ���ϴ� �� �ɸ��� �ð� float elapsedTime = 0f; while (elapsedTime < rotationTime) { elapsedTime += Time.deltaTime; float angle = Mathf.LerpAngle(currentAngle, targetAngle, elapsedTime / rotationTime); transform.eulerAngles = new Vector3(0, 0, angle); yield return null; } yield return new WaitForSeconds(1f); if (bossfires != null) { foreach (var enemy1Fire in bossfires) { enemy1Fire.SpawnBullet(); } } } private IEnumerator Attack2() { yield return new WaitForSeconds(7); if (Enemy.Length > 0) { GameObject enemy1 = Enemy[Random.Range(0, Enemy.Length)]; GameObject enemy2 = Enemy[Random.Range(0, Enemy.Length)]; GameObject enemy3 = Enemy[Random.Range(0, Enemy.Length)]; GameObject enemy4 = Enemy[Random.Range(0, Enemy.Length)]; Instantiate(enemy1, transform.position + new Vector3(-1.5f, 0, 0), Quaternion.identity); Instantiate(enemy2, transform.position + new Vector3(1.5f, 0, 0), Quaternion.identity); Instantiate(enemy3, transform.position + new Vector3(0, 1.5f, 0), Quaternion.identity); Instantiate(enemy4, transform.position + new Vector3(0, -1.5f, 0), Quaternion.identity); } } private IEnumerator RandomAttack() { while (true) { int randomAttack = Random.Range(0, 2); switch (randomAttack) { case 0: yield return StartCoroutine(Attack1()); break; case 1: yield return StartCoroutine(Attack2()); break; } yield return new WaitForSeconds(4f); } } }
최종 보스를 컨트롤 하는 스크립트 중 일부
간단하게 설명하면 최종보스는 Attack1,2 두개 중 하나를 랜덤으로 선택하여 공격한다
Attack1은 앞서 설명한 블랙홀 아이템을 발사하는것
Attack2는 여태 나온 적 유닛들 중 랜덤하게 4개를 선정해 자신의 앞,뒤,양옆에 생성하는것이다
그래서 스탯의 가장 아래 Enemy 배열을 추가한것이다
물론 모든 유닛이 다 들어간건 아니고 밸런스를 위해 몇가지 제한한게 있긴하다
이런 느낌이라고 생각하면 된다. 사실상 Attack2가 너무 강력하여 최종보스가 된 감도 없지 않아 있다
'Galaxy Ball > 2. 싱글플레이 - 스토리모드' 카테고리의 다른 글
(40) 포스터 + 최종 발표 ppt + 홍보 영상 (0) 2024.08.07 (39) 스토리+연출 완성 (0) 2024.08.02 (37) 저장 / 불러오기 구현 (0) 2024.07.28 (35) 새로운 적 유닛 정보창 (0) 2024.07.12 (34) 연출&대사 시스템 총정리 (0) 2024.07.11