Raycast | 정확한 물리처리에 사용

해당 게시물은 유니티에서 레이캐스트를 어떤 식으로 활용할 수 있는지 정리한 게시물입니다.

레이캐스트는 정확한 물리처리를 요구할 때 사용합니다. 2D 플랫포머 게임에서 바닥을 체크할 때, FPS 게임에서의 총알 데미지, 등등 활용되는 방식은 여러가지 입니다. 지금부터 어떻게 사용되는지 간단히 알아보겠습니다.

레이캐스트 간단 설명


어떤 위치에서 광선을 발사하여 그 광선에 닿는 물체가 있는지 검사하는 방식. RaycastHit 을 사용하여 여러가지 값도 쉽게 가져올 수 있어서 많이 사용하는 방법입니다.

유니티 스크립팅 API


https://docs.unity3d.com/kr/530/ScriptReference/Physics.Raycast.html

Physics.Raycast(vector3 시작점,vector3 방향,float 최대거리,layerMask 레이어마스크);

보통 레이캐스트는 위에서 처럼 사용됩니다. 하지만 유니티 스크립팅을 보면 훨씬 다양하게 사용할 수 있으니 한 번은 읽어보는 게 좋습니다.

눈앞의 사물을 감지하고 옮길 수 있는 기능을 만들어보자


지금부터 설명할 방법은 1인칭 시점 3D 캐릭터 기준으로 설명합니다. 1인칭 시점 캐릭터는 메인 카메라를 가지고 있어 직접 움직이면서 게임 세상을 볼 수 있는 특징을 가지고 있습니다.

캐릭터가 가지고 있는 메인 카메라를 활용하여 직접 움직이면서 Raycast 로 물건을 옮기는 기능을 만들어 보겠습니다.

1. Raycast 를 통해 광선을 쏘고 게임 오브젝트를 감지하자


// 카메라를 받을 변수
private Camera playerCam;
// 광선거리
public float distance = 100f;

void Start()
{
    // 스타트 함수를 통해 카메라를 받습니다
    playerCam = Camera.main;
}

void Update()
{
    // 본문 설명 참조
    Vector3 rayOrigin = playerCam.
            ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0));
    Vector3 rayDir = playerCam.transform.forward;
    
    // Scene 창에서만 보이는 선을 발사합니다
    // 레이케스트 광선 범위 확인용입니다
    Debug.DrawRay(rayOrigin, rayDir*distance, Color.red);
    
    // 마우스 왼쪽 버튼을 통해 레이캐스트 발동
    if(Input.GetMouseButtonDown(0))
    {
        if(Physics.Raycast(rayOrigin, rayDir, distance))
        {
            Debug.Log("광선에 맞았다!!");
        }
    }
}

스크립트를 만들어서 코드를 작성합시다. 위 코드는 메인 카메라를 통해 광선을 발사하여 게임 오브젝트를 감지하는 코드입니다.

위 코드에서 눈여겨 봐야하는 부분은 playerCam.ViewportToWorldPoint 입니다.

Raycast는 광선이 발사되는 원점 Vector3 좌표를 요구합니다. 위 코드는 메인 카메라가 Raycast를 발사되게 작성되었고 광선이 발사되는 위치는 메인 카메라의 좌표가 됩니다.

ViewportToWorldPoint 는 유니티 카메라의 내장된 함수인데 카메라의 위치를 찍어줍니다. Vector3(0.5f, 0.5f, 0) 은 화면 정중앙을 의미합니다.

위 스크립트를 가지고 있는 1인칭 시점의 캐릭터는 정상적으로 Raycast를 통해 오브젝트를 인식(물리처리)할 것입니다. 하지만 문제가 있습니다. 원하는 오브젝트만 물리처리 하는 것이 아니라 모든 오브젝트를 물리처리하죠.

2. 레이어를 활용하여 원하는 오브젝트만 Raycast 에 처리되게 하자


유니티의 게임 오브젝트들은 인스펙터 창에서 Layer(레이어) 설정을 할 수 있습니다.

