Physics 

컴포지트 콜라이더 2D(Composite Collider 2D)

 

작성 기준 버전 :: 2019.1.4f1

 

컴포지트 콜라이더 2D 컴포넌트는 다수의 2D 콜라이더를 하나의 콜라이더로 합쳐주는 역할을 하는 컴포넌트이다. 이 컴포넌트는 주로 여러 개의 콜라이더가 하나의 콜라이더로 취급하기 위해서 사용된다.

 

두 콜라이더의 충돌 감지

 

 

그림 1

 

[그림 1]을 보면 원형의 폴리곤 콜라이더를 가진 오브젝트와 사각 콜라이더를 가진 오브젝트가 함께 겹쳐져 있으며 두 오브젝트가 같은 부모 게임 오브젝트 밑에 있는 것을 확인할 수 있다. 위와 같은 상태에서는 두 개의 각 콜라이더가 충돌을 각각 감지한다.

 

public class CollisionTest : MonoBehaviour

{

    private void OnCollisionEnter2D(Collision2D collision)

    {

        Debug.Log(string.Format("OnCollisionEnter2D :: / ", gameObject.name, collision.gameObject.name));

    }

 

    private void OnCollisionStay2D(Collision2D collision)

    {

        Debug.Log(string.Format("OnCollisionStay2D :: / ", gameObject.name, collision.gameObject.name));

    }

 

    private void OnCollisionExit2D(Collision2D collision)

    {

        Debug.Log(string.Format("OnCollisionExit2D :: / ", gameObject.name, collision.gameObject.name));

    }

}

 

위와 같이 충돌을 감지하는 코드를 작성하고,

 

그림 2
그림 3

 

콜라이더가 있는 각 오브젝트에 부착한 뒤,

 

그림 4

 

[그림 4]와 같이 아래로 떨어지는 게임 오브젝트를 만들어서 충돌 테스트를 해보면,

 

그림 5

 

[그림 5]의 로그와 같이 각각의 오브젝트가 따로 충돌을 감지하는 것을 확인할 수 있다.

 

 

컴포지트 콜라이더 2D(Composite Collider 2D)

 

이런 식으로 각각의 콜라이더가 따로 충돌을 감지하는 것이 아니라 두 콜라이더가 하나로 취급되어 충돌을 감지하고자 할 때, 컴포지트 콜라이더 2D를 사용하게 된다.

 

그림 6

 

컴포지트 콜라이더 2D 컴포넌트를 사용하는 방법은 위와 같이 통합하고자하는 콜라이더들의 상위에 컴포지트 콜라이더를 추가해주고,

 

그림 7

 

[그림 7]과 같이 하위에 속하는 콜라이더 컴포넌트들의 Used By Composite 프로퍼티를 true로 설정해주면 된다.

 

그림 8

 

그렇게 하면 상위 오브젝트를 선택했을때, [그림 8]과 같이 하위의 콜라이더들이 하나로 통합되어서 보이는 것을 볼 수 있다.

 

그림 9

 

로그에서도 상위 오브젝트에서의 충돌 체크만 발생하는 것을 알 수 있다.

 

리지드바디 2D(Rigidbody 2D)

 

그림 10

 

컴포지트 콜라이더 2D 컴포넌트를 게임 오브젝트에 부착하면 자동으로 리지드바디 2D 컴포넌트 역시 게임 오브젝트에 부착된다. 때문에 지형지물의 요소에 컴포지트 콜라이더 2D를 사용할 때는 이 리지드바디 2D 컴포넌트의 옵션에 신경써야 한다.

 

그림 11

 

옵션에 신경쓰지 않을 경우, [그림 11]처럼 캐릭터가 지형에 닿자마자 지형이 떨어지거나, 게임이 시작하자마자 지형이 추락해서 지형이 사라지는 모습을 보게 될 수도 있다.

 

그림 12

 

이렇게 컴포지트 콜라이더 2D 컴포넌트를 적용한 게임 오브젝트를 고정된 지형요소로 사용하고자 한다면 리지드바디2D의 바디타입을 Static으로 설정해야한다.

 

그림 13

 

 

생성 타입(Generation Type)

 

