본문 바로가기

Unity 공부/Unity_2D

[Day 15] 오디오 시스템 구축

[ 오디오 시스템 구축 ]  --> 오디오 오브젝트와 로직 구현

  • Hierachy에 Create Empty(AudioManager)
  • Audio 폴더에서 효과음 하나 넣기(드래그&드랍)
  • C# 스크립트 생성(AudioManager)
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AudioManager : MonoBehaviour
{
    public static AudioManager instance;

    [Header("# BGM")] // 이쁘니 작업
    public AudioClip bgmClip; // bgm 클립
    public float bgmVolume; // bgm 볼륨
    AudioSource bgmPlayer; // 오디오 소스
    AudioHighPassFilter bgmEffect; // bgm 이펙트

    [Header("# SFX")] // 이쁘니 작업
    public AudioClip[] SfxClips; // 효과음 클립
    public float sfxVolume; // 효과음 볼륨
    public int channels; // 채널(동시다발적으로 재생 시키려면 채널 필요)
    AudioSource[] sfxPlayers; // 오디오 소스
    int channelIndex; // 채널 번호

    public enum Sfx { Dead, Hit, LevelUp=3, Lose, Melee, Range = 7, Select, Win } // 열거형으로 정리

    void Awake()
    {
        instance = this; // 정적 메모리에 올려두기
        Init();
    }

    void Init()
    {
        // 배경음 플레이어 초기화
        GameObject bgmObject = new GameObject("BgmPlayer");
        bgmObject.transform.parent = transform; // 자식 오브젝트 생성
        bgmPlayer = bgmObject.AddComponent<AudioSource>(); // 오디오소스 초기화
        bgmPlayer.playOnAwake = false; // 게임 시작하자마자 실행X
        bgmPlayer.loop = true; // 루프 설정
        bgmPlayer.volume = bgmVolume; // 볼륨
        bgmPlayer.clip = bgmClip; // bgm 클립 가져오기
        bgmEffect = Camera.main.GetComponent<AudioHighPassFilter>(); // bgm 이펙트 초기화

        // 효과음 플레이어 초기화
        GameObject sfxObject = new GameObject("SfxPlayer");
        sfxObject.transform.parent = transform; // 자식 오브젝트 생성
        sfxPlayers = new AudioSource[channels]; // 채널 개수만큼 만들기

        for(int index=0; index<sfxPlayers.Length; index++)
        {
            sfxPlayers[index] = sfxObject.AddComponent<AudioSource>();
            sfxPlayers[index].playOnAwake = false; // 게임 시작하자마자 실행X
            sfxPlayers[index].bypassListenerEffects = true; // 효과음 필터 끔
            sfxPlayers[index].volume = sfxVolume; // 볼륨
        }
    }

    public void PlayBgm(bool isPlay) // 배경음악 재생 함수
    {
        if(isPlay)
        {
            bgmPlayer.Play(); // 재생
        }
        else
        {
            bgmPlayer.Stop(); // 중단
        }
    }

    public void EffectBgm(bool isPlay) // bgm 이펙트 활성화 함수
    {
        bgmEffect.enabled = isPlay; 
    }

    public void Playsfx(Sfx sfx) // 효과 재생 함수
    {
        for(int index=0; index<sfxPlayers.Length; index++) // Sfx 개수만큼 루프
        {
            int loopIndex = (index + channelIndex) % sfxPlayers.Length;

            if (sfxPlayers[loopIndex].isPlaying) // 실행중인 효과음은 넘어감
            {
                continue;
            }

            int ranIndex = 0;
            if(sfx == Sfx.Hit || sfx == Sfx.Melee) // 효과음을 랜덤으로 재생
            {
                ranIndex = Random.Range(0, 2);
            }
            channelIndex = loopIndex;
            sfxPlayers[loopIndex].clip = SfxClips[(int)sfx];
            sfxPlayers[loopIndex].Play();
            break;
        }
    }
}
  • Audio폴더의 에셋 파일들을 모두 선택 후 AudioManager의 Sfx Clips에 넣어주기(드래그&드랍)
  • 기존의 GameManager 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    [Header("# Game Conrol")]
    public bool isLive;
    public float gameTime;
    public float maxGameTime = 2 * 10f;
    [Header("# Player Info")]
    public int playerId;
    public float hp;
    public float maxHp = 100;
    public int level;
    public int kill;
    public int exp;
    public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
    [Header("# Game Object")]
    public PoolManager pool;
    public Player player;
    public LevelUp uiLevelUp;
    public Result uiResult;
    public GameObject enemyCleaner;

    private void Awake()
    {
        instance = this; 
    }

    public void GameStart(int id)
    {
        playerId = id;
        hp = maxHp; 

        player.gameObject.SetActive(true);
        uiLevelUp.Select(playerId % 2); 
        Resume();

        AudioManager.instance.PlayBgm(true); // bgm 재생
        AudioManager.instance.Playsfx(AudioManager.Sfx.Select); // 캐릭터 선택 효과음
    }

    public void GameOver()
    {
        StartCoroutine(GameOverRoutine());
    }

    IEnumerator GameOverRoutine()
    {
        isLive = false;
        yield return new WaitForSeconds(0.5f);
        uiResult.gameObject.SetActive(true);
        uiResult.Lose();
        Stop();

        AudioManager.instance.PlayBgm(false); // bgm 중단
        AudioManager.instance.Playsfx(AudioManager.Sfx.Lose); // 패배 효과음 
    }

    public void GameRetry()
    {
        SceneManager.LoadScene(0);
    }

    void Update()
    {
        if(!isLive)
        {
            return;
        }
        gameTime += Time.deltaTime;

        if (gameTime > maxGameTime){
            gameTime = maxGameTime;
            GameVictory();
        }
    }


    public void GetExp()
    {
        if (!isLive)
        {
            return;
        }

        exp++;
        if (exp == nextExp[Mathf.Min(level, nextExp.Length-1)])
        {
            level++;
            exp = 0;
            uiLevelUp.Show();
        }
    }

    public void Stop()
    {
        isLive = false;
        Time.timeScale = 0;
    }

    public void Resume()
    {
        isLive = true;
        Time.timeScale = 1;
    }

    public void GameVictory()
    {
        StartCoroutine(GameVictoryRoutine());
    }

    IEnumerator GameVictoryRoutine()
    {
        isLive = false;
        enemyCleaner.SetActive(true);
        yield return new WaitForSeconds(0.5f);
        uiResult.gameObject.SetActive(true);
        uiResult.Win();
        Stop();

        AudioManager.instance.PlayBgm(false); // bgm 중단
        AudioManager.instance.Playsfx(AudioManager.Sfx.Win); // 승리 효과음
    }
}
  • 기존의 LevelUp 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;

