Unity Chapter 6-6. 어메이징 볼링 게임 만들기 : 게임 매니저, 오디오, 최종 빌드

Date:     Updated:

카테고리:

태그:

인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!


Chapter 6. 어메이징 볼링 게임 만들기


🔔 게임 매니저

UI 만들기

  • Canvas (Canvas) : 말그대로 UI를 그리는 도화지가 됨
    • Info bar (Panel) : 점수를 띄워 줌
      • Socre Text(Text) : 현재 점수
      • Best Score Text(Text) : 현재까지의 최고 점수
    • Round Ready Panel (Panel) : 라운드 시작했을때, 라운드 끝났을 때 띄워줄 UI
      • Text (Text) : 레디 상태에선 “Ready…”, 라운드가 끝났을 땐 “Wait For Next Round…” 를 띄울 것.
  1. UI - Canvas 오브젝트 추가
  2. EventSystem은 지워주자. 유저가 손 못대게 할거라
  3. Canvas를 더블 클릭해서 씬 카메라를 맞춰준 후 씬창의 시점을 2D로 바꿔주자.
    • UI는 2D 시점으로 해놓는게 편집하기 편하다.
  4. Canvas의 Canvas Scaler 컴포넌트의 UI Scale Model 값을 Scale With Screen Size로 바꿔주자.
    • Constant Pixel Size : UI 요소의 크기가 변경이 안됨. 해상도 크기에 상관 없이 UI 요소의 크기는 고정되기 때문에 높은 해상도에서는 UI가 작게 보이는 문제가 생길 수 있음.
    • Scale With Screen Size : 기준이 되는 해상도를 설정하여 그 것에 맞는 UI 요소를 설계하면 게임 실행시 해상도가 변해도 알아서 해상도 크기에 맞게 UI 요소의 크기도 같이 변한다.
    • 기준 해상도가 되는 Reference Resoulution 갓값은 X : 640, Y : 360 으로 해준다.
    • Match 값은 Height쪽으로 쭉 땡겨 1로 만든다. 해상도가 변해서 UI 크기가 바뀌더라도 세로 방향은 왜곡이 되지 않도록.
  5. UI - Panel 오브젝트 추가
    • 이름은 Info bar
  6. Info barCanvas의 자식으로 넣어준다.
  7. 트랜스폼 툴바에서 Rect 툴을 선택한 후 Info bar의 크기를 줄여준다. 아래와 같은 모양이 되게끔
    • image
    • UI는 Rect 툴로 크기와 모양을 조정하는게 편하다.
  8. UI - Text 오브젝트를 2개 추가해준 후 둘 다 Info bar의 자식으로 넣어 준다.
    • 이름은 Score Text
    • 이름은 Best Score Text : 현재까지의 최고 점수
  9. Info bar에 Horizontal Layout Group 이라는 컴포넌트를 추가해준다.
    • 이 컴포넌트가 붙은 오브젝트의 자식 오브젝트들을 수평 정렬 시켜준다.
    • 자식인 Socre TextBest Score Text를 수평 정렬 해준다.
  10. Horizontal Layout Group 이라는 컴포넌트의 Control Child Size 값을 width, height 체크 해준다.
    • 여백 없이 두 자식 오브젝트에 꽉 채워 Info bar을 차지 하게끔.
  11. Socre Text의 내용을 “Score : 0”으로, Best Score Text 의 내용을 “Best Score : 0”으로 해준다.
  12. 두 텍스트 오브젝트의 정렬을 가운데, 중앙 정렬 해준다.
  13. 두 텍스트 오브젝트 모두 Best Fit 에 체크해준다.
    • 텍스트 오브젝트 크기에 맞춰 폰트 사이즈가 맞춰진다.
  14. UI - Panel 오브젝트 추가해 준 후 Canvas의 자식으로 넣어준다.
    • 이름은 Round Ready Panel
  15. UI - Text 오브젝트 추가해 준 후 Round Ready Panel의 자식으로 넣어준다.
    • 이름은 Text
    • 내용은 “Ready…” 로 해준다. (초기값)
    • 가운데 중앙 정렬, Best fit 체크
    • 텍스트 Text의 Anchor presets을 Alt키를 누른 후 stretch-stretch 하여 부모인 Round Ready Panel에 맞춰서 전부 차지하도록 해준다.
  16. Round Ready Panel는 꺼둔다.
    • 게임 매니저에서 매번 다음 라운드에 도달할 때마다 켜줄 것.