그림 14

 

컴포지트 콜라이더 2D 컴포넌트에는 생성 타입이라는 프로퍼티가 존재하는데 Synchronous와 Manual 옵션이 존재한다.

 

그림 15

 

Synchronous는 [그림 15]처럼 하위 콜라이더들이 변동되는 때마다 바로 통합된 콜라이더를 새로 만드는 옵션이고, 

 

그림 16

 

Manual은 컴포지트 콜라이더 2D 컴포넌트가 처음 부착되는 시점 호은 콜라이더 재생성(Regenerate Collider) 버튼을 누른 시점에만 콜라이더를 만드는 옵션이다.

 

그림 17

 

[그림 17]과 같이 아래에 있는 노란 원이 애니메이션에 의해서 위치가 바뀌어도 콜라이더는 따라서 움직이지 않기 때문에 빈 공간에 충돌하는 것을 알 수 있다.

 

위의 설명에서 알 수 있듯이 Synchronous 옵션은 통합된 콜라이더가 변경되는 애니메이션에 따라서 지속적으로 업데이트가 되어야하는 경우에 사용하고, 반대로 Manual은 통합된 콜라이더가 일반 지형과 같이 변경되는 경우가 없는 경우에 사용된다.

 

지오메트리 타입(Geometry Type)

 

그림 18

 

그 다음 중요한 옵션은 지오메트리 타입이다. 이 프로퍼티는 콜라이더를 생성할 때 어떤 형태로 생성할 것인가를 결정한다. 옵션 값은 Outlines와 Polygons가 있다. 

 

그림 19
그림 20

 

Outlines로 콜라이더를 생성하면 [그림 19]와 같이 내부에 아무선 선이 없이 생성되고, Polygons로 콜라이더를 생성하면 [그림 20]과 같이 내부에 선이 그어져서 나온다. 이 차이가 의미하는 것은 Outlines 옵션의 경우에는 콜라이더가 외부에 선만 그어져 있고 속은 비어있다는 뜻이고, Polygons 옵션은 내부가 꽉 차있다는 뜻이다.

 

콜라이더의 내부를 굳이 채울 필요가 있냐고 생각할 수도 있지만, 이것은 확실히 필요한 개념이다. 게임 내에서의 발생하는 충돌 감지나 레이캐스트는 주로 옆에서 날아오기 때문에 Outlines 옵션 만으로도 충분하지만 화면 밖에서 들어오는 터치나 사용자의 클릭 같은 이벤트는 개념적으로 위에서 들어오기 때문에 콜라이더의 내부가 차있는 것이 좋다.

 

public class Picker : MonoBehaviour

{

    private void Update()

    {

        if (Input.GetMouseButtonDown(0))

        {

            var hitResult2D = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), transform.up, 0.1f);

            Debug.Log("2D Raycast Result :: " + hitResult2D.collider.name);

        }

    }

}

 

위와 같이 마우스를 클릭한 지점에서 레이캐스트를 발사해서 콜라이더를 검출하는 코드를 작성해서 Outlines 옵션으로 만들어진 컴포지트 콜라이더와 Polygons 옵션으로 만들어진 컴포지트 콜라이더를 클릭하는 테스트를 진행해보면 그 차이를 명확하게 인지할 수 있다.

반응형
  1. 료용 2019.12.26 02:05

    안녕하세요 베르님 좋은글잘보고있습니다.

UI 비법서 (2)

-

하나의 그룹 Canvas Group


작성 기준 버전 :: 2019.1.4f1


UI를 만들 때, 한 UI 창에 텍스트, 이미지, 버튼, 체크박스 등 수많은 구성요소들이 들어간다. 하나의 UI 창을 컨트롤 한다는 것은 이 UI 창 아래에 들어가는 구성요소들을 한꺼번에 통제해야 한다는 것을 의미한다.


 

