Tilemap (3) 

타일맵에 콜라이더 추가하기

 

작성 기준 버전 :: 2019.1.4f1

 

이전 섹션들에서는 타일맵 생성 방법2D 엑스트라의 룰 타일을 이용한 타일 자동 연결 기능을 알아보았다. 이번 섹션에서는 타일맵에 콜라이더를 추가하는 방법을 배워볼 것이다.

 

이제까지 타일맵에 사용될 타일 이미지를 만들고 가져오는 방법, 타일맵을 만들고 사용하는 방법들을 배웠다. 이제 만들어낸 타일맵을 이용해서 맵을 그려내면 게임 레벨이 될 것이다. 하지만 여기에 아직 부족한 점이 있다.

 

지금까지 배운 것들로는 맵을 그리기만 할 수 있다. 무슨 말인가 하면, 2D RPG 류의 게임에서는 어떤 타일은 벽이 되서 캐릭터가 이동하는 것을 막는 장애물이 되어야 하고, 플랫폼 게임(Platform Game)에서는 타일이 캐릭터가 딪고 설 바닥이 되어주어야 한다. 즉, 타일에 콜라이더를 추가해서 물리적인 작용이 가능하게 만들어야 한다는 뜻이다.

 

그림 1

 

플랫폼 게임을 만드는데 노란 공이 떨어져서 바닥에 닿으면 튕기게 만들고 싶다고 가정해보자.

 

그림 2

 

노란 공에 물리효과를 주기 위해서 Circle Collider 2D 컴포넌트와 Rigidbody 2D 컴포넌트를 부착해주었다. 그리고 꽤 그럴듯하게 공처럼 튀기게 만들기 위해서 물리 머티리얼(Physics Material)까지 넣어주었다.

 

그림 3
그림 4

 

하지만 타일맵에 물리적인 컴포넌트가 아무것도 없는 상태이기 때문에 플레이를 시작하면 떨어지는 공은 타일맵을 그냥 통과해버린다. 

 

그림 5

 

타일맵 콜라이더 2D 컴포넌트(Tilemap Collider 2D Component)

 

이전 섹션을 진행해왔다면 하이어라키 뷰에 존재하는 타일맵은 게임 오브젝트하나로 존재하기 때문에 어떻게 콜라이더를 배치해야할지 난감할 수도 있다.

 

그림 6

 

타일맵을 위한 콜라이더를 유니티에서는 이미 제공하고 있다. 타일맵 콜라이더 2D 컴포넌트(Tilemap Collider 2D Component)가 바로 그것이다.

 

그림 7

 

타일맵 컴포넌트가 붙어있는 게임 오브젝트에 타일맵 콜라이더 2D 컴포넌트를 부착하면 씬에서 위의 이미지와 같이 각 타일마다 콜라이더가 생겼음을 알 수 있다.

 

그림 8

 

타일맵에 콜라이더 컴포넌트를 붙인 상태로 다시 게임을 플레이해보면 떨어진 공이 바닥에 맞고 튕기는 것을 볼 수 있다.

 

 

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

 

[그림 8]을 보면 타일맵 컴포넌트 2D를 이용해서 생성된 콜라이더가 각 타일마다 따로 생성되어 있는 것을 볼 수 있다. 이렇게 분할된 콜라이더는 퍼포먼스 상의 문제와 가끔 이동하는 캐릭터가 콜라이더에 끼어서 움직이지 못하게 되는 등의 문제가 발생할 수 있다.

 

그런 문제를 해결하기 위해서 제공되는 것이 컴포지트 콜라이더 2D 컴포넌트이다. 이 컴포넌트는 해당 컴포넌트가 붙어있는 게임 오브젝트의 하위에 존재하는 콜라이더들을 하나로 묶어주는 역할을 한다.

 

그림 9

 

컴포지트 콜라이더 2D 컴포넌트를 사용하기 위해서는 타일맵 콜라이더 2D 컴포넌트를 부착한 컴포넌트에 컴포지트 콜라이더 2D 컴포넌트를 부착하고 타일맵 콜라이더 2D 컴포넌트의 Used By Composite 프로퍼티를 체크해주면 된다.

 

그림 10

 

그렇게 하고 나서 씬 뷰에서 타일맵 게임 오브젝트를 선택해보면 초록색 콜라이더 박스가 타일마다 나누어지지 않고 하나로 합쳐져 있는 것을 확인할 수 있다.

 

그림 11

 

하지만 아직 설정이 다 끝나지 않았다. 플레이를 눌러보면 타일맵이 공과 함께 떨어지는 어이없는 상황이 발생한다. [그림 9]를 보면 그 이유를 조금 짐작할 수 있는데 컴포지트 콜라이더 2D 컴포넌트를 추가할 때, 리지드바디 2D 컴포넌트(Rigidbody 2D 컴포넌트)가 자동으로 추가된 것을 알 수 있는데, 리지드바디 컴포넌트는 게임 오브젝트가 외부의 힘이나 토크를 받아 사실적인 물리적인 운동을 보이도록 도와주는 컴포넌트이다.

 

그림 12

 

자동으로 추가된 리지드바디 2D 컴포넌트를 보면 바디 타입(Body Type)이 다이나믹(Dynamic)으로 설정있는 것을 알 수 있다. 즉 타일맵의 리지드바디가 고정된 것이 아니기 때문에 공과 함께 떨어지는 것이다.

 

그림 13
그림 14

 

바디 타입을 고정(Static)으로 변경하고 실행해보면 [그림 14]와 같이 타일맵이 떨어지지 않고 정상적으로 동작하는 것을 확인할 수 있다.

 

다만 컴포지트 콜라이더 2D를 사용하는 경우에 주의할 점은 하위에 있는 모든 콜라이더를 하나로 통합하기 때문에, 플랫폼 게임을 만들 때 벽 타일의 콜라이더와 바닥 타일의 콜라이더가 플레이어와 충돌 시 다른 동작을 하게 만들고 싶다면 벽 타일의 타일맵과 바닥 타일의 타일맵을 분리하거나, 캐릭터가 충돌한 방향을 검출해서 벽인지 바닥인지를 검출하는 등의 추가 작업이 필요하다.  

 


 

Tilemap (1) - 2D 게임의 기본 타일맵!

Tilemap (2) - 룰 타일로 타일맵 자동 연결하기

Tilemap (3) - 타일맵에 콜라이더 추가하기

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

게임 오브젝트에 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의 정의에 대해서 헷갈리는 경우가 발생할 수 있는데 그것은 캡슡의 상단과 하단에 가상의 구가 존재한다고 생각했을 때, 그 구의 중심 위치라고 생각하면 된다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형
  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