한 라운드의 구성

하나의 라운드는 3가지 페이지로 구성되어 있다. 아래 3가지 페이지가 매 라운드마다 반복 된다.

  1. Reday
    • 하는 일 :
    1. 카메라 리셋
    2. 포신 리셋
    3. 점수 리셋
    4. Ready UI를 띄운다. - 코루틴 함수를 사용해 대기 시간을 둠 - 대기 시간이 지나고 나면 Play 페이지로 넘어감
  2. Play
    • play 페이지는 무한 루프로 돌림
    • 볼이 바닥에 닿아 터지면 무한 루프를 빠져 나와 Play 페이지를 끝냄
  3. End
    • 하는 일
    1. 조작이 불가능 하도록 포신 등등을 꺼둔다.
    2. 전체 컴포넌트 리셋
    3. 엔딩용 UI를 띄운다. - 코루틴 함수를 사용해 대기 시간을 둠 - 대기 시간이 지나고 나면 Ready 페이지로 넘어감

📜 GameManager.cs

  • GameManager라는 빈 오브젝트를 만든 후 📜GameManager.cs 스크립트를 붙여 준다.
  • GameManager가 하는 일 : 전체적인 게임 로직을 관리
    • UI를 갱신
    • 다음 라운드로 넘겨 주고
    • 점수를 계산
  • 싱글톤 사용한다
    • 📜 GameManager.cs 는 여러 곳에서 쓰이고 + 단 하나만 있기 때문에.
    • GameManager 클래스 이름으로 바로 접근할 수 있게
  • 최고 점수를 PlayerPrefs 로 저장하기 때문에 게임을 껐다 켜도 최고 점수가 유지된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    public static GameManager instance; 

    public GameObject readyPannel;
    public Text scoreText;
    public Text bestScoreText;  
    public Text messageText; 

    public bool isRoundActive = false; 

    private int score = 0; 

    public ShooterRotator shooterRotator; 
    public CamFollow cam; 


    void Awake() 
    {
        instance = this; 
        UpdateUI();
    }

    void Start()
    {
        StartCoroutine("RoundRoutine"); 
    }

    public void AddScore(int newScore) 
    {
        score += newScore;
        UpdateBestScore();
        UpdateUI();
    }

    void UpdateBestScore() 
    {
        if(GetBestScore() < score)
        {
            PlayerPrefs.SetInt("BestScore", score); 
        } 
    }

    int GetBestScore()
    {
        int bestScore = PlayerPrefs.GetInt("BestScore"); 
        return bestScore;
    }

    void UpdateUI()
    {
        scoreText.text = "Score: " + score;
        bestScoreText.text = "Best Score: " + GetBestScore();
    }

    public void OnBallDestroy() 
    {
        UpdateUI();
        isRoundActive = false; 
    }

    public void Reset() 
    {
        score = 0;
        UpdateUI();

        StartCoroutine("RoundRoutine");
    }

    IEnumerator RoundRoutine() 
    {
        // READY
        readyPannel.SetActive(true); 
        cam.SetTarget(shooterRotator.transform, CamFollow.State.Idle); 
        shooterRotator.enabled = false; 
        isRoundActive = false; 
        messageText.text = "Ready...";
        yield return new WaitForSeconds(3f); 

        // PLAY
        isRoundActive = true; 
        readyPannel.SetActive(false); 
        shooterRotator.enabled = true; 
        cam.SetTarget(shooterRotator.transform, CamFollow.State.Ready); 
        while (isRoundActive == true)
        {
            yield return null; 
        }

        // END
        readyPannel.SetActive(true);
        shooterRotator.enabled = false;
        messageText.text = "Wait For Next Round...";
        yield return new WaitForSeconds(3f);
        Reset(); 
    }
}