위 이미지와 같은 UI 창이 있고, 이 창이 열리거나 닫힐 때, 투명도를 사용해서 투명해지는 연출을 사용한다고 가정했을 때, 일반적으로는 UI에서 이미지나 텍스트를 표현하는 모든 컴포넌트의 색상 값 중에 알파 값을 일일이 수정해주어야 한다. 이 작업은 UI 창의 구성이 복잡해질 수록 더욱 귀찮고 힘든 작업이 되며 이런 작업을 해놓은 다음에 UI의 구성이 바뀌기라도 하면 더 큰 문제가 발생한다.


 

이러한 문제를 해결해 주는 것이 바로 캔버스 그룹(Canvas Group) 컴포넌트다.



캔버스 그룹(Canvas Group)


 

캔버스 그룹 컴포넌트를 사용하기 위해서는 하나의 그룹으로 하고자하는 UI의 최상단 게임 오브젝트를 선택하고 인스펙터 창의 [Add Component] 버튼을 눌러 Canvas Group를 검색하여 추가하면 된다.


 

그렇게 캔버스 그룹을 추가하면 위의 이미지와 같은 캔버스 그룹 컴포넌트가 생성된다. 캔버스 그룹 컴포넌트는 많은 기능을 가지고 있지 않고 간단한 기능 몇 가지만 제공한다. 하지만 이것들만으로도 원래라면 손이 많이 가는 작업들을 크게 줄여준다.



알파 값 조정


캔버스 그룹의 첫 번째 주요 기능은 하위 그룹의 UI들의 알파 값을 한꺼번에 조절할 수 있게 해주는 것이다.


 

위 이미지를 보면 아까 전의 예시에서는 UI 구성요소들의 알파 값을 일일이 수정해주던 것에서 캔버스 그룹의 알파 값만 수정해주면 되는 것으로 바뀌었다.


 

캔버스 그룹으로 알파 값을 조정하는 방법의 최대의 장점은 그룹 아래의 UI 구조가 어떤 식으로 바뀌어도 그 밑에 있는 모든 UI에 자동으로 적용이 된다는 것이다.



상호작용가능함(Interactable)


두 번째 옵션은 상호작용에 관한 것이다. UI에는 상호작용이 가능한 종류의 컴포넌트인 버튼(Button), 토글(Toggle), 슬라이더(Slider) 등이 있는데, 이런 컴포넌트들은 기본적으로 상호작용에 대한 옵션을 가진다. 그 옵션이 바로 상호작용가능함(Interactable)이다.


 

이것들 역시 캔버스 그룹 없이 한꺼번에 제어하고자 한다면, 각각의 컴포넌트에 일일이 접근하여서 값을 수정해주어야 한다.


 

하지만 캔버스 그룹의 상호작용가능함 옵션을 사용하면 하위에 있는 모든 상호작용가능한 컴포넌트들이 제어되는 것을 확인할 수 있다.





레이캐스트 블록(Blocks Raycasts)


레이캐스트는 일종의 광선을 의미하는데, 일반적으로 마우스를 클릭한 화면 위치에서 게임 속 공간에 레이저를 쏴서 클릭한 위치를 찾아내는 역할을 하는 데에 주로 사용되며, 이를 응용해서 주로 RPG 게임에서 클릭한 위치로 캐릭터를 이동시키는데 사용하게 된다.


UI에서 레이캐스트를 블록, 즉 레이캐스트를 막는다는 것의 의미는 첫 번째로, UI가 입력을 받아들인다는 뜻이다.


 

위의 gif 이미지를 보면, 입력을 받아들인다는 뜻을 이해할 수 있다. Blocks Raycasts를 켰을 때는 레이캐스트가 UI에 막혀서 입력을 받을 수 있게 되고, 반대로 꺼졌을 때는 입력을 받아들이지 못한다.


그렇다면 여기서 위의 상호작용가능함 옵션과 관련해서 어떤 차이가 있느냐는 질문을 할 수가 있다. 둘 다 똑같이 입력을 못받게 되는데 다른게 없지 않은가? 물론 이 두 개의 옵션은 완전히 다른 옵션이다.


상호작용가능함 옵션의 예시 gif에서는 상호작용가능함 옵션이 꺼지면서 버튼, 토글, 슬라이드가 회색으로 변하고 버튼, 토글, 슬라이드와의 상호작용이 불가능하게 바뀌었지만 레이캐스트, 즉 마우스가 클릭될 때 발사되는 눈에 보이지 않는 광선은 그대로 UI에 충돌하는 상태였다.


