(38) 오디오 제어 & 최종보스 제작
오늘은 환경설정으로 오디오가 제어되도록 해보겠다
사실 하면서 복잡하고 생각보다 잘 안풀리길래 그냥 환경설정 자체를 빼버릴까도 고민이 많았지만...
그래도 한번 해보자!
예전에 만들어놓은 환경설정창이다. 하지만 아직 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 스위치가 켜져 있으면 재생, 없으면 재생하지 않는것이다
---------------------------------------
그 다음은 최종 보스
원래는 진짜 센걸로 하나 만들지, 아니면 오히려 기믹적인 요소로 심플하게 디자인할지 고민하다 심플한것을 택했다
디자인은 기본 적 유닛에 완벽한 검은색으로 크게 다른건 없다
대신 기본구체 대신 내구도가 없는 새로운 형태의 총알을 발사한다
이건 사실 예전에 만들어놓은 블랙홀 아이템으로, 여기서 사용하게 되었다
(20) 아이템 #사용구현
•구체 충돌 시 반사•구체 정지 시 확장•Deadzone•Firezone•발사 구현•확장이 완료 시 고정 & 충돌 피해 X•구체 내구도 부여 & 내구도 소진시 구체 파괴•사운드 이펙트•메인 화면 & 환경설정
sangeun00.tistory.com
이대로만 끝나면 최종 보스 난이도가 아무 의미 없어지므로 한가지 더 추가한것이 있다
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가 너무 강력하여 최종보스가 된 감도 없지 않아 있다