Unity Chapter 6-6. 어메이징 볼링 게임 만들기 : 게임 매니저, 오디오, 최종 빌드
카테고리: Unity Lesson 1
태그: Unity Game Engine
인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!
Chapter 6. 어메이징 볼링 게임 만들기
🔔 게임 매니저
UI 만들기
Canvas
(Canvas) : 말그대로 UI를 그리는 도화지가 됨Info bar
(Panel) : 점수를 띄워 줌Socre Text
(Text) : 현재 점수Best Score Text
(Text) : 현재까지의 최고 점수
Round Ready Panel
(Panel) : 라운드 시작했을때, 라운드 끝났을 때 띄워줄 UIText
(Text) : 레디 상태에선 “Ready…”, 라운드가 끝났을 땐 “Wait For Next Round…” 를 띄울 것.
- UI -
Canvas
오브젝트 추가 - EventSystem은 지워주자. 유저가 손 못대게 할거라
- Canvas를 더블 클릭해서 씬 카메라를 맞춰준 후 씬창의 시점을 2D로 바꿔주자.
- UI는 2D 시점으로 해놓는게 편집하기 편하다.
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 크기가 바뀌더라도 세로 방향은 왜곡이 되지 않도록.
- UI - Panel 오브젝트 추가
- 이름은
Info bar
- 이름은
Info bar
을Canvas
의 자식으로 넣어준다.- 트랜스폼 툴바에서 Rect 툴을 선택한 후
Info bar
의 크기를 줄여준다. 아래와 같은 모양이 되게끔- UI는 Rect 툴로 크기와 모양을 조정하는게 편하다.
- UI - Text 오브젝트를 2개 추가해준 후 둘 다
Info bar
의 자식으로 넣어 준다.- 이름은
Score Text
- 이름은
Best Score Text
: 현재까지의 최고 점수
- 이름은
Info bar
에 Horizontal Layout Group 이라는 컴포넌트를 추가해준다.- 이 컴포넌트가 붙은 오브젝트의 자식 오브젝트들을 수평 정렬 시켜준다.
- 자식인
Socre Text
와Best Score Text
를 수평 정렬 해준다.
- Horizontal Layout Group 이라는 컴포넌트의 Control Child Size 값을 width, height 체크 해준다.
- 여백 없이 두 자식 오브젝트에 꽉 채워
Info bar
을 차지 하게끔.
- 여백 없이 두 자식 오브젝트에 꽉 채워
Socre Text
의 내용을 “Score : 0”으로,Best Score Text
의 내용을 “Best Score : 0”으로 해준다.- 두 텍스트 오브젝트의 정렬을 가운데, 중앙 정렬 해준다.
- 두 텍스트 오브젝트 모두 Best Fit 에 체크해준다.
- 텍스트 오브젝트 크기에 맞춰 폰트 사이즈가 맞춰진다.
- UI - Panel 오브젝트 추가해 준 후
Canvas
의 자식으로 넣어준다.- 이름은
Round Ready Panel
- 이름은
- UI - Text 오브젝트 추가해 준 후
Round Ready Panel
의 자식으로 넣어준다.- 이름은
Text
- 내용은 “Ready…” 로 해준다. (초기값)
- 가운데 중앙 정렬, Best fit 체크
- 텍스트
Text
의 Anchor presets을 Alt키를 누른 후 stretch-stretch 하여 부모인Round Ready Panel
에 맞춰서 전부 차지하도록 해준다.
- 이름은
Round Ready Panel
는 꺼둔다.- 게임 매니저에서 매번 다음 라운드에 도달할 때마다 켜줄 것.
한 라운드의 구성
하나의 라운드는 3가지 페이지로 구성되어 있다. 아래 3가지 페이지가 매 라운드마다 반복 된다.
- Reday
- 하는 일 :
- 카메라 리셋
- 포신 리셋
- 점수 리셋
- Ready UI를 띄운다. - 코루틴 함수를 사용해 대기 시간을 둠 - 대기 시간이 지나고 나면 Play 페이지로 넘어감
- Play
- play 페이지는 무한 루프로 돌림
- 볼이 바닥에 닿아 터지면 무한 루프를 빠져 나와 Play 페이지를 끝냄
- End
- 하는 일
- 조작이 불가능 하도록 포신 등등을 꺼둔다.
- 전체 컴포넌트 리셋
- 엔딩용 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 GameObject readyPannel;
- 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() 함수 호출해서 카메라가 추적할 타겟 오브젝트와 줌 사이즈를 설정해주어야 해서.
- 페이지 상태에 따라 줌 사이즈가 다르며 타겟 오브젝트도 다음 ()
- 📜ShooterRotator.cs
🚖 함수
- 이벤트 함수
- void Awake()
- instance = this;
GameManager
오브젝트를 싱글톤 instance에 넣어준다.GameManager
는 다른 곳에서 많이 사용 되므로 그 어떤 Start() 함수들 보다 빨리 실행되야 해서 Awake()안에 넣음.
- UI 업데이트 (현재점수, 최고점수)
- instance = this;
- void Start()
- 코루틴 함수 RoundRoutine()를 실행시킨다.
- 밑에 후술할 3개의 페이지 별로 한 라운드를 실행시키는 코루틴 함수.
- 코루틴 함수 RoundRoutine()를 실행시킨다.
- void Awake()
- 만든 함수
- 외부에서 사용될 public 함수
- public void AddScore(int newScore)
Prop
오브젝트들이 각각 파괴될 때 점수를 추가하기 위해 호출할 것- 하는일
- 현재점수 누적 합
- 최고 점수 업데이트
- UI 업데이트 (현재점수, 최고점수)
- public void OnBallDestroy()
- Play페이지에서 End페이지로 넘어가는 순간은
Ball
이 바닥에 떨어져 OnTriggerEnter 이벤트가 발생할 때다. -📜Ball.cs 에서 호출해주어야 함 - 볼이 폭발하는 순간 📜GameManager.cs내에서 Play페이지가 무한 루프를 벗어나 End페이지로 갈 수 있도록 isRoundActive = false; 해준다.
- UI 업데이트 (현재점수, 최고점수)
- Play페이지에서 End페이지로 넘어가는 순간은
-
- public void Reset()*
- End페이지까지 끝나고 다음 라운드로 넘어갈 때 마다 갈무리 역할
- 점수를 0 으로 초기화 하고
- UI 업데이트 (현재점수, 최고점수)
- 코루틴 함수 RoundRoutine()를 실행시킨다.
- 라운드를 다시 처음부터 시작! 다음 라운드 시작!
- END 페이지가 끝나고 Reset()함수가 호출 될것이니 다시 READY 페이지로 돌아가게 되는 셈
- public void AddScore(int newScore)
- public이 아닌 게임 매니저에서만 사용될 함수
- void UpdateBestScore()
- 최고 점수 업데이트
- 현재 score가 최고 점수보다 높으면 최고 점수를 갱신해준다.
- PlayerPrefs 사용하여 "BestScore" Key 값으로 socre를 저장해준다.
- int GetBestScore()
- 최고 점수 불러와 bestScore에 저장 후 리턴
- PlayerPrefs 사용하여 "BestScore" Key 값으로 Value값을 불러온다.
- void UpdateUI()
- 현재점수, 최고점수 텍스트UI 업데이트
- void UpdateBestScore()
- 코루틴 함수 (게임 매니저의 메인 함수!!)
- 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 이벤트를 발생시킬 때 빠져나온다.
- isRoundActive = true;
- END 페이지
- readyPannel.SetActive(false);
- 레디 패널 켜주기
- 📜ShooterRotator.cs 꺼주기.
- 포신 회전 못하게!
- 레디 페널 텍스트 “Wait For Next Round…”
- 3초 대기 후 Reset()함수로 넘어가기.
- Reset()함수에서 다시 코루틴 함수를 호출하므로 READY 페이지로 가는 것이나 마찬가지다.
- 마치 무한루프…
- readyPannel.SetActive(false);
- READY 페이지
- 한 라운드당 3개의 페이지 부분으로 이루어져있다.
- IEnumerator RoundRoutine()
- 외부에서 사용될 public 함수
슬롯에 연결 잊지 말고 다 해주기
📜 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
Ball
이Prop
들과 충돌을 일으켜 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 처리한다.
- 이 경우 OnTriggerEnter 안에서
- 2️⃣
Ball
이 아무런 충돌 없이 맵 밖에 떨어지는 경우- Start() 함수의 Destroy(gameObject, lifeTime) 에 의해 10초가 지나면 Destroy 된다.
- 따라서 GameManager.instance.OnBallDestroy(); 을 OnTriggerEnter(Collider other) 안에 구현하면 2️⃣경우에는 OnBallDestroy();을 호출할 길이 없어 무한 루프에 빠져나오지 못해 라운드가 끝나지 않고 영원히 PLAY 상태에 머무르게 된다.
- 오브젝트가 Destroy 될 때 자동으로 호출되는 이벤트 함수인 OnDestroy()안에 구현해주자.
- 1️⃣
- 이벤트 함수다.
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.
- 카메라가 이제
- cam.SetTarget(ballInstance.transform, CamFollow.State.Tracking);
- 📜CamFollow.cs 가 붙어있는 ✨✨
Rig
오브젝트를 cam 슬롯에 드래그 앤 드롭 꼭 해주기 ✨✨
- Fire() : 발사시
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 슬롯에 등록해준다.
-
- 📜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();
}
}
🔔 오디오 믹싱
배경음악
- 📁Assets/📁music 에서 배경음악으로 쓸 파일을 고르자
- Hierarchy로 옮겨 오브젝트로서 실존하게 해주자.
- 이름은
BGM
- 이름은
- Audio Source 컴포넌트의 looping과 play on awake 를 체크해준다.
- 게임 시작하자마자 무한 반복으로 재생되니 이게 바로 배경음악.
효과음이 나올 때는 배경음악 크기 줄이기
- Window - Audio - AudioMixer
- Audio Mixer에서 Groups에
BGM
(배경음악),SFX
(효과음)를 추가해준다.- 그룹이란 여러가지 채널을 모아둔 것.
-
BGM
,SFX
이라는 채널을 추가하는 셈- 채널이란 오디오가 출력되는 통로.
- 해당 통로에 효과를 적용해주면 그 통로 안에 있는 음악들에 전부 적용이 된다.
- 해당 채널의 볼륨을 줄이면 채널로 등록된 모든 음악들의 볼륨이 다 줄어든다.
BGM
오브젝트에게BGM
채널 등록 (배경음악)BGM
오브젝트의 Audio Source 컴포넌트의 Output을BGM
채널로 선택
Ball
,BigExplosion
,SmallExplosion
3개의 프리팹 들에SFX
채널 등록 (효과음)- 각 프리팹들을 Hierarchy창으로 잠시 옮겨 오브젝트화 시켜준 후
Ball
같은 경우는 이펙트 오브젝트가 자식으로 붙어있다. - Audio Source 컴포넌트의 Output을SFX
채널로 선택 - Apply 해준 후 다시 오브젝트화 해놓은건 삭제
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을 발동시켜 자신의 사운드를 줄일 것.
- Audio Mixer - Groups -
🔔 빌드
- 언제나 그렇듯 빌드 전에는 꼭 저장
Ctrl + S
- file - build settings - Add open scenes 한 후 Build and Run
- 프로젝트 경로가 아닌 다른 경로에 저장하자! (같은 경로에 저장하면 꼬일 수 있음)
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기