public class LevelUp : MonoBehaviour
{
    RectTransform rect;
    Item[] items;

    private void Awake()
    {
        rect = GetComponent<RectTransform>();
        items = GetComponentsInChildren<Item>(true);  
    }

    public void Show()
    {
        Next();
        rect.localScale = Vector3.one;
        GameManager.instance.Stop();
        AudioManager.instance.Playsfx(AudioManager.Sfx.LevelUp); // 레벨업 효과음 
        AudioManager.instance.EffectBgm(true); // bgm 이펙트 활성화
    }

    public void Hide()
    {
        rect.localScale = Vector3.zero;
        GameManager.instance.Resume();
        AudioManager.instance.Playsfx(AudioManager.Sfx.Select); // 선택 효과음
        AudioManager.instance.EffectBgm(false); // bgm 이펙트 비활성화
    }

    public void Select(int index)
    {
        items[index].OnClick();
    }

    void Next()
    {
        // 1. 모든 아이템 비활성화
        foreach (Item item in items)
        {
            item.gameObject.SetActive(false);
        }
        // 2. 그 중에서 래넘 3개 아이템 활성화
        int[] ran = new int[3];
        while(true)
        {
            ran[0] = Random.Range(0, items.Length);
            ran[1] = Random.Range(0, items.Length);
            ran[2] = Random.Range(0, items.Length);
            if (ran[0] != ran[1] && ran[1] != ran[2] && ran[0] != ran[2])
                break;
        }

        for(int index=0; index < ran.Length; index++)
        {
            Item ranItem = items[ran[index]];
            
            // 3. 만렙 아이템의 경우는 소비 아이템으로 대체
            if (ranItem.level == ranItem.data.damages.Length)
            {
                items[4].gameObject.SetActive(true);
            }
            else
            {
                ranItem.gameObject.SetActive(true);
            }


        }

    }
}
  • 기존의 Weapon 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using System.Runtime.ConstrainedExecution;
using UnityEngine;

public class Weapon : MonoBehaviour
{
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed;

    float timer;
    Player player;

    void Awake(){
        player = GameManager.instance.player;
    }

    void Update(){
        if (!GameManager.instance.isLive)
            return;

        switch (id){
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;

            default:
                timer += Time.deltaTime;

                if(timer > speed){
                    timer = 0f;
                    Fire();
                }
                break;

        }

        // .. Test Code ..
        if (Input.GetButtonDown("Jump")){
            LevelUp(10, 1);
        }
    }

    public void LevelUp(float damage, int count)
    {
        this.damage += damage * Character.Damage;
        this.count += count;

        if(id == 0){
            Batch();
        }

        player.BroadcastMessage("ApplyGear",SendMessageOptions.DontRequireReceiver);
    }

    public void Init(ItemData data)
    {
        // Basic Set
        name = "Weapon " + data.itemId;
        transform.parent = player.transform;
        transform.localPosition = Vector3.zero;

        // Property Set
        id = data.itemId;
        damage = data.baseDamage * Character.Damage;
        count = data.baseCount + Character.Count;

        for(int index=0; index<GameManager.instance.pool.prefabs.Length; index++){
            if (data.projectile == GameManager.instance.pool.prefabs[index]){
                prefabId = index;
                break;
            }    
        }

        switch(id){
            case 0:
                speed = 150 * Character.WeaponSpeed; // (+) --> 반시계방향
                Batch();
                break;

            default:
                speed = 0.5f * Character.WeaponRate;
                break;
        }

        // Hand Set
        Hand hand = player.hands[(int)data.itemType];
        hand.spriter.sprite = data.hand;
        hand.gameObject.SetActive(true);

        player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
    }

