본문 바로가기

유니티 게임 개발하기/2D 게임 개발일지

유니티 - 탄막 슈팅 게임을 만들자(2), 이벤트(Event)와 Delegate 의 사용

안녕하세요, 윈디입니다.


지난 시간에는 보스가 탄을 '쏘도록'만 만들었다면, 이제는 '탄 자체'가 언제 삭제될지에 대해서 생각을 해 봐야할 것 같습니다.


실제로 화면에서 사라져도, 총알은 삭제되지 않고 무한한 거리를 날아갑니다. 이것이 계속 

메모리를 차지한다면, 엄청난 손해가 되겠죠. 프레임도 확 떨어질 거구요.


따라서,보스가 죽으면 총알도 사라져야 하겠죠? 

어떻게 구현해야 할까요?


가장 단순하게 생각할 수 있는 것이, 게임 매니저등에서 태그로 총알들을 모두 검색하여,

배열 안에 저장해서 한번에 모두 지워주는 클리어 함수를 만드는 것입니다.


GameObject[] obj = GameObject.FindGameObjectsWithTag ("Bullet_E");

// Bullet_E 태그를 가진 오브젝트를 모두 찾아서 배열에 추가
foreach (GameObject ob in obj) { 
Destroy (ob); 


이걸 이용하면 보스가 죽을때 한번에 총알이 모두 지워지겠습니다....만!


이 방식은  현재 존재하는 모든 총알들을 동적인 변수를 만들어 직접 다 할당한 뒤 명령을 내리던 방식이어서, 배열을 생성하고 그 안에 순서대로 총알들을 배치한 뒤 하나씩 삭제명령을 내렸기 때문에 식이 복잡하고 효율성이 떨어집니다. 


그런데, 

이벤트 시스템을 사용하고 이벤트 핸들러를 연결하시게 되면,
동적인 변수를 만들고 할당하는 과정을 생략하고 바로 이벤트를 불러오는 형식으로 사용이 가능해집니다. 
예를 들면 버튼에 있는 OnClick 같은 함수들을 직접 만드는 것이죠.


예를 들면 이런식으로 동작하는 겁니다.

탄알이 날아가는 도중에, 어디선가 "이벤트를 호출한다~" 라는 소리가 들리면

그 소리를 듣고 탄은 자기 자신을 삭제하게 되는 거죠.

하나하나 불러모아 줄을 세운다음 하나씩 지울 필요 없이

게임세계 전체에 소리를 질러서 각자 알아서 삭제하게 되는 방법입니다.


이벤트는 다음과 같이 만드실 수 있습니다.


우선 이벤트를 설정해야 합니다.

이 경우엔 이벤트를 항상 불러줄 수 있는 위치에 있는 오브젝트,

그러니까 게임 매니저나 스테이지, 혹은

보스 컨트롤 안에다 집어넣어 주시는 것이 좋겠네요.

모듈화를 위해 저는 보스 컨트롤 안에다 넣었습니다.



public delegate void SpellClearHandler();

public static event SpellClearHandler OnBulletClear;



Delegate 와 Event 함수가 보입니다! 이벤트를 만들 때 꼭 필요한 것들이죠.

델리게이트는 이벤트의 '기능'을 만들때 필요한 것, 

이벤트는 이벤트를 실제로 게임세계에 '방송'할수 있도록 만들어주는 것으로 이해해 주시면 되겠습니다.


한번 Delegate에 대해서 알아보도록 하겠습니다.

델리게이트는 우선 사전적으로 대리자, 즉 대신 해준다는 뜻을 가지고 있습니다.


즉, 어떤 함수의 기능들을 대신해서 수행해준다는 것인데요,

예를 들면 메모지와 같이, 저 함수에 들어가는 내용들은 매우 유동적입니다.

함수이지만 다른 함수들을 담을 수 있는 일종의 상자라고 생각하시면 되겠네요.


실제 저렇게 만들어진, SpellClearHandler(); 는, 마치 타입처럼


public static event SpellClearHandler OnBulletClear; 를 생성하는데 쓰이고 있습니다.


이렇게 생성된 OnBulletClear 라는 '상자'는, 이제 탄들의 정보를 담게 될 겁니다.

탄알 스크립트를 직접 보시는 것이 이해가 좀 더 빠르실 것 같네요.


아래는 탄알의 스크립트입니다.

void OnEnable() 

SpellCtrl.OnBulletClear += BulletClear; 

//특이하지요? OnBulletClear 라는 대리자(델리게이트) 함수에  BulletClear

함수를 더해버리고 있습니다. 이렇게 대리자에 함수를 등록해 버리면, 

델리게이트 함수가 실행되었을때, 델리게이트는 마치 저 함수가 자신의 것이었던 것처럼 

그대로 실행을 합니다. 위에 말씀드렸듯 메모지 같은 느낌으로, 적혀있는 것들을

자연스럽게 모두 실행하는 것이죠.


OnEnable 함수 안에서 이렇게 등록을 했으니, 총알이 생기면 바로 OnBulletClear,(델리게이트) 함수 안에 저 삭제를 위한 내용이 등록이 됩니다.

필요한 일이 있으면 언제든 사용하면 되겠지요.


void OnDisable() 

SpellCtrl.OnBulletClear -= BulletClear; 

//당연히 빼는 것도 가능합니다.

void BulletClear() 

Destroy (this.gameObject); 



다른 언어를 접해보신 분들은, C와 C++의 함수 포인터와 유사하다고 생각하시면 되겠습니다.

이 스크립트까지 총알 안에 등록하고 나시면, 총알은 따로 배열에 저장할 필요 없이

OnBulletClear(); 라는 함수가 발동되면 자연스럽게 모두 삭제될 겁니다.


이제 보스가 죽었을 때는 총알이 모두 삭제가 될 거 같네요.

그럼 다음은 

카메라에서 탄이 보이지 않을 때 삭제해야 되겠군요.


이건 생각보다 간단합니다. 유니티 API에서 이런걸 이미 지원하고 있으니까요.

void OnBecameInvisible(){

Destroy (this.gameObject);

}


OnBecameInvisible() 은 카메라에서 이 스크립트의 소유자가 보이지 않을 때
호출되는 이벤트입니다. 
단, 유니티의 경우 게임화면뿐 아니라 씬화면에서도 안 보여야만 오브젝트가
삭제되고, 씬 화면, 게임화면에서 둘 다 안보여야만 기능이 동작한다는거 기억해 주세요.



▲씬 화면을 살짝 올린 뒤에 찍은 사진입니다.

씬화면을 보시면 아럐쪽의 총탄들이 잘린것처럼 보이실 겁니다.

씬, 게임화면 모두의 범위에서 나갔기 때문에 총탄이 삭제된 거에요.


이제 탄막이 시원하게 날아가네요. 필요없는 총알은 즉각 삭제되고,

업데이트 문으로 연산을 낭비하지도 않고요.


오늘은 여기까지입니다. 모두 즐거운 주말 보내세요!