Barrel
이번 장에서는 장애물 역할로써 총알이 3발 맞으면 터지는 드럼통과 오디오 등을 구현해 보았다. 리소스는 에셋 스토어의 "Barrel" 을 사용하였다.
Barrel 에도 총알이 맞으면 스파크 효과가 나야하고 총알이 사라져야 하기 때문에 Wall에 적용하였던 스크립트를 그대로 적용시켜 준다.
또한 총알에 3회 피격되면 폭발하는 동작을 하기 위해 스크립트를 하나 추가해 주었다. 이 스크립트에서는 드럼통을 폭발시킬 함수인 ExpBarrel() 함수를 새로 만들어 폭발효과 파티클을 생성해주고 드럼통에 Y 방향으로 힘을 강하게 주어 위로 솟구치게 하고 드럼통을 제거해주었다.
// 드럼통 폭파 함수
void ExpBarrel()
{
// 폭발 효과 파티클 생성
GameObject exp = Instantiate(expEffect, tr.position, Quaternion.identity);
// 폭발 효과 5초 후에 제거
Destroy(exp, 5.0f);
// Rigidbody 컴포넌트의 mass를 1.0으로 수정해 무게를 가볍게 함
rb.mass = 1.0f;
// 위로 솟구치는 힘을 가함
rb.AddForce(Vector3.up * 1500.0f);
// 3초 후에 드럼통 제거
Destroy(gameObject, 3.0f);
}
ExpBarrel() 함수는 OnnCollisionEnter(충돌 시 발생) 함수 안에 hitcount 가 3이 되면 발생하도록 하였다. 게임을 실행해 보면 총알이 3발 맞으면 폭발 효과와 함께 Barrel 이 위로 솟구치고 곧 사라지는 것을 확인할 수 있다.
이제 Barrel 모델을 복사한 후 맵 곳곳에 15개 정도 깔아주었다. 이렇게 되면 하이러키 뷰에 표시되는 게임오브젝트의 개수가 많아져 작업할 때 번거롭기 때문에 간략히 유지하는 것이 좋다. 이럴 때는 빈 오브젝트를 하나 생성하고 그 오브젝트의 하위에 드래그 해주면 간략하게 유지할 수 있다. 빈 오브젝트의 이름은 _STAGES로 해주었다.
폭발력 적용하기
드럼통이 폭발하면 주변의 물체까지 폭발력을 전달해 간접적으로 데미지를 주는 로직을 구현해보겠다. 드럼통을 터뜨리면 주변의 드럼통도 영향을 받게끔 하는 것이다. Physics.OverlapSphere 함수를 이용하여 배럴 스크립트를 수정한다. IndirectDamage 함수를 작성하고 드럼통 폭발 함수 안에서 호출시킨다.
void IndirectDamage(Vector3 pos)
{
// 주변에 있는 드럼통을 모두 추출
Collider[] colls = Physics.OverlapSphere(pos, radius, 1 << 3);
foreach(var coll in colls)
{
// 폭발 범위에 포함된 드럼통의 Rigidbody 컴포넌트 추출
rb = coll.GetComponent<Rigidbody>();
// 드럼통의 무게를 가볍게 함
rb.mass = 1.0f;
// freezeRotation 제한값을 해제
rb.constraints = RigidbodyConstraints.None;
// 폭발력을 전달
rb.AddExplosionForce(1500.0f, pos, radius, 1200.0f);
}
}
Physics.OverlapSphere 함수의 인자는 다음과 같다: Physics.OverlapSphere(원점, 반지름, 검출 대상 레이어)
원점은 폭발 원점으로 현재 드럼통의 위치를 넘겨주고, 반지름은 radius 변수값인 10.0f로 적용된다. 즉, 드럼통을 기준으로 반경 10m에 있는 Collider 컴포넌트를 추출한다. 3번째 인자인 레이어는 비트 연산 표기법을 사용해 3번째 레이어를 의미하는 1<<3을 사용한다. 비트 연산 표기법에 의해 2의 3제곱(=8) 을 의미한다.
Physics.OverlapSphere 함수의 반환 값은 조건에 맞고 해당 범위에 들어온 모든 Collider 컴포넌트가 배열에 담겨 넘어온다. 따라서 foreach 구문을 이용해 배열의 처음부터 마지막까지 순회하면서 폭발력을 적용한다.
오디오
이번에는 총소리 오디오를 구현할 것이다. 오디오를 구현하려면 소리를 듣는 역할을 하는 AudioListener 컴포넌트와 소리를 내는 역할을 하는 AudioSource 컴포넌트가 있어야 한다. AudioListener는 씬에서 반드시 한 개만 존재해야 하고, 그와 반대로 AudioSource는 여러 개가 있을 수 있다. 참고로 AudioListener는 Main Camera에 기본 컴포넌트로 추가돼 있다.
Bullet에 AudioSource 컴포넌트를 추가한 후 오디오 파일을 넣어주고 Play On Awake 속성을 체크하면 총알이 생성될 때마다 소리가 발생하므로 잘 작동하는 것 같지만 총알이 벽이나 다른 오브젝트에 부딪혀서 사라지게 되면 소리 역시 사라져서 끊기는 현상이 발생한다. 예를 들어 벽 가까이에서 벽을 향해 총을 발사하면 총소리가 바로바로 끊기게 된다.
그러므로 플레이어 스크립트의 총 발사 부분에 총소리를 내는 코드를 구현한다.
// 총소리에 사용할 오디오 음원
public AudioClip fireSfx;
// AudioSource 컴포넌트를 저장할 변수
private new AudioSource audio;
void Fire()
{
// Bullet 프리팹을 동적으로 생성(생성할 객체, 위치, 회전)
Instantiate(bullet, firePos.position, firePos.rotation);
// 총소리 발생
audio.PlayOneShot(fireSfx, 1.0f);
}
상단 변수 선언 부분에 오디오 음원 프리팹을 저장할 변수를 만들어주고, AudioSource 컴포넌트의 PlayOneShot 메서드를 사용한다.
총구 화염 효과
이번에는 총을 쏠 때, 총구 화염 효과가 나오도록 할 것이다. 캐릭터의 총구 부분에 Quad 오브젝트를 추가하고 "MuzzleFlash" 로 이름을 변경한다. Quad 안에는 Mesh Collider 컴포넌트가 기본으로 추가돼 있는데 이걸 제거하지 않으면 총알이 충돌해 이상하게 휘어 나가기 때문에 삭제해 주어야 한다.
총구 화염을 구현하는 데는 선응을 고려해 단순한 메시를 사용하였다. 이 텍스쳐에는 네 개의 총구 화염이 하나의 이미지 파일에 들어 있다. 실제로 발사를 할 때는 1/4 영역만 사용해야 하기 때문에 텍스처의 Offset 값을 변경해 네 개의 이미지를 불규칙하게 노출해 총구 화염 효과를 표현한다.
코루틴 함수
유니티는 게임을 실행하면 모든 스크립트에 일종의 메시지 루프가 동작한다. 일반적인 함수를 호출하면 해당 함수 안의 로직을 다 수행해야만 실행이 끝난다. 만약 함수 안에서 수행하는 로직이 10초의 시간이 걸린다고 가정하면 10초 동안 메시지 루프의 다른 로직을 실행할 수 없다는 것을 의미한다. 즉, 게임이 10초 동안 멈추는 현상이 발생하게 되는데 이를 블로킹(Blocking)이라고 한다.
유니티에서는 멀티 스레드와 같이 비동기로 처리해야 하는 로직을 구현하기 위해 멀티스레드와 유사한 코루틴(Coroutine)을 제공한다. 메시지 루프와 코루틴이 서로 번갈아 가면서 로직을 수행할 수 있게 되는 것이다.
Yield 키워드
코루틴 함수 안에 있는 yield 키워드를 만나면 제어 권한을 유니티의 메인 메시지 루프로 양보하는 방식으로 점진적인 작업을 처리할 수 있다. yield return null은 다름 프레임까지 해당 코루틴을 잠시 대기하는 동안 메인 메시지 루프로 제어권을 넘겨 다른 작업을 처리하라는 의미다. 즉, 프로세스가 블로킹되지 않고 마치 멀티 스레드로 처리하는 것과 비슷한 효과를 낸다.
IEnumerator Fade()
{
for(float f = 1f; f >= 0; f -= 0.1f)
{
Color c = GetComponent<Renderer>().material.color;
c.a = f;
GetComponent<Renderer>().material.color = c;
yield return null;
}
}
코루틴 함수는 열거자 IEnumerator 타입으로 선언해야 하고 함수 내에 하나 이상의 yield 키워드를 사용해야 한다. 코루틴 함수는 일반 함수를 호출하듯이 함수명으로 호출할 수 없고 StartCoroutine() 함수를 이용해 호출해야 한다.
Void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(Fire());
}
}
IEnumerator Fire()
{
[로직]
yield return null;
}
코루틴 함수를 호출할 때 전달하는 인자는 함수의 원형을 사용한다( Fire() ). 다음 프레임까지 정지하는 yield return null 구문 대신에 WaitForSeonds를 사용해 일정 시간을 정지시킬 수 있다. yield return new WaitForSeconds(0.3f); 로 선언을 했다면 0.3초를 지연시킬 수 있는 것이다. 이것을 이용하여 총구 화염 효과가 깜박거리는 블링크(Blink) 효과를 구현할 수 있다.
IEnumerator ShowMuzzleFlash()
{
// 오프셋 좌푯값을 랜덤 함수로 생성
Vector2 offset = new Vector2(Random.Range(0, 2), Random.Range(0, 2)) * 0.5f;
// 텍스처의 오프셋 값 설정
muzzleFlash.material.mainTextureOffset = offset;
// MuzzleFlash의 회전 변경
float angle = Random.Range(0, 360);
muzzleFlash.transform.localRotation = Quaternion.Euler(0, 0, angle);
// MuzzleFlash의 크기 조절
float scale = Random.Range(1.0f, 2.0f);
muzzleFlash.transform.localScale = Vector3.one * scale;
// MuzzleFlash 활성화
muzzleFlash.enabled = true;
// 0.2초 동안 대기(정지)하는 동안 메시지 루프로 제어권을 양보
yield return new WaitForSeconds(0.2f);
// MuzzleFlash 비활성화
muzzleFlash.enabled = false;
}
이렇게 물리 엔진, 충돌 콜백, 코루틴 함수, 오디오 등의 내용을 학습하였다. 학습을 하는 과정에서 직접 눈으로 과정을 확인하면서 마음대로 수정이 가능하니 재미있게 학습하였다. 다음 장에서는 적 캐릭터를 구현해 보도록 하겠다.
'개발 > Unity' 카테고리의 다른 글
[Unity] 자습서 7장 - 적 공격, 사망 이벤트 (0) | 2023.03.02 |
---|---|
[Unity] 자습서 6장 - 적 캐릭터 구현 (0) | 2023.02.28 |
[Unity] 자습서 4장 - 총 발사 로직(Mac) (0) | 2023.02.22 |
[Unity] 자습서 3장 - 캐릭터 제작(Mac) (0) | 2023.02.21 |
[Unity] 자습서 2장 - 게임 스테이지 제작(Mac) (0) | 2023.02.21 |