하지만 레이캐스트 블록 옵션의 예시 gif에서는 마우스가 클릭될 때 발사되는 광선이 UI를 무시하고 지나간 것이다.


 

레이캐스트 블록 옵션의 꺼진 상태와 켜진 상태의 차이점과 활용에 대한 예시는 디아블로 3로 들 수 있다. 디아블로 3에서는 탭(tab) 키를 누르면 미니맵이 열리는데 미니맵 UI가 화면 전체에 가득 차서 흐릿하게 보일 것이다. 하지만 이 미니맵은 화면 전체를 덮는 UI임에도 불구하고 클릭한 위치로 캐릭터를 이동할 수 있게 마우스의 클릭에서 발사되는 레이캐스트를 막지 않는다.


 

하지만 인벤토리 창의 경우에는 인벤토리 찾 어느 위치를 클릭해도 캐릭터가 인벤토리 창 너머로 클릭된 지형의 위치로 이동하지 않는다. 즉, 마우스 클릭에서 발사된 레이캐스트가 인벤토리라는 UI에 막혀서 이동할 위치를 찾지 못한 것이다.



마지막 옵션인 부모 그룹 무시(Ignore Parent Group)은 잘 사용되지 않는 옵션이다.


이렇듯이 캔버스 그룹을 사용하면 원래는 간단하지만 계층이 복잡해지면 어려워지는 작업을 간단하게 처리할 수 있다.

반응형
  1. 유니티 예비 개발자 2020.01.13 22:52

    정말 이해하기 쉽게 잘 정리 해주셔요!

게임 오브젝트에 Collider 컴포넌트를 추가하지 않고 한번만 충돌체를 찾아내는 Physics의 Cast 계열 함수들의 사용법


유니티 엔진에서 충돌체(Collider)를 찾아내는 방법은 여러 가지가 있다. 일반적으로는 게임 오브젝트에 캡슐(Capsule), 박스(Box), 구(Sphere) 형태의 콜라이터 컴포넌트를 달아서 OnTrigger나 OnCollision 계열의 이벤트를 사용해서 충돌체를 찾아내게 된다. 하지만 위 방법의 경우에는 콜라이더 컴포넌트를 지속적으로 유지해야하고 OnTrigger나 OnCollision 계열의 이벤트는 매 프레임 실행되기 때문에 필요한 순간에 한번만 충돌체를 찾아내려는 경우에는 성능상 부적절할 수 있다.


이렇게 필요한 순간에 단 한번 충돌체를 찾아내는 함수는 Physics라는 클래스가 static으로 가지고 있고 여기에 해당하는 함수들은 Cast라는 이름이 붙어있다. 이 Cast 함수에는 크게 4가지의 충돌체를 찾아내는 모양이 있는데 Ray, Box, Sphere, Capsule이 그것이다.


Ray

가장 일반적으로 사용되는 형식으로 특정 지점에서 시작하여 특정한 방향으로 향하는 직선 형태의 Cast이다. 이 직선에 닿은 Collider를 찾아낸다. 이 Ray Cast는 주로 사용자가 클릭한 지점이나 오브젝트를 3D 공간 상에서 찾아내기 위해서 주로 사용된다.


Box

설정한 중심점을 시작으로 하여 지정한 가로, 세로, 높이에 해당하는 직육면체 형태의 Cast이다. 이 직육면체의 면에 닿거나 직육면체의 안에 있는 Collider를 찾아낸다.


Sphere

설정한 중심점을 기준으로 지정한 반지름 내의 구 형태의 Cast이다. 이 구의 표면에 닿거나 구 안에 있는 Collider를 찾아낸다.


Capsule

설정한 두 점을 있는 선을 중심으로 지정한 반지름만큼의 캡슐 형태의 Cast이다. 이 캡슐의 표면에 닿거나 캡슐 안에 있는 Collider를 찾아낸다.


앞서서 살펴본 내용이 충돌체를 찾아내는 모양에 따른 분류였다면 지금 이야기하는 것은 찾아낼 충돌체의 개수에 따른 분류이다. 이 분류에는 Cast, CastAll, CastNonAlloc이 있다.