🚖 변수

  • public static GameManager instance;
    • GameManager 오브젝트를 싱글톤으로 설정.
    • instance에 자기 자신인 GameManager 오브젝트를 넣을 것이다.
    • static 변수이므로 외부에서 GameManager의 public 변수, 함수들을 일일이 슬롯 연결 해줄 필요 없이 GameManager 이름 하나로 접근할 수 있다.
      • GameManager.instance.AddScore() 이렇게.
  • UI 👉 using UnityEngine.UI 필요
    • public GameObject readyPannel;
      • 라운드가 시작할때, 끝날때 텍스트 띄워줄 패널.
      • Round Ready Panel 연결
    • public Text scoreText;
      • 현재 점수 텍스트
      • Socre Text 연결
    • public Text bestScoreText;
      • 최고 점수 텍스트
      • Best Score Text 연결
    • public Text messageText;
      • Round Ready Panel 패널에 붙어 있는 텍스트
      • 레디 상태에선 “Ready…”, 라운드가 끝났을 땐 “Wait For Next Round…” 를 띄울 것.
      • Text 연결
  • public bool isRoundActive = false;
    • Play 페이지 상태일때만 True, Ready, End 페이지 상태일땐 False
    • 이 값이 false로 바뀌면 Play 페이지가 끝났단 의미고 Play 는 무한 루프문을 빠져 나온다.
  • private int score = 0;
    • 현재 점수
  • 필요한 다른 컴포넌트
    • 📜ShooterRotator.cs
      • public ShooterRotator shooterRotator;
      • 대기 상태일 때는 회신이 회전되지 않도록 📜ShooterRotator.cs를 끄기 위해.
    • 📜CamFollow.cs
      • SetTarget() 함수 호출해서 카메라가 추적할 타겟 오브젝트와 줌 사이즈를 설정해주어야 해서.
      • 페이지 상태에 따라 줌 사이즈가 다르며 타겟 오브젝트도 다음 ()

