Unity 3D OnCollisionEnter
유니티 3D로 게임을 만들 때 포탄과 물체가 충돌할 때 충돌처리의 예
유니티에서 포탄과 같은 오브젝트는 일반적으로 Prefab 으로 설정해놓고 게임 내에서 필요시마다 동적으로 생성하여 장면에 출력하는 방법을 사용하고 있다.
Prefab 은 개발시에 Scene 뷰에나 Hierarchy 뷰에 등록이 되지 않고 Project 뷰에 등록이 되지만 게임이 실행되면 장면에 출력이 된다는 점에서 게임 오브젝트와 같기 때문에 Project 뷰에 등록되어 있는 Prefab 오브젝트도 스트립트를 포함할 수 있는 것은 당연한 것이다.
여기서의 요지는 포탄을 Prefab으로 설정하여 Project 뷰에 등록하고 그 포탄 Prefab에 스크립트를 작성할 때 OnCollisionEnter(collision:Collision) 함수를 오버라이드하여 충돌처리 로직을 작성하면 된다는 것이다.
앞서 작성했던 포탄을 발사하는 프로젝트에 약간의 오브젝트와 스크립트를 추가로 사용하려고 한다.
대포의 반대편에 큐브를 사용하여 장벽을 한개 세우고 포탄이 명중하면 포탄과 장벽이 함께 사라지게 하려고 한다. 장벽 오브젝트에는 Rigid Body를 적용하여 중량물이 넘어지는 효과를 내려고 한다.
Game 뷰를 통해서 본 장면의 모습은 다음과 같다
앞서 작성했던 프로젝트는 Cylinder 로 대포를 만들고 왼쪽 CTRL 키나 마우스 왼쪽버튼을 누르면 포탄이 발사되는 기능이 구현되어 있었다.
그리고 여기에 멀리에 Cube 를 세우고 Component > Physics > RigidBody 항목을 선택하여 큐브에 강체의 속성을 갖도록 했을 뿐이다.
여기까지만 해도 기본적인 충돌처리는 이미 유니티 물리엔진이 지원하고 있기 때문에 개발자가 아직 충돌처리를 하지 않았지만 조준하고 포탄을 발사하여 장벽의 아랫부분을 명중하면 장벽이 뒤로 넘어지는 것을 볼 수 있다.
장벽에 중력의 효과를 적용하지 않으면 포탄이 장벽에 명중할 경우에 장벽이 충돌로 인하여 공중에서 유영하는 것처럼 회전하면서 떠 다니는 것을 볼 수 있다.
중력의 효과를 해제하려면 Inspector 뷰의 Use Gravity 항목을 선택 해제하면 된다. [아래 그림 참조]
위의 그림에서 Inspector 뷰의 Use Gravity 항목 아래에 있는 Is Kinematic 항목을 선택하면 포탄이 장벽에 명중해도 장벽은 아무런 반응이 없고 명중한 포탄이 오히려 튀어 나오게 된다. Is Kinematic 은 외부에서 가해지는 물리적 힘에 반응하지 않는 오브젝트라는 의미이다. 그러나 장벽은 Rigidbody 이므로 다른 오브젝트가 관통할 수는 없는 상태이다
이미 유니티는 기본 충돌처리를 지원하고 있지만 좀 더 실감나고 재미있는 충돌처리를 원한다면 스크립트를 작성하여 필요한 효과를 적용할 수 있다.
아래와 같은 스크립트를 작성하고 CannonBall 프리팹에 포함시키면 개발자는 포탄이 오브젝트와 충돌했을 때를 알 수 있고 충돌한 대상의 오브젝트 정보도 얻을 수 있다
#pragma strict
function Start () {
}
function Update () {
}
function OnCollisionEnter(collision:Collision) {
var hitName:String = collision.transform.name;
print("충돌:"+hitName);
if(hitName=='Wall') {
Destroy(gameObject,1); //목표에 명중한 포탄은 1초만에 사라진다
}else{
Destroy(rigidbody, 3); // 목표에 맞지않은 포탄은 사라지지 않고 3초후에 움직임을 멈춘다
}
}
Prefab에 포함되는 스크립트를 사용할 때는 테스트 중에 흔히 'The referenced script on this Behaviour is missing!' 이라는 오류 메시지를 대할 때가 있는데 문제가 되는 기존 Prefab 을 삭제하고 다시 생성하면 문제가 해결 되었다.
게임 오브젝트의 종류가 많고 오브젝트에 따라 파괴가 되거나 안되게 하려면 위의 방식을 사용하면 코드가 복잡해질 수 있다.
모든 게임 오브젝트에 Tag 를 설정해놓고 포탄이 명중했을 때 게임 오브젝트에 설정한 Tag를 확인하면 파괴의 대상인지 아닌지를 구분할 수 있기 때문에 오브젝트의 종류와 수가 많아도 코드 상에서는 파괴의 대상인지 아닌지 2가지로 구분하여 충돌처리를 하면 되므로 코드가 간결하고 쉽게 구성된다.
예를 들어, 오브젝트에 Tag 를 설정할 때 포탄에 파괴가 되는 오브젝트는 Tag를 'Target' 으로 설정하고, 파괴의 대상이 아닌 오브젝트의 경우에는 Tag를 'NotTarget' 등으로 설정해두고 OnCollisionEnter() 에서 collision.transform.tag=='Target' 을 사용하여 구별하면 된다.
각 오브젝트에 적용할 Tag 등록하기
- 프로그램에서 사용할 Tag 선언
- 특정 오브젝트에 Tag 설정: 현재 프로그램에 선언된 Tag 중에서 하나를 특정 오브젝트에게 설정함
Inspector 뷰의 상단에 있는 Tag 항목을 사용한다. 아래의 그림에서 Untagged 라고 되어 있는 것은 현재 선택된 오브젝트는 아직 Tag 를 설정하지 않은 상태라는 것을 보여준다
아래 그림과 같이 비어 있는 Element 0 항목에 임의의 문자열을 입력하면 자동으로 Element 1 이 생성되므로 얼마든지 많은 Tag를 선언할 수 있다
Scene 뷰에서 바닥을 구성하고 있는 Plane 오브젝트는 파괴의 대상이 아니므로 Tag 를 설정할 때 'NotTarget' 으로 설정하면 되고, 목표물인 Wall 오브젝트는 'Target' 이라는 Tag를 설정하면 된다.
현재 Scene 에서는 Plane, Cylinder, CannonBall 등은 NotTarget 으로 Tag를 설정, Wall 은 Target 으로 설정한다.
이제 충돌처리 부분의 스크립트를 약간 변경하여 아래처럼 Tag를 사용하여 포탄과 충돌한 오브젝트를 2종류로 구별할 수 있게 되었다
#pragma strict
function Start () {
}
function Update () {
}
function OnCollisionEnter(collision:Collision) {
var tag:String = collision.transform.tag;
print("충돌:"+tag);
if(tag=='Target') {
Destroy(gameObject,1); //목표에 명중한 포탄은 1초만에 사라진다
}else if(tag=='NotTarget'){
Destroy(rigidbody, 3); // 목표에 맞지않은 포탄은 사라지지 않고 3초후에 움직임을 멈춘다
}
}