[Unity] 레이어와 비트 연산 (LayerMask)

Donghee·2024년 5월 16일
0

개요

레이어는 충돌 선별, 카메라 렌더링, 레이캐스팅 선별 등 효과적인 성능을 위해 사용되는 오브젝트의 분류다. 레이어는 다음과 같이 0번부터 31번까지 정할 수 있으며, 총 32개이다.

레이어를 쓰는 용도는 여러가지지만, 제일 접하기 쉬운 길로는 레이캐스팅에서의 활용이 있다.

int layerMask = 1 << 8;

if (Physics.Raycast(transform.position, Vector3.forward, Mathf.Infinity, layerMask))
{
    Debug.Log("The ray hit the player");
}

위는 8번째 레이어에 속해 있는 오브젝트들만 감지하는 Raycast 코드다. 여기서 특이한 점이 있다.

int layerMask = 1 << 8;

왜 위와 같은 코드를 쓰는 것일까? '<<' 는 비트연산자라는데, 왜 레이어를 지정해주는데 이런 생소한 연산자를 사용하는 것일까?

레이어가 32개인 이유

우선 레이어의 개수가 32개인 이유부터 알아보아야 한다. Physics.Raycast에 넣는 layerMask는 int형인데, 왜 레이어는 32개밖에 되지 않을까?
이유는 유니티가 LayerMask를 받아들이는 방법에서 찾을 수 있다.

레이어마스크(LayerMask)

유니티는 코드에서 레이어를 파악하기 위한 방법으로 레이어마스크라는 방법을 사용한다. 이는 여러가지의 레이어 상태를 동시에 파악하기 위한 방법이다. 우리가 레이어에 대해 어떤 동작을 수행할 때는, '레이어를 포함시킬거냐, 아니냐'의 두 가지 선택지만 존재한다. 이를 우리는 0과 1로 생각할 수 있다. 포함시키는 상태를 1으로, 포함시키지 않는 상태를 0으로 둔다.
여기서 레이어가 32개인 점에 주목해보자. 우리는 0과 1로, 즉 True와 False로 상태를 구분해야할 레이어가 총 32개인 것이다. 결국 True/False를 32개 조합할 수 있다면, 모든 레이어에 대해서 상태를 구분지을 수 있다.

int 형

우리에게는 0과 1을 32개 조합하는 가장 익숙한 방법이 있다. int 형이 바로 그것이다.

int num = 20;

위 코드는 int형 변수인 num을 20으로 초기화시킨다. 이때 num에는 '20'이라는 숫자로 저장되지 않는다. 00000000 00000000 00000000 000010100 라는 2진수로 저장이 되어있다. 이는 일반적인 숫자를 비트 단위로 본 것이다. 그럼 이 비트 단위의 관점을 빌려 레이어마스크를 바라보자.

비트 마스크와 비트 플래그

레이어마스크는 비트 단위를 통해 32개의 True/False를 구분짓는다. 8번째 레이어만 포함시키는 레이어마스크라면, 나머지 비트는 모두 0이라 하고 8번째 비트만 1이라고 해주면 되는 것이다.

8번째 레이어만 포함하는 레이어마스크: 00000000 00000000 00000000 10000000

이런 식으로 비트 하나하나에 True/False를 새겨주는 방식을 비트 플래그(Bit Flag)라고 한다. 비트 하나하나가 마치 깃발처럼 올렸다(1)/내렸다(0) 하는 방식으로 여러 상태를 구분할 수 있게 해주어 이런 이름이 붙었다고 한다.
비트 플래그처럼 비트를 자료구조로 이용하는 기법을 비트 마스크(Bit Mask)라고 하고, 레이어마스크도 이 비트 마스크라는 명칭에서 따온듯 하다. 이를 통해 32개의 모든 레이어의 포함 여부를 알 수 있고, 동시에 여러 레이어가 포함되는 상황도 쉽게 전달할 수 있다.

비트 연산

지금까지 한 내용을 정리해보자.
1. 유니티 스크립트에서 레이어의 포함 여부는 레이어마스크로 표현된다.
2. 레이어마스크는 비트 플래그 기법으로, 32개의 레이어들의 포함 여부를 나타낸다.
3. 레이어마스크는 각 레이어 순서에 해당하는 비트를 0/1로 조절하며 하나의 int 형으로 표현된다.

지금까지는 레이어마스크가 어떤 기법을 사용하고, 이 기법의 특징에 대해서 알아봤다.
그렇다면 레이어마스크를 우리 입맛대로 조절하는 방법을 알아보자. 우리는 맨 처음에서 <<라는 연산자를 보았다. 이는 비트 연산자로, 평범한 숫자 연산 대신 비트 하나하나에 관해 연산을 수행한다.
<<라는 연산자는 왼쪽 시프트라고도 불리며, 왼쪽 피연산자에 해당하는 숫자를 오른쪽 피연산자만큼 미는(Shift) 연산을 뜻한다. 말로만 들으면 난해하니 코드를 봐보자.

int layerMask = 1 << 8; // 8번 레이어만 포함하는 상태

여기서 1 << 8은, 1이라는 숫자를 8번 왼쪽으로 미는 비트 연산이다.
int 형에서 1은, 00000000 00000000 00000000 00000001로 표현이 된다.
이 비트를 8번 왼쪽으로 밀어보면, 00000000 00000000 00000001 00000000이 된다. 위에서 살펴봤던 레이어마스크 방식으로 이를 해석하면, 나머지 레이어는 모두 포함시키지 않고 8번 레이어만 포함하는 상태가 되는 것이다.
즉, layerMask에는 '8번 레이어만 포함하는 상태'가 저장되어 있다.

다른 비트 연산자

<<말고도 >>, |, & ^, ~ 같은 여러 비트 연산자들이 있다. 여기서 | 연산자, OR 연산자에 대해 좀 더 알아보자.
이는 논리 연산자 &&와 비슷한 역할로, 똑같은 순서의 비트가 하나라도 1이면 1, 둘다 0이면 0을 반환한다. 이 비트 연산자를 통해 우리는 여러 레이어가 포함되는 상태를 쉽게 만들어낼 수 있다.

int layerMask = 1 << 8 | 1 << 10; // 8번 레이어와 10번 레이어만 포함하는 상태;

위 연산을 살펴보자.
1 << 8은 00000000 00000000 00000001 00000000이고,
1 << 10은 00000000 00000000 00000100 00000000이다.
이 두 이진수 사이에 OR 연산을 수행하면, 00000000 00000000 00000101 00000000이 된다. 즉, 8번과 10번 레이어만 포함하는 상태인 레이어마스크가 되는 것이다.

만약 8번 레이어만 제외하고 나머지를 다 포함시키고 싶다면 NOT 연산자인 ~을 활용하면 된다.

int layerMask = ~(1 << 8); // 8번 레이어만 포함하지 않는 상태;

~(1 << 8)은 11111111 11111111 11111110 11111111이다. 이는 8번 레이어만 포함시키지 않고 나머지 레이어는 모두 포함하는 상태를 말한다. 여러 비트 연산자를 활용해 원하는 상태의 레이어마스크를 쉽게 만들어낼 수 있다.

참고

profile
마포고개발짱

0개의 댓글