🚖 함수

  • 이벤트 함수
    • void Awake()
      • instance = this;
        • GameManager 오브젝트를 싱글톤 instance에 넣어준다.
        • GameManager는 다른 곳에서 많이 사용 되므로 그 어떤 Start() 함수들 보다 빨리 실행되야 해서 Awake()안에 넣음.
      • UI 업데이트 (현재점수, 최고점수)
    • void Start()
      • 코루틴 함수 RoundRoutine()를 실행시킨다.
        • 밑에 후술할 3개의 페이지 별로 한 라운드를 실행시키는 코루틴 함수.
  • 만든 함수
    • 외부에서 사용될 public 함수
      • public void AddScore(int newScore)
        • Prop 오브젝트들이 각각 파괴될 때 점수를 추가하기 위해 호출할 것
        • 하는일
          • 현재점수 누적 합
          • 최고 점수 업데이트
          • UI 업데이트 (현재점수, 최고점수)
      • public void OnBallDestroy()
        • Play페이지에서 End페이지로 넘어가는 순간은 Ball이 바닥에 떨어져 OnTriggerEnter 이벤트가 발생할 때다. -📜Ball.cs 에서 호출해주어야 함
        • 볼이 폭발하는 순간 📜GameManager.cs내에서 Play페이지가 무한 루프를 벗어나 End페이지로 갈 수 있도록 isRoundActive = false; 해준다.
        • UI 업데이트 (현재점수, 최고점수)
        • public void Reset()*
        • End페이지까지 끝나고 다음 라운드로 넘어갈 때 마다 갈무리 역할
          • 점수를 0 으로 초기화 하고
          • UI 업데이트 (현재점수, 최고점수)
          • 코루틴 함수 RoundRoutine()를 실행시킨다.
            • 라운드를 다시 처음부터 시작! 다음 라운드 시작!
            • END 페이지가 끝나고 Reset()함수가 호출 될것이니 다시 READY 페이지로 돌아가게 되는 셈
    • public이 아닌 게임 매니저에서만 사용될 함수
      • void UpdateBestScore()
        • 최고 점수 업데이트
        • 현재 score가 최고 점수보다 높으면 최고 점수를 갱신해준다.
        • PlayerPrefs 사용하여 "BestScore" Key 값으로 socre를 저장해준다.
      • int GetBestScore()
        • 최고 점수 불러와 bestScore에 저장 후 리턴
        • PlayerPrefs 사용하여 "BestScore" Key 값으로 Value값을 불러온다.
      • void UpdateUI()
        • 현재점수, 최고점수 텍스트UI 업데이트
    • 코루틴 함수 (게임 매니저의 메인 함수!!)
      • IEnumerator RoundRoutine()
        • 한 라운드당 3개의 페이지 부분으로 이루어져있다.
          • READY 페이지
            • 꺼져있던 Round Ready Panel 켜주기.
            • 카메라
              • 추적 타겟 👉 포신
              • 줌 상태 👉 CamFollow.State.Idle
            • 📜ShooterRotator.cs 꺼주기.
              • 포신 회전 못하게!
            • isRoundActive = false;
              • PLAY페이지의 무한 루프 아직 못 돌리게
            • 레디 페널 텍스트 “Ready…”
            • 3초 대기 후 PLAY 페이지로 넘어가기
          • PLAY 페이지
            • isRoundActive = true;
              • Ball이 바닥에 떨어져 OnTriggerEnter 이벤트를 발생시킬 때 까지 계속 PLAY 상태에 머물러야 하므로 무한 루프에 있도록 true로 변경
            • readyPannel.SetActive(false);
              • 레디 패널 꺼주기
            • shooterRotator.enabled = true;
              • 이제 📜ShooterRotator.cs 켜주기. 포신이 회전하고 볼 쏘도록
            • 카메라
              • 추적 타겟 👉 포신
              • 줌 상태 👉 CamFollow.State.Ready
            • 무한 루프.
              • Ball이 바닥에 떨어져 OnTriggerEnter 이벤트를 발생시킬 때 빠져나온다.
          • END 페이지
            • readyPannel.SetActive(false);
              • 레디 패널 켜주기
            • 📜ShooterRotator.cs 꺼주기.
              • 포신 회전 못하게!
            • 레디 페널 텍스트 “Wait For Next Round…”
            • 3초 대기 후 Reset()함수로 넘어가기.
              • Reset()함수에서 다시 코루틴 함수를 호출하므로 READY 페이지로 가는 것이나 마찬가지다.
              • 마치 무한루프…

image

슬롯에 연결 잊지 말고 다 해주기

📜 Prop.cs

Prop이 데미지를 입어 Takedamage 함수가 발동되면 📜GameManager.cs의 AddScore 함수를 호출하여 점수를 증가시켜야 한다.

  • GameManager.instance.AddScore(score);
    • 싱글톤인 GameManager 오브젝트를 바로 접근하여 점수를 증가시킨다.
public void TakeDamage(float damage)
{
    hp -= damage;

    if (hp <= 0)
    {
        ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation);

        AudioSource explosionAudio = instance.GetComponent<AudioSource>();
        explosionAudio.Play();

        GameManager.instance.AddScore(score);  // 추가

        Destroy(instance.gameObject, instance.duration);
        gameObject.SetActive(false);
    }
}