Raycast 에 물리처리되게 하고싶은 게임 오브젝트들을 적당한 레이어를 선택해 주시면 됩니다. 그리고 당연히 스크립트를 수정해야 합니다.

private Camera playerCam;

public float distance = 100f;
// 레이어 선택을 위한 변수
public LayerMask whatIsTarget;

void Start()
{
    playerCam = Camera.main;
}

void Update()
{
    Vector3 rayOrigin = playerCam.
            ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0));
    Vector3 rayDir = playerCam.transform.forward;
    
    Debug.DrawRay(rayOrigin, rayDir*distance, Color.red);
    
    if(Input.GetMouseButtonDown(0))
    {
        // LayerMask 변수인 whatIsTarget 을 추가하자
        if(Physics.Raycast(rayOrigin, rayDir, distance, whatIsTarget))
        {
            Debug.Log("광선에 맞았다!!");
        }
    }
}

LayerMask 타입의 변수는 현재 생성되있는 레이어를 인스펙터에서 선택할 수 있습니다. 물론 퍼블릭으로 선언해야 인스펙터에서 선택할 수 있겠죠.

저는 Raycast에 물리처리 되게 하고싶은 게임 오브젝트의 Layer를 위 사진과 같은 레이어로 설정했었습니다. 해당 레이어를 인스펙터에서 LayerMask 변수에 대입시키면 됩니다.

코드에서 레이캐스트 함수에 레이어마스크 파라미터를 추가했기 때문에 레이캐스트는 설정했었던 레이어에 포함돼있는 오브젝트만 물리처리하게 됩니다.

3. RaycastHit 을 통해 광선에 닿은 게임 오브젝트의 정보를 가져와 게임 오브젝트를 옮기자


private Camera playerCam;

public float distance = 100f;

public LayerMask whatIsTarget;

// 이동시킬 게임 오브젝트 트랜스폼 변수
private Transform moveTarget;

// 해당 오브젝트의 거리
private float targetDistance;

void Start()
{
    playerCam = Camera.main;
}

void Update()
{
    Vector3 rayOrigin = playerCam.
            ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0));
    Vector3 rayDir = playerCam.transform.forward;
    
    Debug.DrawRay(rayOrigin, rayDir*distance, Color.red);
    
    // 레이캐스트힛 변수 선언
    RaycastHit hit;
    
    if(Input.GetMouseButtonDown(0))
    {
        
        if(Physics.Raycast(rayOrigin, rayDir, out hit, distance, whatIsTarget))
        {
            // out 키워드는 입력으로 들어온 값이 내부에서
            // 어떠한 값이 생겨서 빠져나간다는 의미
            GameObject hitTarget = hit.collider.gameObject;
            
            Debug.Log("광선에 맞았다!!");
            
            // 레이캐스트힛 으로 받아온 오브젝트 대입
            moveTarget = hitTarget.transform;
            // 게임 오브젝트와의 거리
            targetDistance = hit.distance;
        }
    }
    
    // 마우스 버튼 때면 moveTarget 변수 비우기
    if(Input.GetMouseButtonUp(0))
    {
        moveTarget = null;
    }
    
    // moveTarget 에 값이 있을 시 오브젝트 이동
    if(moveTarget != null)
    {
        moveTarget.position = rayOrigin + rayDir * targetDistance;
    }
}

레이캐스트힛 으로 선언한 변수가 Update 함수안에 있습니다. Raycast 의 파라미터를 통해 RaycastHit 변수에 값을 대입시켰습니다.

RaycastHit 변수는 많은 정보를 담고 있습니다. Raycast 에 충돌처리된 오브젝트의 정보를 RaycastHit 으로 받아올 수 있는데 게임 오브젝트를 이동시키는 핵심이 RaycastHit 으로 받아온 게임 오브젝트의 값을 활용하는 것입니다.

그리고 Raycast 파라미터 중에 out 키워드가 있는데, out 키워드를 통해 RaycastHit 변수에 값이 대입되는 것입니다.