지난번에 이어 게임을 만들어보자.
이번에 참고할 강의는 아래 강의이다.
https://www.youtube.com/watch?v=C65ExBy6WPA&list=PLFt_AvWsXl0ctd4dgE1F8g3uec4zKNRV0&index=23
이번 강의에서는 사운드를 추가해본다.
시작해보자.
위 강의 영상의 설명란에서 오디오 파일을 다운받고
에셋 폴더에 Audio 폴더를 만들어 넣어준다.
이제 씬에 빈 오브젝트 Audio Manager 를 생성하고 같은 이름의 스크립트를 만들어
적용한 뒤 오디오를 관리 할 코드를 작성하자.
| AudioManager 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour {
float masterVolumePercent = 1; // 마스터 볼륨
float sfxVolumePercent = 1; // 효과음 볼륨
float musicVolumePercent = 1; // 음악 볼륨
AudioSource[] musicSources; // 음악을 가져올 오디오소스 레퍼런스 배열
int activeMusicSourceIntdex; // 재생중인 음악 인덱스
public static AudioManager instance; // 싱글톤 패턴
void Awake() {
instance = this; // 인스턴스 설정
musicSources = new AudioSource[2]; // 크기 설정하여 할당
for(int i = 0; i < 2; i++) {
GameObject newMusicSource = new GameObject("Music Source " + (i + 1)); // 오디오소스를 가질 오브젝트 생성
musicSources[i] = newMusicSource.AddComponent<AudioSource>(); // 오디오소스 할당
newMusicSource.transform.parent = transform; // 부모 오브젝트 설정
}
}
// ■ 음악 재생 메소드
public void PlayMusic(AudioClip clip, int fadeDuration = 1) {
activeMusicSourceIntdex = 1 - activeMusicSourceIntdex; // 재생할 음악 인덱스 설정
musicSources[activeMusicSourceIntdex].clip = clip; // 재생할 음악 클립 설정
musicSources[activeMusicSourceIntdex].Play(); // 음악 재생
StartCoroutine(AnimateMusicCrossFade(fadeDuration)); // 음악 크로스페이드 코루틴 실행
}
// ■ 사운드 재생 메소드
public void PlaySound(AudioClip clip, Vector3 pos) {
AudioSource.PlayClipAtPoint(clip, pos, sfxVolumePercent * masterVolumePercent); // pos 위치에 효과음 * 마스터 볼륨 크기로 clip 재생
}
// ■ 음악 크로스페이드(부드럽게 변환) 코루틴
IEnumerator AnimateMusicCrossFade(int duration) {
float percent = 0; // 음악 퍼센트
while(percent < 1) {
percent += Time.deltaTime * 1 / duration; // 퍼센트 계산
musicSources[activeMusicSourceIntdex].volume = Mathf.Lerp(0, musicVolumePercent * masterVolumePercent, percent);
musicSources[1 - activeMusicSourceIntdex].volume = Mathf.Lerp(musicVolumePercent * masterVolumePercent, 0, percent);
// 음악 볼륨 설정, Lerp 를 통해 활성화하는 음악은 점점 커지게, 비활성와 음악은 점점 작아지게 설정.
yield return null; // 다음 프레임으로 스킵
}
}
}
그리고 MusicManager 스크립트를 생성해 AudioManager 오브젝트에 적용 후 수정하자.
| MusicManager 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicManager : MonoBehaviour
{
public AudioClip mainTheme; // 메인 음악
public AudioClip menuTheme; // 메뉴 음악
void Start() {
AudioManager.instance.PlayMusic(menuTheme, 2); // 음악 재생 메소드 호출
}
void Update() {
if (Input.GetKeyDown(KeyCode.Space)) { // 스페이스바 누를 때
AudioManager.instance.PlayMusic(mainTheme, 3); // 음악 재생 메소드 호출
}
}
}
Main Theme 와 Menu Theme 를 다운받은 음원으로 각각 할당해주고 실행해보자.
이제 무기 사운드를 추가해보자.
Gun 스크립트로 이동해 수정하자.
| Gun 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gun : MonoBehaviour
{
public enum FireMode {Auto, Burst, Single}; // 총 격발 모드 (자동, 점사, 단발)
public FireMode fireMode; // 격발 모드 변수
public Transform[] projectileSpawn; // 총알 발사 위치 배열
public Projectile projectile; // 총알
public float msBetweenShots = 100; // 총알 발사 시간 간격
public float muzzleVelocity = 35; // 총알 발사 속도
public int burstCount; // 점사 모드 총알 발사 개수
public int projectilesPerMag; // 탄창 총알 수
public float reloadTime = .3f; // 재장전 속도
[Header("Recoil")]
public Vector2 kickMinMax = new Vector2(.05f, .5f); // 뒤쪽 반동 최소, 최대값
public Vector2 recoilAngleMinMax = new Vector2(3, 5); // 위쪽 반동 최소, 최대값
public float recoilMoveSettleTime = .1f; // 뒤쪽 반동 회복 시간
public float recoilRotationSettleTime = .1f; // 뒤쪽 반동 회복 시간
[Header("Effect")]
public Transform shell; // 탄피 레퍼런스
public Transform shellEjection; // 탄피 배출 위치 레퍼런스
public AudioClip shootAudio; // 총알 발사 사운드
public AudioClip reloadAudio; // 재장전 사운드
MuzzleFlash muzzleFlash; // 총구 화염 레퍼런스
float nextShotTime; // 다음 총알 발사 시간
bool triggerReleasedSinceLastShoot; // 지난 발사 후 방아쇠(마우스 좌클릭)을 놓았는지
int shotsRemainingInBurst; // 점사 모드 남은 총알
int projectilesRemainingInMag; // 탄창에 남아있는 총알
bool isReloading; // 장전중인지 여부
Vector3 recoilSmoothDampVelocity; // 반동 회복 속도
float recoilRotSmoothDampVelocity; // 반동 회복 속도
float recoilAngle; // 위쪽 반동 각도
void Start() {
muzzleFlash = GetComponent<MuzzleFlash>(); // 총구 화염 레퍼런스 할당
shotsRemainingInBurst = burstCount; // 점사 모드 남은 총알 개수 설정
projectilesRemainingInMag = projectilesPerMag; // 기본 탄창 총알 개수 설정
}
void LateUpdate() {
transform.localPosition = Vector3.SmoothDamp(transform.localPosition, Vector3.zero, ref recoilSmoothDampVelocity, recoilMoveSettleTime);
// SmoothDamp 를 통해 부드러운 움직임으로 현재 위치에서 원지점인 zero 까지 지정한 속도로 지정한 시간동안 움직인다.
recoilAngle = Mathf.SmoothDamp(recoilAngle, 0, ref recoilRotSmoothDampVelocity, recoilRotationSettleTime);
// 위와 같이 각도 또한 부드럽게 원지점으로 복구하도록 함
transform.localEulerAngles = transform.localEulerAngles + Vector3.left * recoilAngle;
// 위쪽 반동 각도 적용
if(!isReloading && projectilesRemainingInMag == 0) { // 재장전중이 아니고 탄창에 총알이 없는 경우
Reload(); // 재장전 메소드 호출
}
}
void Shoot() // 총알 발사 메소드
{
if(!isReloading && Time.time > nextShotTime && projectilesRemainingInMag > 0) {
// 장전중이지 않고, 총알 발사 시간이 지났고, 탄창에 총알이 있는지 확인
// □ 무기 모드 설정에 따른 발사
if (fireMode == FireMode.Burst) { // 무기 발사 모드가 점사인 경우
if(shotsRemainingInBurst == 0) { // 발사 할 남은 총알이 없는 경우
return; // 아래 코드(총알 발사) 실행 건너뜀
}
shotsRemainingInBurst--; // 남은 총알 개수 감소
}
else if (fireMode == FireMode.Single) { // 무기 발사 모드가 단발인 경우
if (!triggerReleasedSinceLastShoot) { // 이미 총알을 발사 하고 방아쇠를 당기고(마우스 좌클릭 중) 있는 경우
return; // 아래 코드(총알 발사) 실행 건너뜀
}
}
// □ 총알 생성 및 발사
for (int i = 0; i < projectileSpawn.Length; i++) { // 총알 발사 개수만큼 반복
if(projectilesRemainingInMag == 0) { // 탄창에 총알이 없는 경우
break; // 루프 탈출(총알 생성 취소)
}
projectilesRemainingInMag--; // 탄창에서 총알 감소
nextShotTime = Time.time + msBetweenShots / 1000; // 다음 총알 발사 시작 시간 저장
Projectile newProjectile = Instantiate(projectile, projectileSpawn[i].position, projectileSpawn[i].rotation) as Projectile;
// 총알 프리팹을 통해 오브젝트를 인스턴스화 하여 생성.
newProjectile.SetSpeed(muzzleVelocity); // 총알 발사 속도 설정
}
// □ 무기 이펙트(탄피, 총구 화염)
Instantiate(shell, shellEjection.position, shellEjection.rotation); // 탄피 인스턴스화 하여 생성
muzzleFlash.Activate(); // 총구 화염 생성
// □ 무기 반동
transform.localPosition -= Vector3.forward * Random.Range(kickMinMax.x, kickMinMax.y); // 뒤쪽 반동값 설정
recoilAngle += Random.Range(recoilAngleMinMax.x, recoilAngleMinMax.y); // 위쪽 반동값 설정
recoilAngle = Mathf.Clamp(recoilAngle, 0, 30); // 무기 반동 각도 최소, 최대 범위 지정
AudioManager.instance.PlaySound(shootAudio, transform.position); // 총알 발사 사운드 재생
}
}
// ■ 재장전 메소드
public void Reload() {
if(!isReloading && projectilesRemainingInMag != projectilesPerMag) { // 재장전중이 아니고 탄창에 총알이 꽉 차있지 않은 경우
StartCoroutine(AnimateReload()); // 재장전 애니메이션 코루틴 실행
AudioManager.instance.PlaySound(reloadAudio, transform.position); // 재장전 사운드 재생
}
}
// ■ 재장전 애니메이션 코루틴
IEnumerator AnimateReload() {
isReloading = true; // 재장전중으로 변경
yield return new WaitForSeconds(.2f); // 다음 코드 실행까지 0.2 초 대기
float reloadSpeed = 1 / reloadTime; // 재장전 속도 설정
float percent = 0; // 애니메이션 실행 비율
Vector3 initialRot = transform.localEulerAngles; // 기존 각도
float maxReloadAngle = 30; // 최대 장전 각도 설정
while (percent < 1) {
percent += Time.deltaTime * reloadSpeed; // 애니메이션 실행 비율 설정
float interpolation = (-Mathf.Pow(percent, 2) + percent) * 4; // 재장전 각도를 위한 보간값 설정
float reloadAngle = Mathf.Lerp(0, maxReloadAngle, interpolation); // 재장전 각도 설정
transform.localEulerAngles = initialRot + Vector3.left * reloadAngle; // 각도 적용
yield return null; // 다음 프레임 스킵
}
isReloading = false; // 재장전중이 아님으로 변경
projectilesRemainingInMag = projectilesPerMag; // 탄창 총알 수 설정
}
// ■ 에임 보정 메소드
public void Aim(Vector3 aimPoint) {
if(!isReloading) { // 재장전중이 아닐 때
transform.LookAt(aimPoint); // 전달받은 위치를 보도록 방향 설정
}
}
// ■ 방아쇠(마우스 좌클릭)를 누르고 있을 때
public void OnTriggerHold() {
Shoot(); // 총알 발사
triggerReleasedSinceLastShoot = false; // 방아쇠 잡은 상태로
}
// ■ 방아쇠(마우스 좌클릭)를 놓았을 때
public void OnTriggerRelease() {
triggerReleasedSinceLastShoot = true; // 방아쇠 놓은 상태로
shotsRemainingInBurst = burstCount; // 점사 모드 남은 총알 개수 설정
}
}
이제 각 무기 프리팹으로 이동해 각각 사운드를 적용해주자.
게임상에서 재생되는 오디오 소스를 듣고 그 사운드를 스피커로 출력해주는 오디오 리스너(Audio Listener)가 있다.
이 오디오 리스너는 기본적으로 메인 카메라에 붙어있는데 이번에는 플레이어에게 붙어있도록 하고 싶다.
하지만 플레이어는 죽으면 오브젝트가 파괴되므로 문제가 생긴다.
이를 해결하기 위해 메인 카메라 오브젝트의 기존 Audio Listener 컴포넌트를 삭제하고,
Audio Maneger 오브젝트 하위에 Audio Listener 오브젝트를 만들고 오디오 리스너 컴포넌트를 추가한다.
그리고 이제 이 오브젝트가 플레이어를 따라다니도록 AudioManager 스크립트를 수정하자.
| AudioManager 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour {
float masterVolumePercent = 1; // 마스터 볼륨
float sfxVolumePercent = 1; // 효과음 볼륨
float musicVolumePercent = 1; // 음악 볼륨
AudioSource[] musicSources; // 음악을 가져올 오디오소스 레퍼런스 배열
int activeMusicSourceIntdex; // 재생중인 음악 인덱스
public static AudioManager instance; // 싱글톤 패턴
Transform audioListener; // 오디오 리스너 위치 레퍼런스
Transform playerT; // 플레이어 위치 레퍼런스
void Awake() {
instance = this; // 인스턴스 설정
musicSources = new AudioSource[2]; // 크기 설정하여 할당
for(int i = 0; i < 2; i++) {
GameObject newMusicSource = new GameObject("Music Source " + (i + 1)); // 오디오소스를 가질 오브젝트 생성
musicSources[i] = newMusicSource.AddComponent<AudioSource>(); // 오디오소스 할당
newMusicSource.transform.parent = transform; // 부모 오브젝트 설정
}
audioListener = FindObjectOfType<AudioListener>().transform; // 오디오 리스너가 있는 오브젝트의 위치를 저장
playerT = FindObjectOfType<Player>().transform; // 플레이어 오브젝트의 위치 저장
}
void Update() {
if(playerT != null) { // 플레이어가 있는 경우
audioListener.position = playerT.position; // 오디오 리스너가 있는 오브젝트 위치를 플레이어 위치로 설정
}
}
// ■ 음악 재생 메소드
public void PlayMusic(AudioClip clip, int fadeDuration = 1) {
activeMusicSourceIntdex = 1 - activeMusicSourceIntdex; // 재생할 음악 인덱스 설정
musicSources[activeMusicSourceIntdex].clip = clip; // 재생할 음악 클립 설정
musicSources[activeMusicSourceIntdex].Play(); // 음악 재생
StartCoroutine(AnimateMusicCrossFade(fadeDuration)); // 음악 크로스페이드 코루틴 실행
}
// ■ 사운드 재생 메소드
public void PlaySound(AudioClip clip, Vector3 pos) {
if(clip != null) { // 오디오 클립이 있는 경우
AudioSource.PlayClipAtPoint(clip, pos, sfxVolumePercent * masterVolumePercent);
// pos 위치에 효과음 * 마스터 볼륨 크기로 clip 재생
}
}
// ■ 음악 크로스페이드(부드럽게 변환) 코루틴
IEnumerator AnimateMusicCrossFade(int duration) {
float percent = 0; // 음악 퍼센트
while(percent < 1) {
percent += Time.deltaTime * 1 / duration; // 퍼센트 계산
musicSources[activeMusicSourceIntdex].volume = Mathf.Lerp(0, musicVolumePercent * masterVolumePercent, percent);
musicSources[1 - activeMusicSourceIntdex].volume = Mathf.Lerp(musicVolumePercent * masterVolumePercent, 0, percent);
// 음악 볼륨 설정, Lerp 를 통해 활성화하는 음악은 점점 커지게, 비활성와 음악은 점점 작아지게 설정.
yield return null; // 다음 프레임으로 스킵
}
}
}
이제 실행해서 무기를 사용해보자
이렇게 무기를 사용할 때 발사 사운드가 재생된다.
다음 강의에서도 사운드를 추가해본다.
'Unity > 게임개발' 카테고리의 다른 글
[Unity] 게임 개발 일지 | 탑다운 슈팅 24 (0) | 2023.12.19 |
---|---|
[Unity] 게임 개발 일지 | 탑다운 슈팅 23 (0) | 2023.12.15 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 21 (0) | 2023.12.13 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 20 (1) | 2023.12.12 |
[Unity] 게임 개발 일지 | 탑다운 슈팅 19 (1) | 2023.12.11 |