I'm FanJae.

[20260527] Unity 정리 ( Unity Raycast 응용 : LayerMask, Tag, Raycast를 이용한 상호작용 ) 본문

Unity/Unity 초격차캠프

[20260527] Unity 정리 ( Unity Raycast 응용 : LayerMask, Tag, Raycast를 이용한 상호작용 )

FanJae 2026. 5. 27. 16:59

1. Raycast 복습

- Raycast는 시작 위치에서 특정 방향으로 광선을 발사한다.

- 특정 방향으로 광선을 쏴서 가장 먼저 닿은 Collider를 검사하는 기능이다.

- 이미지로 본다면, 대충 이런 느낌. 특정 Object에 있는 Collider를 확인한다. 이때, Raycast 는 가장 먼저 맞은 대상 하나를 검사하지만, RaycastAll 은 Ray에 맞은 여러 대상을 배열로 가져온다.


2. Tag와 Layer의 차이

① Tag

- 오브젝트의 정체성을 구분한다.

Player, Enemy, Boss, Item

- 주로, 이 오브젝트가 무엇인지를 분류하기 위한 용도로 사용한다.

 

② Layer

- 오브젝트의 물리/렌더링/검사 대상으로 어떻게 처리할지를 구분한다.

- Raycast 필터링, 충돌 제어, 카메라 렌더링 분리에 사용한다.

- Unity LayerMask는 Raycast에서 사용할 Layer 집합을 지정할 때 사용한다.


3. LayerMask

[SerializeField] private LayerMask targetLayer;

Physics.Raycast(transform.position, transform.forward, out hit, rayDistance, targetLayer)

- 위와 같은 방식으로 사용하는데, targetLayer 에 포함된 Layer의 Collider만 Raycast 검사 대상으로 삼아 사용한다.

① LayerMask의 번호를 지정할 때 유의할 점.

Physics.Raycast(origin, dir, out hit, distance, 9);

- 이때 숫자 9는 Layer 번호 9를 검사한다는 뜻이 아니다. 정수 9를 비트 마스크로 해석한다. Unity 공식 문서에서 이를 확인할 수 있다.

- 링크 : https://docs.unity3d.com/6000.3/Documentation/Manual/layermask-introduction.html

- 간단하게 해석하면, 숫자 9를 2진수로 해석하여 00001001로 인식한다. 이 경우, Layer 0과 Layer 3을 검사한다고 볼 수 있다.


4. 여러 대상 검사 ( RaycastAll, RaycastNonAlloc )

① RaycastAll

- Raycast는 가장 먼저 맞은 대상 하나만 검사하지만, RaycastAll은 Ray에 맞은 여러 대상을 배열로 가져온다.

② RaycastNonAlloc

- RaycastNonAlloc은 RaycastAll과 비슷하게 여러 대상을 검사하지만, 결과를 새 배열로 반환하지 않고 미리 만들어둔 배열에 저장한다.

- 이를 이용해서 반복적으로 호출되는 감지 로직에서는 GC 부담을 줄일 수 있다.

using UnityEngine;

public class RaycastAllExample : MonoBehaviour
{
    [SerializeField] private float rayDistance = 10f;
    [SerializeField] private LayerMask targetLayer;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            RaycastHit[] hits = Physics.RaycastAll(
                transform.position,
                transform.forward,
                rayDistance,
                targetLayer
            );

            foreach (RaycastHit hit in hits)
            {
                Debug.Log($"감지된 대상: {hit.collider.name}");
            }
        }
    }
}

using UnityEngine;

public class RaycastNonAllocExample : MonoBehaviour
{
    [SerializeField] private float rayDistance = 10f;
    [SerializeField] private LayerMask targetLayer;

    private RaycastHit[] hits = new RaycastHit[10];

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            int hitCount = Physics.RaycastNonAlloc(
                transform.position,
                transform.forward,
                hits,
                rayDistance,
                targetLayer
            );

            for (int i = 0; i < hitCount; i++)
            {
                Debug.Log($"감지된 대상: {hits[i].collider.name}");
            }
        }
    }
}

- 이처럼 RaycastAll은 Ray에 맞은 여러 대상을 배열로 반환한다. 사용은 쉽지만, 호출 시 배열 할당이 발생한다.

- RaycastNonAlloc은 미리 만들어둔 배열에 결과를 저장한다. 따라서, 반복 호출 시 GC 부담을 줄일 수 있다. 단, 배열 크기 만큼만 결과를 받아낸다.


5. Raycast의 활용 예시

① 총 발사

if (Physics.Raycast(cameraTransform.position,
                    cameraTransform.forward,
                    out hit,
                    shootDistance,
                    shootLayer))
{
    if (hit.collider.TryGetComponent<Enemy>(out Enemy enemy))
    {
        enemy.TakeDamage(damage);
    }
}

- FPS에서 총알을 실제 오브젝트로 날려서 충돌 판정을 보지 않고 처리할 수 있다.

- 카메라 쪽에서 Raycast를 쏴서 맞은 적에게 데미지를 주는 방식. 즉, 히트스캔 방식으로 구현할 수 있다.


② 상호 작용

// IInteractable.cs
public interface IInteractable
{
    void Interact();
}

- 상호작용이라는 것이 가능한 객체에 대해서 IInteractable()을 구현할 수 있도록 별도의 인터페이스로 나눠준다.

// Door.cs
public class Door : MonoBehaviour, IInteractable
{
    public void Interact()
    {
        // 문 열기/닫기
    }
}
// ItemObject.cs
public class ItemObject : MonoBehaviour, IInteractable
{
    [SerializeField] private string itemName = "체력포션";

    public void Interact()
    {
        Destroy(gameObject);
    }
}

- 이와 같이 Door와 ItemObject에 대해서 각자에게 맞는 Interact()을 구현한다.

if (Physics.Raycast(cameraTransform.position,
                    cameraTransform.forward,
                    out hit,
                    interactDistance,
                    interactLayer))
{
    if (Input.GetKeyDown(KeyCode.E))
    {
        if (hit.collider.TryGetComponent<IInteractable>(out IInteractable interactable))
        {
            interactable.Interact();
        }
    }
}

- 이와 같은 방식으로 구현하면, PlayerInteraction은 대상이 무엇인지는 알지 않아도 된다. (문인지 아이템인지 몰라도 되고 그냥 해당 객체가 IInteractable()을 구현한 오브젝트인지만 확인하면 된다.)


 

Comments