ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (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 스위치가 켜져 있으면 재생, 없으면 재생하지 않는것이다

     

    ---------------------------------------

     

    그 다음은 최종 보스

     

    왼쪽은 이미지, 오른쪽은 자세한 스탯이다

     

    원래는 진짜 센걸로 하나 만들지, 아니면 오히려 기믹적인 요소로 심플하게 디자인할지 고민하다 심플한것을 택했다

     

    디자인은 기본 적 유닛에 완벽한 검은색으로 크게 다른건 없다

     

    대신 기본구체 대신 내구도가 없는 새로운 형태의 총알을 발사한다

     

    이건 사실 예전에 만들어놓은 블랙홀 아이템으로, 여기서 사용하게 되었다

     

    (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가 너무 강력하여 최종보스가 된 감도 없지 않아 있다

Designed by Tistory.