4 분 소요

1) Ray 활용기초

  • Physics.Raycast는 직선을 씬에 투영하여 대상에 적중되면 true를 리턴하는 물리 함수다.
  • Ray 변수
  • RaycastHit 변수
  • Raycast 함수
  • RaycastAll 함수
  • RaycastNonAlloc 함수

2) Ray 구조체 사용법

  • Ray는 직선의 시작점(origin)과 방향(direction)을 가지고 있는 단순한 구조체다.
  • 시작점(origin)은 Vector3 타입의 월드 포지션이며 방향(direction)은 직선의 방향을 나타낼 Vector3 타입의 법선 벡터다.
  • Ray는 사용할 때 마다 업데이트 되어야 한다.
    • Ray의 시작점과 방향이 매 프레임마다 달라지는 경우 Ray도 매 프레임 마다 갱신되어야 한다.
private void update()
{
    // Creates a Ray from this object, moving forward
    Ray objectForwardRay = new Ray(transform.position, transform.forward);

    // Creates a Ray from the center of the viewport
    // 아래에서 0.5f 값은 뷰포트의 중간값을 나타낸다.
    Ray cameraCenterRay = Camera.main.ViewportPointToRay(new Vector3 (0.5f, 0.5f, 0));

    // Creates a Ray from the mouse position
    Ray mousePointRay = Camera.main.ScreenPointToRay(Input.mousePosition);
}

3) RaycastHit 구조체 사용법

  • RaycastHit은 객체와 Ray의 충돌에 대한 결과 정보를 저장하는 구조체다.
  • Raycast 함수의 out 파라미터로 사용된다.
  • 월드에서 Raycasting의 Hit가 발생한 위치, Ray가 충돌한 물체, Ray의 원점에서 얼마나 떨어져있는지 등의 정보를 저장하여 돌려준다.
private void update()
{
    // Container for hit data
    RaycastHit hitData;

    // Gets hit position
    Vector3 hitPosition = hitData.point;

    // Gets 원점에서 충돌 지점까지의 거리
    float hitDistance = hitData.distance;

    // Reads the Collider tag
    string tag = hitData.collider.tag;

    // Gets a Game Object reference from its Transform
    GameObject hitObject = hitData.transform.gameObject;
}

4) Raycast 함수 사용법

  • Ray가 씬의 다른 객체와 충돌하는지 여부를 알 수 있으며 충돌할 경우 충돌 정보를 RaycastHit 변수에 저장할 수 있다.
  • Physics.Raycast 함수의 리턴 타입은 bool이다.
    • Ray에 어떠한 오브젝트라도 걸리면 true를 리턴한다.
  • 추가 디폴트 인자로 ray의 충돌 탐지 거리 제한, 특정 레이어 또는 트리거 콜라이더 무시하기 등의 제약사항을 추가할 수 있다.
    • 이러한 세팅들은 어떤 오버로드 된 레이케스트 함수를 사용하느냐에 따라 달라진다.
void Update()
{
    Ray ray = new Ray(transform.position, transform.forward);
    RaycastHit hitData;

    if (Physics.Raycast(ray, out hitData))
    {
        // The Ray hit something!
    }
}
  • 추가 디폴트 인자 사용
// Raycast할 Layer 선택
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 the a certain Layer
        // But it wasn't a Trigger Collider
    }
}

LayerMask를 사용할 때 레이어 번호를 직접 입력하는 방법

  • 우리는 때로 Layer Mask를 동적으로 지정해야할 때도 있다.
  • 유니티에서는 총 32개의 Layer를 지원하며 각 Layer를 구분하기 위해 32bit Bit Mask를 사용한다.

image

  • Layer는 0부터 시작하며 31이 마지막 Layer번호다.

image

  • 9번 레이어는 오른쪽에서 부터 0을 포함해 10번째 이므로 아래와 같은 비트 마스크를 만들어야 한다.

image

  • 만일 9번 레이어를 선택하기 위해 9를 넘기게 되면 결과적으로 아래 그림과 같이 마스킹 되어 0번, 3번 레이어가 선택 되게 된다.

image

  • 9번 레이어를 선택하기 위해서는 9가 아닌 512를 넘겨 주어야 한다.

image

  • 만일 9번과 4번 레이어를 동시에 선택하고 싶다면 아래와 같이 528을 넘겨야 한다.

image