Cast

Cast는 찾아낸 충돌체 하나만을 반환한다. Ray Cast를 예로 들자면 제일 처음 선에 충돌한 물체만을 반환하는 형식이다. 그 결과는 RayCastHit이라는 구조체로 반환된다.


CastAll

CastAll은 찾아낸 충돌체 전부를 반환한다. 찾아낸 결과는 찾아낸 오브젝트의 개수와 딱맞는 RayCastHit 배열로 반환된다.


CastNonAlloc

CastNonAlloc은 약간 독특한 방식인데 반환이 return을 통해서 이루어지는 것이 아니라, 매개변수를 통해서 이루어진다. 사용자가 RayCastHit의 배열을 만들어서 함수의 매개변수에 넣어주면, 함수가 그 배열에 찾아낸 오브젝트를 채워서 돌려주는 방식이다. 그렇기 때문에 찾아낸 오브젝트의 수가 배열의 수보다 적을 수도 많을 수도 있기 때문에 항상 주의해서 사용해야 한다.


각 형태와 방식에 따른 Cast의 사용법은 다음과 같다.




Cast 사용법

// Raycast

Vector3 startVect = Vector3.zero;
Vector3 direction = Vector3.forward;
float distance = 10f;

Ray ray = new Ray(startVect, direction);
RayCastHit hit = Physics.Raycast(ray, distance);



// RaycastAll

Vector3 startVect = Vector3.zero;
Vector3 direction = Vector3.forward;
float distance = 10f;

Ray ray = new Ray(startVect, direction);
RaycastHit[] hits = Physics.RaycastAll(ray, distance);

foreach(var hit in hits)
{
    Debug.Log(hit.collider.name);
}



// RaycastNonAlloc

Vector3 startVect = Vector3.zero;
Vector3 direction = Vector3.forward;
float distance = 10f;
Ray ray = new Ray(startVect, direction);
RaycastHit[] hits = new RaycastHit[10];     // 여기서 할당한 배열 수 이상은 가지고 오지 못한다.
int hitCount = Physics.RaycastNonAlloc(ray, hits, distance);

// foreach를 사용할 경우, 찾아낸 숫자가 배열 길이보다 작으면 에러가 발생한다.
for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}



// BoxCast

Vector3 boxCenter = Vector3.zero;
Vector3 boxHalfSize = new Vector3(1f, 1f, 1f);  // 캐스트할 박스 크기의 절반 크기. 이렇게 하면 가로 2 세로 2 높이 2의 크기의 공간을 캐스트한다.
Vector3 direction = Vector3.up;


bool isCollide = Physics.BoxCast(boxCenter, boxHalfSize, direction);    // 일반적으로 BoxCast는 충돌 여부만 반환한다.


RaycastHit hit;

Physics.BoxCast(boxCenter, boxHalfSize, direction, out hit);    // 충돌 결과에 대한 내용을 가져오려면 RaycastHit 구조체를 out 매개변수로 넣어주어야 한다.

Debug.Log(hit.collider.name);



// BoxCastAll

Vector3 boxCenter = Vector3.zero;
Vector3 boxHalfSize = new Vector3(1f, 1f, 1f);  // 캐스트할 박스 크기의 절반 크기. 이렇게 하면 가로 2 세로 2 높이 2의 크기의 공간을 캐스트한다.
Vector3 direction = Vector3.up;
RaycastHit[] hits = Physics.BoxCastAll(boxCenter, boxHalfSize, direction);    // BoxCastAll은 찾아낸 충돌체를 배열로 반환한다.

foreach (var hit in hits)
{
    Debug.Log(hit.collider.gameObject.name);
}



// BoxCastNonAlloc

Vector3 boxCenter = Vector3.zero;
Vector3 boxHalfSize = new Vector3(1f, 1f, 1f);  // 캐스트할 박스 크기의 절반 크기. 이렇게 하면 가로 2 세로 2 높이 2의 크기의 공간을 캐스트한다.
Vector3 direction = Vector3.up;
RaycastHit[] hits = new RaycastHit[10];
int hitCount = Physics.BoxCastNonAlloc(boxCenter, boxHalfSize, direction, hits);

