비트 연산을 통해 Unity Layer에 대해 이해해보자

PenguinGod·2021년 12월 9일
3

Intro

저희는 Unity에서 자주 Layer를 사용합니다. 그 중 가장 대표적으로 쓰이는 것 중 하나가 Raycast가 특정 Layer를 가진 오브젝트만 감지하도록 설정하는 것이죠.

대표적으로 구글에 "untiy 특정 raycast만 포함" 이라고 검색하면 요런 문법이 뜹니다.

// raycast가 9번 레이어만 감지하게 하는 코드
int mask 1 << 9
if (Physics.Raycast(positions, forwards, out hitInfo, 200f, mask))

여기서 저희는 뭔지는 모르겠지만 << 라는 친구를 사용하고 Ray는 정말 착하게도 저희가 원했던 layer만 감지합니다.

근데 문제는 제가 성공했다는 기쁨에 취해 거기서 끝냈다는 거죠.

<< 라니 본 적도 없고 볼일도 없을 거 같은 친구입니다. 그래서 거기서 인연을 끝내버리죠.

하지만 안타깝게도 저 친구는 무려 컴퓨터의 근간을 이루는 이진법, 정확히는 비트와 관련된 친구였습니다.

Bit가 뭐야?

뭘까요. 일단 "드랍 더 비트" 할때 비트가 떠오르긴 하지만 진짜 아무리 생각해도 그건 아니라서 입 밖으로 꺼낼 수는 없습니다.

그럼 어떻게 저것의 정체를 알 수 있을까요? 간단합니다. 구글에 검색하면 나와요.

예. 구글신께서는 0과 1로 이루어진 데이터의 최소 단위라 말하고 계십니다.

여담을 붙이자면 주로 전기 신호의 유무를 나타나는데 쓰였다고 합니다. 0이면 전기 신호가 없고 1이면 있는 형식이라고 합니다.

그리고 저 친구가 8개가 모이면 1byte가 됩니다. 바이트가 1000개면 1킬로 바이트 킬로 바이트가 천개면 1메가 바이트 메가 바이트가 1000개면 1기가 바이트입니다.

갑자기 제가 지금까지 하찮게 보았던 1GB 대단해 보이는데요. 롤 용량이 10GB가 넘는 것을 보면 사실 하찮긴 한 것 같습니다.

그래서 Layer랑 무슨 상관인데?

우선 layer의 타입을 알아야겠죠. unity에서 자체적으로 구현해서 LayerMask라는 타입을 지원하긴 하지만 근본은 int입니다.

그리고 int는 4byte입니다.

위에서 1byte는 8bit라고 했고, 이는 0또는 1이 총 8개 존재한다는 뜻입니다.

그러면 4바이트라면? 32개 존재할 수 있겠죠.

근데 놀랍게도 layer를 만들 수 있는 최대 개수도 32개 입니다.

"오 대단해 그런 비밀이 있었다니!!" 라고 생각하실 수도 있지만 이렇게 생각하시는 분들이 계실 겁니다. "int는 32까지가 아니라 거의 20억까지 지원하는데 왠 32개?" 라는, 정말 멋지고 합리적인 의문이죠.

원래 int 형식은 약 -21억 ~ 21억 까지 표현이 가능합니다.

하지만 layer에서는 42억개의 정수를 표현하는데 집중하지 않습니다. 대신, 32개의 상태가 있는데 집중합니다.

32개의 0과 1을 이용해 0이면 감지하지 않고 1인 번호의 레이어만 감지하는 겁니다.

마치 크기가 32개인 bool 배열과 같죠

이러한 사용법을 bit flag라고 합니다. 플래그는 깃발에서 가져온 용어로 "깃발을 세우다" 같은 느낌으로 쓰입니다. 1이면 깃발을 세운거고 0이면 아닌거죠

한번 예시를 봅시다. 저희가 어떤 영화에서 웅장한 싸움을 앞둔 등장인물이 8명 있다고 가정해 봅시다.

근데 갑자기 3번 등장인물이 "돌아오면 그녀에게 꼭 고백하겠어" 라는 대사를 칩니다. 이는 일명 "사망 플래그"를 세운 것이죠.

마침 등장인물도 8명이겠다 이들의 사망 플래그를 1바이트의 크기를 가진 byte 변수로 표현하자면

// ob는 이진법 앞에 붙는 기호 
byte dieFlag = ob00000100; // 3번 등장인물의 사망 플래그가 켜졌기에 1로 표시

이처럼 등장인물 8명의 사망 플래그를 1바이트로 표현할 수 있습니다.

만약 이를 bool로 표시한다면

bool[] arr_DieFlag = 
     new bool[8] {true, false, true, false, false, false,false, false};

위처럼 됩니다.

코드가 길기는 하지만 index로 접근이 가능하기에 위처럼 선언하는 것이 더 효율적이라 생각하실 수 있습니다. 하지만 여기서 중요한 부분은 bool의 크기입니다.

