본문 바로가기

Unity

[Unity] 함수 내 충돌 처리(isTouching , OverlapBox)

서론

유니티에서 충돌 처리를 위해 OnTriggerEnter, OnCollisionEnter등을 쓰는 경우는 매우 많다. 하지만 이 함수들의 단점은,

충돌이 일어났을 때 함수를 실행한다는 점이다. 즉, 기존에 쓰던 함수 내에서 따로 충돌 처리를 할 수 없다는 점이다.

 

나 같은 경우엔, 다음과 같은 일이 있었다.

더보기

플레이어와 닿을 시 점수 오브젝트를 랜덤한 위치로 옮긴다. 옮겨진 위치에 똑같은 점수 오브젝트가 있을 경우, 다시 위치를 옮긴다.

그렇게 점수 오브젝트끼리 겹치는 일을 없앤다.

지금까진 OnTriggerEnter를 사용해 해당 일을 처리했는데, 새로 생성된 오브젝트 뿐만이 아닌, 기존에 그 자리에 있던 오브젝트도 위치가 변한다는 단점이 있었다. 이는 점수를 먹으려던 플레이어에게 부정적인 경험을 줬다.

 

OnTriggerEnter로 항시 충돌 체크를 하는 것이 아닌, 재생성하는 시기에만 충돌 체크를 하고 싶었다.

 

 

 

 

 

본론

커스텀 함수 내부에서 충돌 체크를 할 수 있는 함수는, 대표적으로 다음 두 가지가 있다.

 

1. Physics2D.isTouching(Collider2D, Collider2D) 혹은 Collider2D.isTouching(Collider) (로직은 같다.)

2. Physics2D.OverlapBox

두 함수에 대해 알아보자.

 

 

 

1. Physics2D.isTouching

public static bool IsTouching(Collider2D collider1, Collider2D collider2);

 

간단히 설명하면, 인자로 받은 collider1과 collider2가 충돌한 상태라면 True를 반환하는 함수이다.

 

허나 실제 게임 내에선 태그 또는 레이어로 충돌 체크를 하는 일이 잦으니, 조금 더 유용한 함수가 있다.

 

 

 

1-1. Physics2D.isTouchingLayers

public static bool IsTouchingLayers(Collider2D collider, int layerMask = Physics2D.AllLayers);

 

첫번째 인자로 받은 collider가, 두번째 인자로 받은 레이어의 오브젝트와 충돌했는지 알려주는 함수이다.

 

내 게임을 예로 들면, 이렇게 사용할 수 있다.

void MoveToEmptyPosition()
{
	MoveToRandomPosition();	// 랜덤한 위치로 이동하는 함수
    
    while(Physics2D.isTouchingLayers(GetComponent<Collider2D>(), LayerMask.GetMask("Score"))
    {	// 당연하지만, 실제론 GetComponent를 캐싱해뒀다.
    	MoveToRandomPosition();
    }
}

랜덤한 위치로 이동한 후, 해당 위치에 Score 레이어 오브젝트가 있다면, 없을 때까지 위치를 이동한다.

 

혹은 다음과 같이 쓸 수 있다.

void MoveToEmptyPosition()
{
	MoveToRandomPosition();	// 랜덤한 위치로 이동하는 함수
    
    while(GetComponent<Collider2D>().isTouchingLayers(LayerMask.GetMask("Score"))
    {
    	MoveToRandomPosition();
    }
}

위와 아래의 로직은 완전히 같다. 그러니 편한 방법을 사용하자.

 

그러나 이 함수엔, 주의점2가지 있다.

 

 

1-1-1. NameToLayer 사용 금지

LayerMask.GetMask 대신 LayerMask.NameToLayer를 썼을 때 제대로 동작하지 않는다. 이는 정확히는 레이어에 관한 이해가 필요한 부분이지만, 나도 습관처럼 NameToLayer를 쓴 뒤에 고치게 되었다.

 

아래 주소를 참조하길 바란다.

https://forum.unity.com/threads/ground-check.308036/

 

Ground check

With the update to version 5 we got a cool method on colliders called isTouchingLayers(int layerMask) and thought this would be an easy way to setup a...

forum.unity.com

 

 

 

1-1-2. 물리 업데이트에 관한 이해 필요

유니티 스크립팅 API에서 isTouching을 찾아가보면, 이런 문구가 새겨져 있다.

더보기

It is important to understand that checking if Colliders are touching or not is performed against the last physics engine update i.e. the state of touching Colliders at that time. If you have just added a new Collider2D or have moved a Collider2D but a physics update has not yet taken place then the Colliders will not be shown as touching. The touching state is identical to that indicated by the physics collision or trigger callbacks.

간단히 해석하자면, 물리 업데이트가 끝나지 않았다면, 실제론 충돌하고 있는 상황이어도 충돌하지 않은 것으로 나타날 수 있다는 것이다. 물리 연산은 FixedUpdate 내에서 작동하며, isTouchingLayers의 실행 시기가 FixedUpdate보다 빠르다면, 정확한 값이 나오지 않는다는 것이다.

 

아래 Ground Check 이슈를 살펴보면 도움이 될 것이다.

https://forum.unity.com/threads/using-istouchinglayers-accurately.346688/

 

Using isTouchingLayers() Accurately

I decided it would be nice to use isTouchingLayers for a ground check in my new game, replacing the OverlapCircleNonAlloc I was using before....

forum.unity.com

 

실제로 나도 isTouchingLayers를 사용하려다 1-1-2의 이슈를 겪고, isTouchingLayers 대신 OverlapBox를 사용하기로 했다.

 

 

 

 

 

2. Physics2D.OverlapBox

public static Collider2D OverlapBox(Vector2 point, Vector2 size, float angle, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);

 

point를 기준으로, size 크기의 박스를 그리고, angle 각도 내에서, layerMask에 속하는 오브젝트를 찾아 반환한다.

 

물리 연산과는 별개로, 해당 시점에 충돌 체크를 실행하기 때문에 프레임과 관련된 이슈가 나지 않는다. 또한 특정 오브젝트나 Collider에 국한된 것이 아닌, 어느 지점이든 실행할 수 있다는 장점이 있다.

 

해당 함수를 사용하면 다음과 같이 코드를 짤 수 있다.

void MoveToEmptyPosition()
{
	MoveToRandomPosition();	// 랜덤한 위치로 이동하는 함수
    
    while(Physics2D.OverlapBox(transform.position, objectSize, 0, LayerMask.GetMask("Score"))
    {	// objectSize는 해당 오브젝트의 크기를 캐싱해둔 값이다.
    	MoveToRandomPosition();
    }
}

나는 점수 오브젝트가 사각형의 충돌 범위를 가졌기에 OverlapBox를 사용하였지만, Physics2D에는 OverlapCircle, OverlapCapsule등 다양한 형태의 함수가 있으니, 상황에 맞는 함수를 선택하도록 하자.

 

OverlapBox 함수를 사용하여, 나는 위의 코드처럼 오브젝트 재생성 시 어디에도 겹치지 않는 위치에, (겉으로 보기엔)한 번에 생성할 수 있었다. 여러분도 OnTrigger나 OnCollision 함수에 얽매이지 않고, 유연한 코드를 작성할 수 있길 바란다.