📜 Ball.cs

  • BallProp들과 충돌을 일으켜 OnTriggerEnter 이벤트 함수를 실행할 때 📜GameManager.cs의 OnBallDestroy()가 실행되야 한다. PLAY 페이지의 무한루프를 끝내고 다음 라운드로 넘어가야 하기 때문에!
    • GameManager.instance.OnBallDestroy();
  • private void OnDestroy()
    • 이벤트 함수다.
      • 오브젝트가 Destroy 될 때 자동으로 호출되는 이벤트 함수다.
      • 마치 소멸자 같은 것.
    • GameManager.instance.OnBallDestroy();
      • Ball이 사라지면 GameManager에게 PLAY상태의 무한 루프를 빠져나가고 다음 라운드로 가게끔 해주는 OnBallDestroy() 함수를 호출한다.
      • Ball이 사라지는 경우
        • 1️⃣ Prop과 성공적으로 충돌하여 OnTriggerEnter 이벤트를 발동하는 경우
          • 이 경우 OnTriggerEnter 안에서 Ball을 Destroy 처리한다.
        • 2️⃣ Ball이 아무런 충돌 없이 맵 밖에 떨어지는 경우
          • Start() 함수의 Destroy(gameObject, lifeTime) 에 의해 10초가 지나면 Destroy 된다.
        • 따라서 GameManager.instance.OnBallDestroy(); 을 OnTriggerEnter(Collider other) 안에 구현하면 2️⃣경우에는 OnBallDestroy();을 호출할 길이 없어 무한 루프에 빠져나오지 못해 라운드가 끝나지 않고 영원히 PLAY 상태에 머무르게 된다.
        • 오브젝트가 Destroy 될 때 자동으로 호출되는 이벤트 함수인 OnDestroy()안에 구현해주자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ball : MonoBehaviour
{
    public LayerMask whatIsProp;

    public ParticleSystem explosionParticle;
    public AudioSource explosionAudio;

    public float maxDamage = 100f;
    public float explosionForce = 1000f;
    public float lifeTime = 10f;
    public float explosionRadius = 20f;

    void Start()
    {
        Destroy(gameObject, lifeTime);
    }

    private void OnTriggerEnter(Collider other)
    {
        Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius, whatIsProp);

        for (int i = 0; i < colliders.Length; i++)
        {
            Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
            targetRigidbody.AddExplosionForce(explosionForce, transform.position, explosionRadius); 

            Prop targetProp = colliders[i].GetComponent<Prop>();

            float damage = CalculateDamage(colliders[i].transform.position);

            targetProp.TakeDamage(damage);
        }

        explosionParticle.transform.parent = null;

        explosionParticle.Play(); 
        explosionAudio.Play();

        // GameManager.instance.OnBallDestroy();

        Destroy(explosionParticle.gameObject, explosionParticle.duration);
        Destroy(gameObject);
    }

    private float CalculateDamage(Vector3 targetPosition)
    {
        Vector3 explosionToTarget = targetPosition - transform.position;

        float distance = explosionToTarget.magnitude;

        float edgeToCenterDistance = explosionRadius - distance;

        float percentage = edgeToCenterDistance / explosionRadius;

        float damage = maxDamage * percentage;

        damage = Mathf.Max(0, damage);

        return damage;
    }

    private void OnDestroy()  // 이벤트 함수 추가
    {
        GameManager.instance.OnBallDestroy(); // 추가
    }
}

📜BallShotter.cs