변수의 최소 크기는 1바이트입니다. bit가 아니죠 즉 bool 값 8개를 만들면 8바이트가 됩니다. 1바이트의 크기로 8개의 상태를 표현할 수 있는 byte 타입과 비교되죠.

크기도 그렇지만 접근 자체도 무조건 편하다고 볼 수는 없습니다. 바이트로 접근할때와 배열로 접근할때를 비교해보죠.

int mask = 1 << 9;
arr_MaskFlag[9] = true;

저희가 << 의 정체를 몰라서 그렇지 나란히 놓고 보면 별 차이가 없습니다. 하지만 메모리는 int가 8배 이상 이득입니다.

자 그래서 layer가 사실은 int 형이고 비트 플래그를 사용하는 것도 알았고 왜 쓰는지도 알았습니다.

그럼 이제 "어떻게 쓰는가?" 이 부분이 중요하겠죠. 하지만 우리는 이미 구글링을 통해 어떻게 쓰는지는 알고 있습니다.

알고 싶은 건 "어떻게 동작하는가?" 이 부분이죠.

<< 의 비밀

지금까지의 서론이 굉장히 길었으니 결론부터 말하자면 << 라는 친구는 연산자입니다. 네 저희가 쓰는 +, -, /, * 말하는거 맞습니다.

대신 2글자를 앞에 붙여야 합니다. 그 2글자를 붙여서 "비트 연산자"라고 부릅니다.

비트 연산자는 재밌게 생긴 친구가 많습니다. 저희가 살펴본 << 말고도 >>, &, |, ^, ~ 가 있죠.

저걸 다 설명하지는 않을거고 우선 << 부터 살펴봅시다.

저 친구는 간단하게 말하자면 미는 겁니다. 어디로 미냐하면 화살표 방향대로 밉니다. 즉 오른쪽으로 밉니다.

int mask = 1 << 9;

저 코드를 설명해보도록 하죠.

우선 1은 ob00000000000000000000000000000001 입니다.

그리고 << 9 는 모든 비트를 9칸씩 미는 겁니다.

근데 어차피 1빼고 다 0이니 생각하기 편합니다. 1만 9칸 밀고 나머지는 싹 다 0으로 채우면 됩니다.

그러면 mask에 들어가는 값은 ob00000000000000000000001000000000 이 됩니다.

저 값을 Raycast의 인자값에 넘기면 "9번 레이어가 켜졌구만" 하게 됩니다.

저희가 위에서 "3번 등장인물은 죽겠구만" 하는 거랑 똑같은 논리구조입니다.

그리고, 여기서 만약 "10번째 bit가 켜진건데 왜 9번 레이이냐?" 라고 물으신다면.

요 사진으로 대답 가능합니다.

보시면 Layer는 0부터 시작하죠? 즉 정수 1은 layer 0번입니다. 거기서 9칸을 밀어서 9번 레이어를 나타내는 것이죠.

이렇게 비트 연산에 대한 사실을 알면 다른 문법도 설명 가능합니다.

int mask = 1 << LayerMask.NameToLayer(layerName);

LayerMask.NameToLayer(layerName) 부분은 레이어 이름을 받아서 몇 번 레이어인지 return하는 함수입니다.

만약 리턴값이 9라면 우리가 위에서 했던 코드와 완전히 똑같이 동작합니다.

사실 유니티에서 좀 더 편하게 사용할 수 있도록 구현해 주었기 때문에 저런거 없이 쓸 수도 있습니다.

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

역시 Wall이 9번이라면 똑같이 작동합니다.

부록

만약 1번 레이어와 3번 레이어를 동시에 감지하는 것처럼 2개 이상의 레이어를 키고 싶다면? 이때 | 를 사용합니다.

저 친구는 조건문에서 || (OR) 와 비슷합니다.

간단하게 봐보죠

byte a = ob00000010;
byte b = ob10000000;
byte c = a | b;
// c : ob10000010

코드에서 보시는 것처럼 2개의 바이트를 연산합니다. 이때 대응하는 비트 중 하나라도 1이 있다면 1을 반환합니다. a는 2번째가 1이고 b는 8번째가 1이기 때문에 c는 2번째와 8번째가 1인 ob1010이 되었습니다.

이것을 layer에 적용하면

int mask = 1 << 5 | 1 << 10

이러면 mask는 6번째와 11번째가 켜진 ob00000000000000000000010000100000 이 됩니다.

그리고 5번과 10번 레이어만 감지할 수 있습니다.

profile
수강신청 망친 새내기 개발자

4개의 댓글

comment-user-thumbnail
2022년 12월 21일

잘 봤습니다. 유니티로 게임 공부하는데 많은 도움이 되었습니다.

답글 달기
comment-user-thumbnail
2023년 10월 5일

너무 맛있게 적어주셔서 이해가 쏙쏙 됐습니다, 감사합니다!

답글 달기
comment-user-thumbnail
2024년 3월 20일

설명이 재밌고 코드 예시가 많아서 이해가 잘 됐어요! 감사합니다.

답글 달기

사망플래그 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 덕분에 재밌게 잘 읽고 갑니다 감사합니다

답글 달기