강의, 책/[Unity] C#과 유니티로 만드는 MMORPG 게임 개발 시리즈

Section 4. Collision - Raycasting, LayerMask

hye3193 2024. 1. 21. 00:20

Raycasting: 말 그대로 광선을 쏜다, 쏜 광선이 어딘가에 충돌하면 충돌한 물체에 대한 정보를 알 수 있음

 

Debug.DrawRay를 활용해 그려진 Ray의 경로를 확인할 수 있다

Debug.DrawRay(transform.position + Vector3.up, Vector3.forward * 10. Color.red);

이런 식으로 코드 작성이 가능하다

첫번째 인자: 출발점, 두번째 인자: 방향 및 길이, 세번째 인자: 색

전달하는 인자들의 구성은 여러 버전이 있으니 참고해서 작성하면 된다

 

Vector3 look = transform.TransformDirection(Vector3.forward);
RaycastHit hit;

if (Physics.Raycast(transform.position + Vector3.up, look, out hit, 10))
{
    Debug.Log($"Raycast {hit.collider.gameObject.name}!");
}

광선이 충돌한 오브젝트 하나만을 알 수 있는 코드는 위와 같다(반환값: bool)

Vector3인 변수 look은 TransformDirection 함수를 이용해 로컬 forward 값을 월드 기준으로 변환했고

Raycast의 인자의 경우, 첫번째 인자: 출발점, 두번째 인자: 방향(방향벡터), 세번째: 충돌한 객체에 대한 정보, 네번째: 최대 길이(없어도 됨)로 구성되어 있고, 이 역시 다양한 버전이 있으니 필요한 걸 찾아서 사용하면 된다

 

Vector3 look = transform.TransformDirection(Vector3.forward);

RaycastHit[] hits;
hits = Physics.RaycastAll(transform.position + Vector3.up, look, 10);

foreach (RaycastHit hit in hits)
{
    Debug.Log($"Raycast {hit.collider.gameObject.name}!");
}

광선이 충돌한 모든 오브젝트를 보려면 RaycastAll 함수를 사용하면 된다(반환값: RaycastHit 배열)

 

 

화면을 마우스로 클릭했을 때, 카메라에서 마우스Pos를 잇는 선을 연장시켜 어떠한 오브젝트에 충돌하는지를 감지할 수도 있다

if (Input.GetMouseButtonDown(0))
{
    Vector3 mousePos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane));
    Vector3 dir = mousePos - Camera.main.transform.position;
    dir = dir.normalized;
    
    Debug.DrawRay(Camera.main.transform.position, dir * 100.0f, Color.red, 1.0f); // 1.0f : 1초간 유지

    RaycastHit hit;
    if (Physics.Raycast(Camera.main.transform.position, dir, out hit, 100.0f)) // 100.0f까지만 광선이 나가도록
    {
        Debug.Log($"Raycast Camera @ {hit.collider.gameObject.name}");
    }
}

1. 마우스 위치를 screenToWorldPoint 함수를 이용해서 가지고 온다

* 카메라로부터 near 거리만큼 떨어진 위치를 기준으로 마우스 위치를 가져와야 하기 때문에 Z좌표에는 Camera.main.nearClipPlane(near값)을 넣어준다

2. 카메라의 위치에서 마우스까지의 방향을 구해주고, 이를 크기가 1인 방향 벡터로 만들어주기 위해 normalized해준다

3. 그리고 카메라 위치에서 시작, dir 방향으로 광선을 쏘아 hit 정보를 받아준다

+ Debug.DrawRay에서는 방향벡터의 크기에 따라 광선의 길이가 정해지므로 얼마나 뻗어나갈지를 곱해주어야 한다

 

위의 과정은 원리를 풀어쓴 것이고, Ray를 이용하여 간단하게 사용할 수 있는 코드는 아래와 같다

if (Input.GetMouseButtonDown(0))
{
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    
    Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
    
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f))
    {
        Debug.Log($"Raycast Camera @ {hit.collider.gameObject.name}");
    }
}

 

 

그런데 Raycasting은 부하를 많이 주는 작업이기 때문에, 부하를 줄이고 최적화하기 위해 특정한 객체들에만 연산을 적용시키기 위해 Layer을 사용할 수 있다

비트 플래그를 이용하여 특정 레이어를 가져올 수 있는데, 예를 들어 8번 레이어를 가져오기 위해서는 (1 << 8); 과 같이 비트를 왼쪽으로 8번 밀어주면 된다

if (Input.GetMouseButtonDown(0))
{
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    
    Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
    
    int mask = (1 << 8) | (1 << 9);
    
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f, mask)) // mask 인자 추가
    {
        Debug.Log($"Raycast Camera @ {hit.collider.gameObject.name}");
    }
}

위 코드에서는 8번째 비트와 9번째 비트가 각각 1이 되도록 한 비트를 | (or)을 이용해 연결해주었다

그럼 00000000000000000000001100000000 이런 int 32 비트가 나오게 된다

 

비트 플래그는 아래와 같이 계산기에서 확인할 수도 있다

 

LayerMask mask = LayerMask.GetMask("Monster") | LayerMask.GetMask("Wall");

아니면 이런 식으로 레이어명을 명시해서 layer 정보를 가져올 수도 있다

자동으로 int 형식으로 변경해주기 때문에 위에서 사용했던 코드의 mask 값을 이 코드로 대체해도 무방하다