Ball을 날리면 카메라가 Ball을 추적하게 하고 싶다.

  • public CamFollow cam;
    • Fire() : 발사시
      • cam.SetTarget(ballInstance.transform, CamFollow.State.Tracking);
        • 카메라가 이제 Ball을 추적하며 줌 사이즈 상태는 State.Tracking.
    • 📜CamFollow.cs 가 붙어있는 ✨✨ Rig 오브젝트를 cam 슬롯에 드래그 앤 드롭 꼭 해주기 ✨✨
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BallShooter : MonoBehaviour
{
    public CamFollow cam; // 추가

    public Rigidbody ball;
    public Transform firePos;
    public Slider powerSlider;

    public AudioSource shootingAudio;
    public AudioClip fireClip;
    public AudioClip chargingClip;

    public float minForce = 15f;
    public float maxForce = 30f;
    public float chargingTime = 0.75f;

    private float currentForce;
    private float chargeSpeed;
    private bool fired;

    private void OnEnable()
    {
        currentForce = minForce;
        powerSlider.value = minForce;
        fired = false;
    }

    private void Start()
    {
        chargeSpeed = (maxForce - minForce) / chargingTime;
    }

    private void Update()
    {
        if (fired == true) return;

        if (currentForce >= maxForce && !fired)
        {
            currentForce = maxForce;
            Fire();
        }
        else if (Input.GetButtonDown("Fire1"))
        {
            currentForce = minForce;
            shootingAudio.clip = chargingClip;
            shootingAudio.Play();
        }
        else if (Input.GetButton("Fire1") && !fired)
        {
            currentForce += chargeSpeed * Time.deltaTime;
            powerSlider.value = currentForce;
        }
        else if (Input.GetButtonUp("Fire1") && !fired)
        {
            Fire();
        }
    }

    private void Fire()
    {
        fired = true;

        Rigidbody ballInstance = Instantiate(ball, firePos.position, firePos.rotation);

        ballInstance.velocity = currentForce * firePos.forward;

        shootingAudio.clip = fireClip;
        shootingAudio.Play();

        currentForce = minForce;

        cam.SetTarget(ballInstance.transform, CamFollow.State.Tracking); // 추가
    }
} 

UnityEvent

해당 이벤트가 발동하는 순간에 그 이벤트에 등록된 모든 함수들이 자동으로 발동됨.

📜GameManager.cs

  • 유니티이벤트를 사용하기 위해서 using UnityEngine.Events;을 해주어야 함.
  • public UnityEvent onReset 라는 이벤트 선언
    • 유니티에서 onReset 이벤트가 발동할 때 함께 발동된 함수들을 onReset 슬롯에 등록해준다.
    • image
      • 📜spawnGenerator.cs를 드래그 앤 드롭 해와서 spawnGenerator - Reset() 함수를 등록해주자.
      • 이제 onReset.invoke()를 해주는 곳에서 📜spawnGenerator.cs의 Reset()이 실행될 것이다.
  • onReset.invoke()*
    • 코루틴 함수의 READY페이지 시작 부분에서 실행해준다.
    • 📜spawnGenerator.cs의 Reset()를 실행시켜 프롭들을 다시 재배치한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; 
using UnityEngine.Events;  // 추가 ✨✨✨✨✨

public class GameManager : MonoBehaviour
{
    public UnityEvent onReset; // 추가 ✨✨✨✨✨

    public static GameManager instance; 

    public GameObject readyPannel;
    public Text scoreText;
    public Text bestScoreText;  
    public Text messageText; 

    public bool isRoundActive = false; 

    private int score = 0; 

    public ShooterRotator shooterRotator; 
    public CamFollow cam; 


    void Awake() 
    {
        instance = this; 
        UpdateUI();
    }

    void Start()
    {
        StartCoroutine("RoundRoutine"); 
    }

    public void AddScore(int newScore) 
    {
        score += newScore;
        UpdateBestScore();
        UpdateUI();
    }

    void UpdateBestScore() 
    {
        if(GetBestScore() < score)
        {
            PlayerPrefs.SetInt("BestScore", score); 
        } 
    }

    int GetBestScore()
    {
        int bestScore = PlayerPrefs.GetInt("BestScore"); 
        return bestScore;
    }

    void UpdateUI()
    {
        scoreText.text = "Score: " + score;
        bestScoreText.text = "Best Score: " + GetBestScore();
    }

    public void OnBallDestroy() 
    {
        UpdateUI();
        isRoundActive = false; 
    }

    public void Reset() 
    {
        score = 0;
        UpdateUI();

        StartCoroutine("RoundRoutine");
    }

