유니티 엔진에서 충돌체(Collider)를 찾아내는 방법은 여러 가지가 있다. 일반적으로는 게임 오브젝트에 캡슐(Capsule), 박스(Box), 구(Sphere) 형태의 콜라이터 컴포넌트를 달아서 OnTrigger나 OnCollision 계열의 이벤트를 사용해서 충돌체를 찾아내게 된다. 하지만 위 방법의 경우에는 콜라이더 컴포넌트를 지속적으로 유지해야하고 OnTrigger나 OnCollision 계열의 이벤트는 매 프레임 실행되기 때문에 필요한 순간에 한번만 충돌체를 찾아내려는 경우에는 성능상 부적절할 수 있다.
이렇게 필요한 순간에 단 한번 충돌체를 찾아내는 함수는 Physics라는 클래스가 static으로 가지고 있고 여기에 해당하는 함수들은 Cast라는 이름이 붙어있다. 이 Cast 함수에는 크게 4가지의 충돌체를 찾아내는 모양이 있는데 Ray, Box, Sphere, Capsule이 그것이다.
가장 일반적으로 사용되는 형식으로 특정 지점에서 시작하여 특정한 방향으로 향하는 직선 형태의 Cast이다. 이 직선에 닿은 Collider를 찾아낸다. 이 Ray Cast는 주로 사용자가 클릭한 지점이나 오브젝트를 3D 공간 상에서 찾아내기 위해서 주로 사용된다.
설정한 중심점을 시작으로 하여 지정한 가로, 세로, 높이에 해당하는 직육면체 형태의 Cast이다. 이 직육면체의 면에 닿거나 직육면체의 안에 있는 Collider를 찾아낸다.
설정한 중심점을 기준으로 지정한 반지름 내의 구 형태의 Cast이다. 이 구의 표면에 닿거나 구 안에 있는 Collider를 찾아낸다.
설정한 두 점을 있는 선을 중심으로 지정한 반지름만큼의 캡슐 형태의 Cast이다. 이 캡슐의 표면에 닿거나 캡슐 안에 있는 Collider를 찾아낸다.
앞서서 살펴본 내용이 충돌체를 찾아내는 모양에 따른 분류였다면 지금 이야기하는 것은 찾아낼 충돌체의 개수에 따른 분류이다. 이 분류에는 Cast, CastAll, CastNonAlloc이 있다.
Cast는 찾아낸 충돌체 하나만을 반환한다. Ray Cast를 예로 들자면 제일 처음 선에 충돌한 물체만을 반환하는 형식이다. 그 결과는 RayCastHit이라는 구조체로 반환된다.
CastAll은 찾아낸 충돌체 전부를 반환한다. 찾아낸 결과는 찾아낸 오브젝트의 개수와 딱맞는 RayCastHit 배열로 반환된다.
CastNonAlloc은 약간 독특한 방식인데 반환이 return을 통해서 이루어지는 것이 아니라, 매개변수를 통해서 이루어진다. 사용자가 RayCastHit의 배열을 만들어서 함수의 매개변수에 넣어주면, 함수가 그 배열에 찾아낸 오브젝트를 채워서 돌려주는 방식이다. 그렇기 때문에 찾아낸 오브젝트의 수가 배열의 수보다 적을 수도 많을 수도 있기 때문에 항상 주의해서 사용해야 한다.
각 형태와 방식에 따른 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의 정의에 대해서 헷갈리는 경우가 발생할 수 있는데 그것은 캡슡의 상단과 하단에 가상의 구가 존재한다고 생각했을 때, 그 구의 중심 위치라고 생각하면 된다.
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.