Game Engine/Unity

[Unity] 레이캐스트 (Physics.Raycast)

  • -

Unity에서 광선을 쏘아 충돌체를 감지할 수 있는 Physics.Raycast알아보고,

 

이를 디버깅할 때 사용할 수 있는 Draw.Debug에 대해 간단히 알아보자.

 

 

개념

시작 지점에서 특정 방향으로 씬의 모든 충돌체를 상대하는 광선을 투사한다.

 

※ 주의할 점: Raycast의 시작 지점과 충돌해있는 Collider은 감지되지 않는다.

 

 

매개변수

Physics.Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask);

 

origin에서 direction 방향으로 maxDistance 길이의 광선을 쏜다.

  해당 layerMask의 Collider만 충돌하며, 충돌체에 대한 정보를 hitInfo에 담는다.

 

maxDistance 생략 시, default 값으로 무한대의 거리로 지정

 

- layerMask 생략 시, default 값으로 defaultRaycastLayers로 지정

 

- hitInfo 생략 시, 충돌체에 대한 정보를 따로 저장하지 않음

 

 

Physics.Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);

 

- Vector3형의 시작점과 방향을 가지고 있는 구조체

 

- ex) Ray ray = new Ray(transform.position, transform.forward);

 

 

사용법

public class MyRaycast : MonoBehaviour
{
	const float length = 3f;
    
    void Update()
    {
    	if(Physics.Raycast(transform.position, transform.forward, length))
        	Debug.Log("Hit");
    }
}

 

해당 스크립트가 포함된 오브젝트의 위치에서 해당 오브젝트의 정면으로 3f 길이 만큼의 광선을 쏘는 코드이다.

 

광선과 Collider가 충돌했을 경우 Hit라는 메세지가 출력된다.

 

 

Hit라는 메세지는 나왔지만, 광선이 눈에 보이지 않아 디버깅을 할 때 불편하다.

 

그럴 때 사용하는 것이 Debug.DrawRay다.

 

 

Debug.DrawRay

1. 매개변수

Debug.DrawRay(Vector3 origin, Vector3 direction, Color color, float duration, bool depthTest);

 

origin에서 origin + direction 좌표까지 color 색으로 광선을 쏜다. duration 초 동안 광선이 유지되며,

  depthTest에 따라 광선이 오브젝트에 가리는 부분을 눈에 띄게 출력할지 말지 결정한다.

 

color 생략 시, default 값으로 white 색으로 지정

 

duration 생략 시, default 값으로 0으로 지정, 0은 1프레임동안 유지됨을 뜻함

 

depthTest 생략 시, default 값으로 true로 지정, true는 광선이 오브젝트에 가리는 부분을 옅게 출력함을 뜻함

 

Scene View 에서만 나타나며, 실제 효능은 없고 디버깅 용이다.

 

 

2. 사용법

public class MyRaycast : MonoBehaviour
{
    const float length = 3f;

    void Update()
    {
        if (Physics.Raycast(transform.position, transform.forward, length))
            Debug.Log("Hit");

        Debug.DrawRay(transform.position, transform.forward * length);
    }
}

 

아까 쓴 코드에서 DrawRay만 추가하였다. 꼭 Raycast와 같이 쓸 필요는 없다. 

 

해당 스크립트가 포함된 오브젝트의 위치에서 오브젝트의 정면으로 length 길이 만큼의 디버깅 광선을 쏘는 코드다.

 

 

Scene View에서만 보이는 하얀색 광선을 확인할 수 있다.

 

 

3. duration 실험

 

좌측은 duration을 따로 설정하지 않았을 때 (= duration이 0f일 때), 

 

우측은 duration을 1f로 설정했을 때다.

 

상황에 따라 다르겠지만, 움직이는 물체를 디버깅할 때에는 좌측이 더 편했다.

 

 

4. depthTest 실험

 

좌측은 depthTest를 true로 설정했을 때, 광선이 오브젝트에 가린 부분이 옅게 출력되어있다.

 

우측은 depthTest를 false로 설정했을 때, 광선이 가려진 부분 없이 모두 짙게 출력되어있다.

 

 

벡터 연산

Raycast와 DrawRay는 모두 시작점과 방향에 대한 벡터를 가지지만, 이는 앞서 말했듯이 다르게 적용된다.

 

Raycast는 origin에서 direction 방향으로 maxDistance 길이의 광선을 쏘는 것이고,

 

DrawRay는 origin에서 origin + direction 좌표까지 광선을 쏘는 것이다. 

 

이는 실제 광선과 디버그용 광선의 길이가 다르다는 것을 의미한다.

 

public class MyRaycast : MonoBehaviour
{
    const float length = 3f;

    void Update()
    {
        Vector3 rightForward = transform.forward + transform.right;

        if (Physics.Raycast(transform.position, rightForward, length))
            Debug.DrawRay(transform.position, rightForward * length, Color.red);
        else
            Debug.DrawRay(transform.position, rightForward * length, Color.green);
    }
}

 

앞서 작성했던 코드와 비슷하지만, tranform.forwad만 사용한 것이 아닌 transform.forward + transform.right를 사용했다.

 

 

DrawRay는 큐브에 충돌했지만, 빨간색으로 변하지 않았으므로 Raycast는 충돌하지 않았다.

 

즉, Raycast가 더 짧다는 것을 알 수 있다.

 

원점에서 축을 따라가는 1차원 광선을 쏠 때는 길이가 동일하여 신경쓰지 않아도 되지만,

 

위의 코드처럼 2차원이나 3차원으로 쏘아야 한다면, 계산의 과정을 한 번 더 거쳐야한다.

 

 