    IEnumerator RoundRoutine() 
    {
        // READY
        onReset.Invoke(); // 추가 ✨✨✨✨✨
        readyPannel.SetActive(true); 
        cam.SetTarget(shooterRotator.transform, CamFollow.State.Idle); 
        shooterRotator.enabled = false; 
        isRoundActive = false; 
        messageText.text = "Ready...";
        yield return new WaitForSeconds(3f); 

        // PLAY
        isRoundActive = true; 
        readyPannel.SetActive(false); 
        shooterRotator.enabled = true; 
        cam.SetTarget(shooterRotator.transform, CamFollow.State.Ready); 
        while (isRoundActive == true)
        {
            yield return null; 
        }

        // END
        readyPannel.SetActive(true);
        shooterRotator.enabled = false;
        messageText.text = "Wait For Next Round...";
        yield return new WaitForSeconds(3f);
        Reset(); 
    }
}


🔔 오디오 믹싱

배경음악

  1. 📁Assets/📁music 에서 배경음악으로 쓸 파일을 고르자
  2. Hierarchy로 옮겨 오브젝트로서 실존하게 해주자.
    • 이름은 BGM
  3. Audio Source 컴포넌트의 looping과 play on awake 를 체크해준다.
    • 게임 시작하자마자 무한 반복으로 재생되니 이게 바로 배경음악.

효과음이 나올 때는 배경음악 크기 줄이기

  1. Window - Audio - AudioMixer
  2. Audio Mixer에서 Groups에 BGM(배경음악), SFX(효과음)를 추가해준다.
    • 그룹이란 여러가지 채널을 모아둔 것.
      • BGM, SFX이라는 채널을 추가하는 셈
        • 채널이란 오디오가 출력되는 통로.
    • 해당 통로에 효과를 적용해주면 그 통로 안에 있는 음악들에 전부 적용이 된다.
      • 해당 채널의 볼륨을 줄이면 채널로 등록된 모든 음악들의 볼륨이 다 줄어든다.
  3. BGM 오브젝트에게 BGM채널 등록 (배경음악)
    • BGM오브젝트의 Audio Source 컴포넌트의 OutputBGM 채널로 선택
  4. Ball, BigExplosion, SmallExplosion 3개의 프리팹 들에 SFX채널 등록 (효과음)
    • 각 프리팹들을 Hierarchy창으로 잠시 옮겨 오브젝트화 시켜준 후
    • Ball같은 경우는 이펙트 오브젝트가 자식으로 붙어있다. - Audio Source 컴포넌트의 OutputSFX 채널로 선택 - Apply 해준 후 다시 오브젝트화 해놓은건 삭제
  5. SPX 채널이 발동할 때는 BGM 채널의 볼륨이 줄어들도록 하자.
    • Audio Mixer - Groups - BGM - Add Effect - Duck volume
    • Duck volume은 다른 채널로부터 신호를 받으면 자신의 사운드를 줄인다.
      • Attack time을 0으로 하여 지연 시간 없이 신호를 바로 받기 위해.
      • Threshold를 적당히 낮춰 신호를 쉽게 받게
      • Ratio를 높여서 음량이 꺾어지는 정도를 높게, - Audio Mixer - Groups - SFX - Add Effect - send - BGM 선택
    • send : 다른 채널에게 신호를 보냄.
      • send level은 최대. 신호를 그대로 받음.
    • 이제 이펙트 효과 오브젝트들이 생성되 SFX에서 신호가 발동되면 BGM이 선택 됐으니 이 채널에 신호를 보내면 BGM이 Duck volume을 발동시켜 자신의 사운드를 줄일 것.


🔔 빌드

  1. 언제나 그렇듯 빌드 전에는 꼭 저장 Ctrl + S
  2. file - build settings - Add open scenes 한 후 Build and Run
  3. 프로젝트 경로가 아닌 다른 경로에 저장하자! (같은 경로에 저장하면 꼬일 수 있음)


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기


Unity Lesson 1 카테고리 내 다른 글 보러가기

댓글 남기기