for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}



// SphereCast

Vector3 origin = Vector3.zero;
Vector3 direction = Vector3.up;
Ray ray = new Ray(origin, direction);
float radius = 5f;
bool isCollide = Physics.SphereCast(ray, radius);

RaycastHit hit;
Physics.SphereCast(ray, radius, out hit);

Debug.Log(hit.collider.name);



// SphereCastAll

Vector3 origin = Vector3.zero;
Vector3 direction = Vector3.up;
Ray ray = new Ray(origin, direction);
float radius = 5f;
RaycastHit[] hits = Physics.SphereCastAll(ray, radius);

foreach (var hit in hits)
{
    Debug.Log(hit.collider.gameObject.name);
}



// SphereCastNonAlloc

Vector3 origin = Vector3.zero;
Vector3 direction = Vector3.up;
Ray ray = new Ray(origin, direction);
float radius = 5f;
RaycastHit[] hits = new RaycastHit[10];
int hitCount = Physics.SphereCastNonAlloc(ray, radius, hits);

for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}



// CapsuleCast

Vector3 v1 = new Vector3(0f, 0f, 0f);    // 캡슐 시작 부분의 구에 대한 중심점
Vector2 v2 = new Vector3(0f, 3f, 0f);    // 캡슐 끝 부분의 구에 대한 중심점
Vector3 direction = Vector3.up;
float radius = 5f;
bool isCollide = Physics.CapsuleCast(v1, v2, radius, direction);

RaycastHit hit;
Physics.CapsuleCast(v1, v2, radius, direction, out hit);

Debug.Log(hit.collider.name);



// CapsuleCastAll

Vector3 v1 = new Vector3(0f, 0f, 0f);
Vector2 v2 = new Vector3(0f, 3f, 0f);
Vector3 direction = Vector3.up;
float radius = 5f;
RaycastHit[] hits = Physics.CapsuleCastAll(v1, v2, radius, direction);

foreach (var hit in hits)
{
    Debug.Log(hit.collider.gameObject.name);
}



// CapsuleCastNonAlloc

Vector3 v1 = new Vector3(0f, 0f, 0f);
Vector2 v2 = new Vector3(0f, 3f, 0f);
Vector3 direction = Vector3.up;
float radius = 5f;
RaycastHit[] hits = new RaycastHit[10];
int hitCount = Physics.CapsuleCastNonAlloc(v1, v2, radius, direction, hits);

for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}


추가적인 이야기로는 캡슐 캐스트를 사용할 때, v1과 v2의 정의에 대해서 헷갈리는 경우가 발생할 수 있는데 그것은 캡슡의 상단과 하단에 가상의 구가 존재한다고 생각했을 때, 그 구의 중심 위치라고 생각하면 된다.

반응형
  1. 료용 2020.02.21 01:19 신고

    Vector3 startVect = Vector3.zero;
    Vector3 direction = Vector3.forward;
    float distance = 10f;

    Ray ray = new Ray(startVect, direction);
    RaycastHit hit = Physics.Raycast(ray, distance);
    이거 이렇게쓰니까
    RaycastHit hit = Physics.Raycast(ray, distance);
    이부분에서 암시적으로 bool형식을 unityEngine.RaycastHit 형식으로 변환할수없다고뜨는데 뭐가문제인거죠?


    • wergia 2020.02.23 00:07 신고

      꽤나 구버전 코드라 그런지 RayCast가 약간 바뀐것 같습니다.
      최신 버전 유니티에서는
      Physics.RayCast 함수가 충돌을 감지했는지에 대한 bool 값을 반환하고 감지 결과는 out 매개변수로 받도록 변경되었습니다.
      따라서 아래 코드와 같이 작성하시면 될것 같습니다.
      Vector3 startVect = Vector3.zero;
      Vector3 direction = Vector3.forward;
      float distance = 10f;

      Ray ray = new Ray(startVect, direction);
      RaycastHit hit;
      if(Physics.Raycast(ray, out hit, distance))
      {

      }

+ Recent posts