    void Batch(){
        for(int index=0; index < count; index++) {
            Transform bullet;
           
            if(index < transform.childCount){
                bullet = transform.GetChild(index);
            }
            else{
                bullet = GameManager.instance.pool.Get(prefabId).transform;
                bullet.parent = transform;

            }

            bullet.localPosition = Vector3.zero;
            bullet.localRotation = Quaternion.identity;

            Vector3 rotVec = Vector3.forward * 350 * index / count;
            bullet.Rotate(rotVec);
            bullet.Translate(bullet.up * 1.5f, Space.World);
            bullet.GetComponent<Bullet>().Init(damage, -1, Vector3.zero); // -1 is Infinity Per.

        }
    }

    void Fire()
    {
        if (!player.scanner.nearestTarget){
            return;
        }

        Vector3 targetPos = player.scanner.nearestTarget.position;
        Vector3 dir = targetPos - transform.position;
        dir = dir.normalized;

        Transform bullet = GameManager.instance.pool.Get(prefabId).transform;
        bullet.position = transform.position;
        bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir);
        bullet.GetComponent<Bullet>().Init(damage, count, dir);

        AudioManager.instance.Playsfx(AudioManager.Sfx.Range); // 발사 효과음

    }
}
  • 기존의 Enemy 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public float speed;
    public float hp;
    public float maxhhp;
    public RuntimeAnimatorController[] animCon;
    public Rigidbody2D target;

    bool isLive;

    Rigidbody2D rigid;
    Collider2D coll;
    Animator anim;
    SpriteRenderer spriter;
    WaitForFixedUpdate wait;

    void Awake(){
        rigid = GetComponent<Rigidbody2D>();
        coll = GetComponent<Collider2D>();
        anim = GetComponent<Animator>();
        spriter = GetComponent<SpriteRenderer>();
        wait = new WaitForFixedUpdate();

    }

    void FixedUpdate(){
        if (!isLive || anim.GetCurrentAnimatorStateInfo(0).IsName("Hit")){
            return;
        }
        if (!GameManager.instance.isLive)
            return;

        Vector2 dirVec = target.position - rigid.position;
        Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime;
        rigid.MovePosition(rigid.position + nextVec);
        rigid.velocity = Vector2.zero;
    }

    void LateUpdate(){
        if (!isLive){
            return;
        }
        if (!GameManager.instance.isLive)
            return;

        spriter.flipX = target.position.x < rigid.position.x;
    }

    void OnEnable(){
        target = GameManager.instance.player.GetComponent<Rigidbody2D>();
        isLive = true;
        coll.enabled = true;
        rigid.simulated = true;
        spriter.sortingOrder = 3;
        anim.SetBool("Dead", false);
        hp = maxhhp;
    }

    public void Init(SpawnData data){
        anim.runtimeAnimatorController = animCon[data.spriteType];
        speed = data.speed;
        maxhhp = data.hp;
        hp = data.hp;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (!collision.CompareTag("Bullet") || !isLive){
            return;
        }

        hp -= collision.GetComponent<Bullet>().damage;
        StartCoroutine(KnockBack());

        if (hp > 0) {
            anim.SetTrigger("Hit");
            AudioManager.instance.Playsfx(AudioManager.Sfx.Hit); // 히트 효과음
        }
        else{
            isLive = false;
            coll.enabled = false;
            rigid.simulated = false;
            spriter.sortingOrder = 2;
            anim.SetBool("Dead", true);
            GameManager.instance.kill++;
            GameManager.instance.GetExp();

            if (GameManager.instance.isLive) // 게임 진행중에만 실행
            {
                AudioManager.instance.Playsfx(AudioManager.Sfx.Dead); // 사망 효과음
            }
        }
    }

    IEnumerator KnockBack(){
        yield return wait; // 하나의 물리 프레임을 딜레이
        Vector3 playerPos = GameManager.instance.player.transform.position;
        Vector3 dirVec = transform.position - playerPos;
        rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);

    }

    void Dead(){
        gameObject.SetActive(false);
    }
}
  • bgm 에셋을 다운로드 후 Audio 파일에 넣어주기
  • bgm 에셋을 AudioManager의 Bgm Clip에 넣어주기(드래그&드랍)
  • Main Camera 오브젝트에 Add Component
        - Audio High Pass Filter --> 비활성화

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

★Slow and Steady★

'Unity 공부 > Unity_2D' 카테고리의 다른 글

[Day 17] 모바일 빌드  (0) 2023.07.27
[Day 16] 로직 보완  (0) 2023.07.26
[Day 14] 캐릭터 해금 시스템  (0) 2023.07.24
[Day 13] 플레이 캐릭터 선택  (0) 2023.07.19
[Day 12] 게임 시작과 종료 구현  (0) 2023.07.18