Unity의 Physics.Raycast는 직선을 씬에 투영하여 대상에 적중되면 true를 리턴하는 물리 함수입니다. Raycast 함수는 캐스팅 성공 실패에 따른 결과만 리턴하는 간단한 형태에서부터 대상과 Ray의 충돌에 관련된 자세한 정보를(직선과 객체의 교차 정보. 거리, 위치, 캐스팅에 검출 된 객체의 Transform에 대한 참조 등) 리턴하는 다양한 버전이 제공되고 있습니다.
이번 포스트에서는 Raycast 함수를 사용하기 위해 알아야할 필수적인 요소들을 살펴보도록 하겠습니다.
Unity에서 Raycast를 사용하는 방법
Unity 2020.3 버전 기준으로 Physics.Raycast는 다양한 버전으로 오버로드되어 제공되고 있습니다. 파라미터가 많아 복잡해 보이지만 디폴트 파라미터들을 제외하고 보면 결국 Raycast 함수의 핵심은 아래 세 가지 정도로 요약됩니다.
- Ray 변수
- RaycastHit 변수
- Raycast 함수
Ray 구조체 사용법
Ray는 직선의 시작점(origin)과 방향(direction)을 가지고 있는 단순한 구조체입니다. 시작점(origin)은 Vector3 타입의 월드 포지션이며 방향(direction)은 직선의 방향을 나타내는 Vector3 타입의 법선 벡터입니다.
Ray를 생성할 수 있는 방법은 여러 가지가 있습니다. 먼저 new를 이용해 직접 생성하는 방법이 있습니다.
// Creates a Ray from this object, moving forward
Ray ray = new Ray(transform.position, transform.forward);
카메라 뷰포트 중앙에서 시작하는 Ray와 같은 경우 헬퍼 함수를 이용해 아래와 같이 Ray를 자동으로 생성할 수 있습니다.
// Creates a Ray from the center of the viewport
// 아래에서 0.5f 값은 뷰포트의 중간값을 나타낸다.
Ray ray = Camera.main.ViewportPointToRay(new Vector3 (0.5f, 0.5f, 0));
스크린의 마우스 위치로부터 Ray를 만들어 낼 수도 있습니다.
// Creates a Ray from the mouse position
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Ray가 시작되는 위치와 방향을 결정한 후, Ray로부터 얻은 데이터를 RaycastHit 변수에 저장합니다.
RaycastHit 구조체 사용법
RaycastHit은 객체와 Ray의 충돌에 대한 결과 정보를 저장하는 구조체입니다. Raycast 함수의 out 파라미터로 사용되며 월드에서 레이캐스팅 히트가 발생한 위치, Ray가 충돌한 물체, Ray의 원점에서 얼마나 떨어져있는지 등의 정보를 저장하여 돌려줍니다.
RaycastHit을 사용하기 위해선 다음과 같이 선언합니다.
// Container for hit data
RaycastHit hitData;
그리고 Raycast 함수를 통해 씬에 Ray를 발사하면 캐스팅 결과에 따라 충돌에 대한 정보를 RaycastHit 변수에 저장합니다. RaycastHit에 저장된 정보를 아래와 같이 접근할 수 있습니다.
- RaycastHit.point를 이용하여 월드에서 레이캐스팅이 감지된 위치를 얻을 수 있습니다.
- RaycastHit.distance를 사용하여 Ray의 원점에서 충돌 지점까지의 거리를 구할 수 있습니다.
- Tag와 같은 히트 된 대상 객체의 Collider 세부 정보를 얻을 수 있습니다.
- RaycastHit.transform을 사용하여 충돌 객체의 Transform에 대한 참조를 얻을 수 있습니다.
Raycast 함수의 다양한 기능들
Unity에서는 Raycast 함수를 이용하여 씬 안에서 Ray를 발사하여 충돌체를 검출할 수 있다. 이 함수는 다양한 버전이 있으며, 각각은 서로 다른 기능을 제공한다. 예를 들어, 일부 버전은 몇 가지 인자만 사용하여 간단한 기능을 수행하며, 다른 오버로드된 버전은 더 복잡한 인자들을 받아들이고 더 복잡한 작업을 한다.
가장 기본적인 버전의 Physics.Raycast 함수는 Ray 변수 하나만을 인자로 받는다.
if (Physics.Raycast(ray))
{
// The Ray hit something
}
하지만, 다른 오버로드된 버전의 Physics.Raycast 함수는 Ray, RaycastHit, MaxDistance, LayerMask(특정 레이어가 포함되거나 제외 되는 것을 지정) 및 TriggerCollider를 사용할 수 있는지 여부를 결정하는 QueryTriggerInteraction을 설정할 수 있다.
public LayerMask layerMask;
void Update()
{
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hitData;
if (Physics.Raycast(ray, out hitData, 10, layerMask, QueryTriggerInteraction.Ignore))
{
// The Ray hit something less than 10 Units away,// It was on a certain Layer// But it wasn't a Trigger Collider
}
}
이제부터 Raycast 함수들이 제공하는 기능들에 대해 살펴보도록 하자.
최대 거리를 지정하여 Raycast 범위 제한
대부분의 오버로드된 Physics.Raycast 함수에서는 아래와 같이 레이 캐스팅 최대 거리를 제한할 수 있다.
Ray ray = new Ray(transform.position, transform.forward);
if (Physics.Raycast(ray, 10))
{
// Hit something closer than 10 units away
}
최대 거리를 제한하므로써 최대 사거리가 있는 발사 무기의 명중 판정이라던지, 단순히 씬 전체를 무한히 가로질러 발생할 수 있는 다양한 문제를 예방할 수 있다.
하지만 거리 제한만으로 충분할까? 아니다. 제한된 거리 내에서도 다양한 충돌 객체가 감지될 수 있다. 어떤 객체가 충돌에 감지되어야 하고 그렇지 않은지를 결정하는 것은 전적으로 사용자에게 달려있다. 이제부터 알아볼 것은 충돌이 감지되었을 때 어떻게 구분하여 별도의 처리를 해줄 수 있는지를 살펴보도록 하겠다.
Raycast에 Layer Mask 사용하기
Raycast 함수의 유용한 기능 중 하나는 레이어에 따라 충돌체를 필터링하는 기능이다. 이를 통해 레이캐스팅에서 무시해야 하는 객체를 쉽게 구분할 수 있다. 만일 당신이 아주 커다란 씬에서 엄청나게 많은 객체들이 있고 각각의 객체들이 서로 다양한 타입을 가지고 있다고 상상해보자. 이 기능은 이럴 때 당신에게 필요한 특정 몇몇 객체들에 대해서만 레이캐스팅을 진행할 수 있게 해주는 아주 유용한 도구다.
예를 들어, 'world'라는 레이어가 있고 해당 레이어의 객체들만 레이캐스팅을 이용해 감지하려고 한다고 가정해보자. 당신이 가장 먼저 해야 할 일은 public으로 선언된 LayerMask 변수를 생성하는 것이다.
public class CameraRay : MonoBehaviour
{
public LayerMask worldLayer;
// ...
}
이렇게 생성된 public LayerMask 변수는 인스펙터에서 설정이 가능하다.
또는 내장 메서드를 이용해 레이어 이름으로 LayerMask를 찾을 수 있다.
LayerMask.GetMask(params string[] layerNames)
Raycast를 사용할 때 trigger collider를 무시하는 방법
Raycasting 함수가 trigger collider에 대해서 동작하는 것을 원치 않는 경우 해당 객체를 별도의 레이어에 배치하는 방법이 있겠지만, 그리 좋은 방법은 아닙니다. 이번 섹션에서는 레이어에서 트리거 콜라이더를 무시하는 방법에 대해 살펴 보도록 하겠습니다.
기본적으로 raycasting은 트리거 콜라이더를 감지합니다. 레이캐스트가 트리거 콜라이더에 충돌하면 다른 콜라이더와 동일한 방식으로 동작합니다. 하지만 프로젝트 설정을 통해 전역적으로 또는 Raycast별로 동작을 변경할 수 있습니다.
모든 Raycast Trigger 충돌을 비활성화 하는 방법
모든 Raycast 트리거 충돌을 비활성화 하는 가장 간단한 방법은 프로젝트 셋팅에서 해당 옵션을 끄는 것입니다. Project Setting를 열고 Physics 메뉴를 선택 후 Queries Hit Trigger의 체크를 해제합니다.
이제 기본적으로 Raycast는 모든 트리거 충돌을 무시하게 됩니다. 그리고 이 옵션을 끈 상태에서도 Raycast 함수의 QueryTriggerInteraction 파라미터를 이용해 전역 설정을 덮어 쓸 수 있습니다.
Raycast를 이용하여 여러 물체를 맞추는 방법
Raycast 함수는 단일 객체에 충돌이 발생하면 true를 리턴하고 멈춥니다. 하지만 때때로 레이저가 여러 물체를 관통하는 것과 같이 동일 Ray를 사용하여 여러 객체에 대한 충돌을 검사해야 하는 때가 있습니다. 이런 경우 단일 객체에 대한 레이캐스팅을 진행하는 Raycast 함수 대신 RaycastAll을 사용할 수 있습니다.
RaycastAll 함수 사용법
RaycastAll 함수는 기본적으로 Raycast 함수와 매우 비슷하게 동작합니다. 단 Raycast 함수에서 단 하나의 객체에 대한 충돌 정보만 반환하는 대신 RaycastHit 구조체 배열을 이용해 여러 개체에 대한 충돌 정보들을 반환합니다.
RaycastAll은 단일 Ray를 사용하여 충돌한 여러 객체에 대한 정보를 얻는 데 사용됩니다. 예를 들어 아래와 같이 Ray가 충돌한 객체들의 개수를 알아 낼 수 있습니다.
또는 Ray의 경로에 있던 모든 객체들을 파괴하는데 사용될 수도 있습니다.
RaycastAll의 문제
RaycastAll은 Ray에 충돌하는 모든 객체에 대한 정보를 얻어 오는데 유용하게 사용 될 수 있다. 하지만, RaycastAll은 여러 충돌체를 감지 할 수 있지만 정의되지 않은 순서로 검색한다는 문제가 있다. 결과값에 대해 동시에 처리를 한다면 이렇게 순서가 뒤섞이는 것이 문제가 되지는 않지만 순서가 중요한 경우가 있다면 거리에 따라 배열을 정렬하는 방법도 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
...
void FireLaser()
{
RaycastHit[] hits;
Ray ray = new Ray(transform.position, transform.forward);
hits = Physics.RaycastAll(ray);
// 거리에 따라 배열을 정렬하는 방법
System.Array.Sort(hits, (x, y) => x.distance.CompareTo(y.distance));
}
RaycastAll vs RaycastNonAlloc
RaycastNonAlloc은 RaycastAll과 매우 유사하게 동작한다. 다만, RaycastNonAlloc은 RaycastAll과 달리 호출 될 때마다 내부적으로 RaycastHit 배열을 생성하지 않고, 이미 생성된 배열을 파라미터로 전달하여 가비지 생성을 줄일 수 있다.
RaycastNonAlloc은 충돌한 객체의 개수를 리턴하지만 그 수는 인자로 넘겨진 배열의 길이보다 크지 않다. 실제 반환된 충돌된 객체의 개수를 알면 리턴된 배열이 가득 차지 않았을 때 빈 요소들을 참조하는 것을 방지할 수 있다.
RaycastHit[] results = new RaycastHit[10];
void Update()
{
if (Input.GetMouseButtonDown(0))
{
FireLaser();
}
}
void FireLaser()
{
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
int hits = Physics.RaycastNonAlloc(ray, results);
for (int i = 0; i < hits; i++)
{
Destroy(results[i].transform.gameObject);
}
}
일반적으로 RaycastNonAlloc은 RaycastAll보다 효율적인 버전이다. 하지만 RaycastAll과 마찬가지로 RaycastNonAlloc 역시 정의되지 않은 순서로 충돌 정보 배열을 리턴한다. 이때문에 RaycastNonAlloc으로부터 리턴되는 충돌 객체의 정보가 배열의 길이에 제한되어 일부 정보가 손실될 수 있다. 이 배열은 정의되지 않은 순서로 저장되기 때문에 리턴된 충돌 정보들이 시작점으로부터 가까운 객체들이라는 보장이 없다.
예를 들어 최대 3명의 적을 관통하는 무기를 만들려는 경우, 결과를 받아올 RaycastHit 배열의 크기가 3인 경우, RaycastNonAlloc을 사용하면 3개의 결과가 반환 되긴하지만 이 결과를 정렬하더라도 가장 가까운 3개가 될것이라는 보장을 하지 못한다.
RaycastNonAlloc이 성능을 향상시킬 수 있는 좋은 방법이지만, 이를 효과적으로 사용하기 위해서는 LayerMask와 같은 기능들을 사용하여 raycasting 결과로 리턴되는 개수가 한정적일 때 최대 길이의 배열을 사용하여 반복적인 배열을 재할당 없이 사용하는 것이 좋다.
'Unity' 카테고리의 다른 글
Unity Animator의 Any State 기능이란? (0) | 2023.06.29 |
---|---|
Unity : LayerMask (0) | 2023.06.23 |
Unity의 IL2CPP란? (0) | 2023.06.21 |
Unity의 OnApplicationQuit, OnDisable, OnDestroy 이벤트 함수의 차이점 (0) | 2023.05.20 |
C# Action과 UnityEvent의 차이점 (2) | 2023.05.11 |