if (Physics.Raycast(ray, 10, 528))
{
    // Layer 9 or 4 was hit!
}

정수를 LayerMask 값으로 변환하는 방법

  • 레이어를 번호로 선택할때는 쉬프트 연산을 사용하는 것이 효과적이다.
  • 9번 레이어만 선택하여 Raycast 체크하기
void Update()
{
    Ray ray = new Ray(transform.position, transform.forward);
    
    if (Physics.Raycast(ray, 10, 1<<9))
    {
        // Layer 9 was hit!
    }
}
  • 9번 레이어만 제외하고 모든 레이어 Raycast 체크하기
void Update()
{
    Ray ray = new Ray(transform.position, transform.forward);

    if (Physics.Raycast(ray, 10, ~(1<<9)))
    {
        Debug.Log("something else was hit");
    }
}

레이어 이름과 레이어 번호의 상호작용

  • LayerMask.NameToLayer(string layerName) : 레이어의 문자열 이름을 입력으로 레이어 번호를 반환한다.
  • LayerMask.LayerToName(int layerNumber) : 레이어의 번호를 입력으로 레이어 문자열 이름을 반환한다.
int layerNum = LayerMask.NameToLayer("UI");
Debug.Log(layerNum); // 5
  • 쉬프트 연산자를 사용해야 한다는 것을 주의하자
Ray ray = new Ray(transform.position, transform.forward);
int layerNum = LayerMask.NameToLayer("UI");

if (Physics.Raycast(ray, 10, 1<<layerNum))
{
    Debug.Log("something else was hit");
}

trigger collider를 무시하는 법

  • QueryTriggerInteraction 파라미터는 아래 세 가지 중 하나의 값을 가질 수 있다.
    • Ignore - 트리거 콜라이더의 충돌을 무시한다.
    • Collider - 트리거 콜라이더의 충돌을 허용한다.
    • UseGlobal - Physics 옵션에 정의된 기본 값을 따른다

5) RaycastAll 함수 사용법

  • Raycast 함수에서 단 하나의 객체에 대한 충돌 정보만 반환하는 대신 RaycastHit 구조체 배열을 이용해 여러 개체에 대한 충돌 정보들을 반환한다.
public RaycastHit[] hits;

void Update()
{
    Ray ray = new Ray(transform.position, transform.forward);
    hits = Physics.RaycastAll(ray);
}
  • Ray의 경로에 있던 모든 객체들을 파괴하는데 사용 될 수도 있다.
public class CameraRay : MonoBehaviour
{
    public RaycastHit[] hits;

    void Update()
    {
       if(Input.GetMouseButtonDown(0))
        {
            FireLaser();
        }
    }

    void FireLaser()
    {
        Ray ray = new Ray(transform.position, transform.forward);
        hits = Physics.RaycastAll(ray);

        foreach(RaycastHit obj in hits)
        {
            Destroy(obj.transform.gameObject);
        }
    }
}

RaycastAll의 문제

  • RaycastAll은 여러 충돌체를 감지 할 수는 잇지만 정의되지 않은 순서로 검색한다.
  • 물체를 통과한 레이저의 시작점과 가까이 있는 순서대로 배열에 들어갈것 같지만 실제 결과값은 예측 할 수 없는 순서로 저장된다.
  • 순서가 중요하다면 아래와 같이 거리에 따라 배열을 정렬하는 방법도 있다.
void FireLaser()
{
    RaycastHit[] hits;
    Ray ray = new Ray(transform.position, transform.forward);
    hits = Physics.RaycastAll(ray);

    // Sorts the Raycast results by distance
    Array.Sort(hits, (RaycastHit x, RaycastHit y) => x.distance.CompareTo(y.distance));
}

6) RaycastNonAlloc 함수 사용법

  • 외부에서 이미 생성된 배열을 out 파라메터로 재사용 할 수 있어 가비지(garbage)의 발생을 줄인다.
  • 충돌한 객체의 개수를 리턴하지만 그 수는 인자로 넘겨진 배열의 길이 보다는 크지 않다.
  • 실제 반환된 충돌된 객체의 개수를 알면 리턴된 배열이 가득 차지 않았을 때 빈 요소들을 참조하는 것을 방지할 수 있다.
  • RaycastNonAlloc도 정의되지 않은 순서로 충돌 정보 배열을 리턴한다.
void FireLaser()
{
    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);
        }
    }
}

업데이트: