[ 오디오 시스템 구축 ] --> 오디오 오브젝트와 로직 구현
- 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 |