레이어마스크와 비트연산

김정환·2024년 10월 14일
0

강의에서 나왔던 레이어마스크 개념과 비트연산을 다시 정리해보았다.

태그와 레이어

유니티에는 태그와 더불어 자주 사용하는 개념인 레이어가 있다.

태그 Tag

의미 그대로 오브젝트에 꼬리표(Tag)를 달 수 있다.
쓰임새는 대체로 오브젝트를 구분하는데 사용한다.

예시

  • 충돌 시, 부딪힌 오브젝트가 플레이어인지, 적인지 등을 구분하는 용도

레이어 Layer

계층, 층이라는 의미인 레이어는 태그와 비슷해 보이지만 사용 방법이 다르다.
레이어는 오브젝트의 계층을 분리해서 상호작용할 수 있다.
간단히 말하면 오브젝트에 레이어를 설정하여 원하는 레이어끼리만 상호작용하는데 쓰인다.

예시

  • 직관적인 예시는 이 이미지이다.

유니티 3D 충돌 설정에서 각 레이어가 다른 레이어에 대한 충돌 감지 여부를 결정하는 창이다.

Player 레이어는 Default, TransparentFX, Ignore Raycast, Water, UI, Player(자기자신)과 충돌 상호작용을 할 수 있다.
저 중에서 원하는 레이어를 체크 해제하여 충돌시키지 않을 수 있다.

레이어마스크 LayerMask

위처럼 세팅 단에서 충돌 여부 감지를 설정할 수 있다.
하지만, 이 방식은 정적 방식이라 런타임 중에 레이어 감지를 변경할 수 없다는 단점이 있다.

레이어 감지 여부를 동적으로 할 수 있도록 하는 것이 레이어마스크(LayerMask)다.

마스크 Mask

마스크(Mask)라는 의미는 대체로 특정 부분은 가려서 안보이게 하는 것 정도로 생각할 수 있다.
포토샵, 일러스트 등에서 사용하는 마스크와 같이 1(흰색)은 감지하고 0(검정)은 무시한다.

레이어마스크 구조

유니티에서 레이어 총 32개까지 지원한다.
이는 비트로 표현했을 때 32개가 나온 것인데, 자료 사이즈로 보면 32 bit로 지원하고 있는 것이다.
비트 표현의 예시) 대략 이런 느낌

1 = 0000 0000 0000 0000 0000 0000 0000 0001
2 = 0000 0000 0000 0000 0000 0000 0000 0010
3 = 0000 0000 0000 0000 0000 0000 0000 0011
...
  • 레이어의 구성이 기본적으로 비트 형식이다 보니
    레이어마스크 계산 등 활용 시 직관적인 수체계를 사용할 수 없다.
  • 레이어는 비트 필드로 표현되어 각 비트가 다른 레이어를 나타낸다.
1번 레이어 (1) = 0000 0000 0000 0000 0000 0000 0000 0001
2번 레이어 (2) = 0000 0000 0000 0000 0000 0000 0000 0010
3번 레이어 (4) = 0000 0000 0000 0000 0000 0000 0000 0100
4번 레이어 (8) = 0000 0000 0000 0000 0000 0000 0000 1000
...
  • 이렇게 비트를 이용한 마스크로 주어진 레이어를 걸러내는 역할을 수행한다.

비트연산자

비트연산는 직관적인 수체계가 아니라 자주 사용하지 않던 방법이다.
(비트 자료의 표시는 읽기 편하게 임의로 작성했다.)
그래서 지금 다시 정리해볼 필요가 있다.

단항

  • NOT(~) : 모든 비트를 반전. == * -1
x = 1001;
y = ~x;		// 0110

이진

  • 왼쪽 시프트(n << m) : n을 왼쪽으로 m만큼 이동 (새로운 칸은 0으로 채움)
x = 1001;
y = x << 3;	// 1000
  • 오른쪽 시프트 (n >> m) : n을 오른쪽으로 m만큼 이동 (새로운 칸은 0으로 채움)
x = 1001;
y = x >> 2;	// 0010

논리

  • AND(&) : 두 비트 필드 모두에게 해당 비트가 설정되어 있을 때 1 (True)
x = 1111 1000; 
y = 1001 1101;
z = x & y; // 1001 1000
  • OR(|) : 두 비트 필드 중 하나라도 해당 비트가 설정되어 있을 때 1 (True)
x = 1111 1000; 
y = 1001 1101;
z = x | y; // 1111 1101
  • XOR(^) : 두 비트 필드에서 해당 필드가 서로 다를 때만 1 (True)
x = 1111 1000; 
y = 0001 1100;
z = x ^ y; // 1110 0100

레이어마스크 적용

[SerializeField] LayerMask layerMask; 	// 인스펙터에서 Player 레이어로 설정
[SerializeField] GameObject go;			// 레이어를 Player로 설정

public void TestDebug()
{
    Debug.Log($"go.layer : {go.layer}");                    // 6
    Debug.Log($"layerMask.value : {layerMask.value}");      // 64
    
    Debug.Log(layerMask.value == go.layer);                 // False
    
    Debug.Log(IsMatched(layerMask.value, go.layer));        // True
    Debug.Log(LayerMask.NameToLayer("Player") == go.layer); // True
}

bool IsMatched(int val, int layer)
{
    return val == (val | 1 << layer);
}

코드를 짜서 어떤 차이가 있는지 비교해보았다.

  • go.layer : 게임오브젝트에 지정된 값으로 정수형 6을 반환했다.
  • layerMask.value : 비트 값이며 정수형으로는 64를 반환했다.
    • 비트 표현 시 : 0000 0000 0000 0000 0000 0000 0100 0000(2)
  • layerMask.value == go.layer : 서로 수체계가 달라 False 가 나왔다.
  • IsMatched(layerMask.value, go.layer) : 강의에 나온 방법이다.
    • IsMatched 함수 계산 과정을 비트로 표현
 val = 0000 0000 0000 0000 0000 0000 0100 0000
 layer = 6
 
 1 = 0000 0000 0000 0000 0000 0000 0000 0001
 1 << layer(6) = 0000 0000 0000 0000 0000 0000 0100 0000
 
 val | 1 << layer =
 0000 0000 0000 0000 0000 0000 0100 0000
 0000 0000 0000 0000 0000 0000 0100 0000
 | 연산 : 0000 0000 0000 0000 0000 0000 0100 0000
 
 val == val | 1 << layer = True
 
 LayerMask.NameToLayer("Player") == go.layer
 이 메서드는 매개변수로 입력한 문자열의 레이어를 직관적인 수체계로 반환한다.
 
 LayerMask.NameToLayer("Player") = 6
 go.layer = 6
 
 LayerMask.NameToLayer("Player") == go.layer = True
profile
사파 개발자

1개의 댓글

comment-user-thumbnail
2024년 11월 10일

35번정도 더 읽으러 오겠습니다.

답글 달기