1. 1차원 벡터의 길이

(1,0,0) 방향의 길이가 3인 광선을 쏘는 경우, 

 

DrawRay의 경우 (3,0,0)을 인자로 넣기 때문에 길이는 √(32) 이므로 3이고,

 

Raycast는 길이를 실수로 넣기 때문에 3이므로 결과적으로 같다.

 

 

2. 2차원 벡터의 길이

(1,0,1) 방향의 길이가 3인 광선을 쏘는 경우,

 

DrawRay의 경우 (3,0,3)을 인자로 넣기 때문에 길이는 √(32 + 32 ) 이므로 약 4.24이고,

 

Raycast3이므로 길이가 다르다.

 

해결책은 다음과 같다.

 

 

3. magnitude

Vector3.magnitude

 

해당 벡터의 길이를 반환해주는 함수다.

 

다음과 같이 활용할 수 있다.

 

public class MyRaycast : MonoBehaviour
{
    const float length = 3f;

    void Update()
    {
        Vector3 rightForward = transform.forward + transform.right;
        float vectorLength = (rightForward * length).magnitude;

        Debug.DrawRay(transform.position, rightForward * length, Color.green);

        if (Physics.Raycast(transform.position, rightForward, vectorLength))
            Debug.DrawRay(transform.position, rightForward * length, Color.red);
    }
}

 

 

float vectorLength는 결과적으로 벡터 (3,0,3)의 길이를 저장하는 것이다.

 

이번 포스팅과는 연관 없지만, Vector3.sqrMagnitude 라는 해당 벡터의 제곱 길이를 반환하는 함수도 있는데,

 

유니티 공식 문서에 따르면 단순히 거리를 비교하기 위해서는 magnitude보다 sqrMagnitude의 계산 속도가 더 빠르다고 한다.

 

 

활용법 Ⅰ

Raycast의 매개변수 out Raycast hitInfo를 활용하는 법이다.

 

hitInfo를 통해 충돌한 Collider의 컴포넌트에 액세스할 수 있다.

 

public class MyRaycast : MonoBehaviour
{
    const float length = 3f;

    void Update()
    {
        Debug.DrawRay(transform.position, transform.forward * length, Color.green);

        RaycastHit hit;
        if (Physics.Raycast(transform.position, transform.forward, out hit, length))
            hit.collider.gameObject.SetActive(false);
    }
}

 

구조체인 RaycastHit의 객체인 hit를 선언해주고, Raycast의 인자에 out hit를 추가로 넣어준다.

 

해당 코드는 광선에 충돌한 모든 오브젝트를 비활성화 시켜준다.

 

실행 결과는 다음과 같다.

 

 

 

활용법 Ⅱ

Raycast의 매개변수 int layerMask를 활용하는 법이다.

 

설정된 layerMask와 같은 layer로 설정되어있는 오브젝트만 충돌하게끔 설정하는 것이다.

 

public class MyRaycast : MonoBehaviour
{
    [SerializeField] LayerMask layerRed;
    const float length = 3f;

    void Update()
    {
        Debug.DrawRay(transform.position, transform.forward * length, Color.green);

        RaycastHit hit;
        if (Physics.Raycast(transform.position, transform.forward, out hit, length, layerRed))
            hit.collider.gameObject.SetActive(false);
    }
}

 

Layer가 layerRed로 설정되어있는 오브젝트만 충돌 처리를 하는 코드다.

 

 

충돌 처리를 하고자 하는 오브젝트의 Layer을 바꿔준다.

 

 

Layer 클릭 후 Add Layer로 새로 생성할 수 있다.

 

필자는 Red라는 Layer를 만들었다.

 

 

My Raycast 스크립트가 부착되어 있는 오브젝트의 인스펙터 창에서 Layer를 고를 수 있게 되었다.

 

똑같이 Red를 골라준다.

 

실행 결과는 다음과 같다.

 

 

 

활용법 Ⅲ

Raycast는 기본적으로 충돌해야하는 오브젝트와 충돌 시, 통과하지 못하는 성질을 가지고 있다.

 

 

10f 길이의 광선을 쏘았지만, 맨 앞에 있는 오브젝트만 충돌하는 것을 알 수 있다.

 

고로 설정한 모든 충돌체를 한 번에 감지하려면 RaycastAll을 사용해야 한다.

 

RaycastAll(Vector3 origin, Vector3 direction, float maxDistance, int layerMask);

RaycastAll(Ray ray, float maxDistance, int layerMask);

 

Raycast와는 다르게 RaycastHIt[] 를 반환형으로 가지고 있다.

 

따라서 Raycast를 쓸 때와는 다르게 아래와 같이 사용해야 한다.

 

public class MyRaycast : MonoBehaviour
{
    [SerializeField] LayerMask layerRed;
    const float length = 10f;

    void Update()
    {
        Debug.DrawRay(transform.position, transform.forward * length, Color.green);

        RaycastHit[] hitsArray;
        hitsArray = Physics.RaycastAll(transform.position, transform.forward, length, layerRed);

        foreach(RaycastHit hit in hitsArray)
            Debug.Log(hit.collider.name);
    }
}

 

실행 결과는 다음과 같다.

 

 

Red Layer로 설정된 모든 오브젝트를 감지하였다.

 

하지만 광선을 쏘는 오브젝트와 가까운 순서대로 배열에 담기는 것은 아님을 알 수 있다.

 

 

개인 공부용 포스팅인 점을 참고하시고, 잘못된 부분이 있다면 댓글로 남겨주시면 감사드리겠습니다.
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.