유니티) 레이어마스크(LayerMask) 사용법, 2D 가장 가까운 적 탐색
흔히 오브젝트 간에 분류하기 위해 태그, 레이어를 쓰고는 합니다.
CompareTag() 함수나 .Tag 를 통해 태그를 비교하여 그 오브젝트가 적인지, 아이템인지 등을 구분하고는 하지요.
레이어는 보통 레이캐스트에서 구분을 위해 쓰고는 합니다.
빛을 쏘아 맞은 오브젝트에 대한 정보를 받아오는 것인데 특정 오브젝트들만 맞게끔 하고 싶다면 이 때 등장하는 것이 레이어마스크 입니다.
제일 아래의 Add Layer... 를 통해 원하는 레이어를 추가할 수 있습니다.
사용법은 3가지 방법으로 소개해 드리겠습니다.
되도록이면 1, 2번 방법을 쓰는 것을 권장하며
3번 방법은 게임 개발자가 굳이 여기까지 알아야 하나 싶은 내용까지 들어가 있기에
왜 이렇게 쓰는지 궁금증을 풀 분들만 보셔도 될 내용이니 참고하시길 바랍니다.
1. 인스펙터에서 사용할 레이어 지정하기
인스펙터에 나올 수 있도록 변수를 선언합니다.
인스펙터에서 원하는 레이어를 설정하기에 한 스크립트를 여러 곳에서 쓰며 각각 다른 레이어를 설정해줘야 할 때 편하게 쓸 수 있습니다.
항목을 우클릭 혹은 휠클릭 하면 여러 레이어를 동시에 지정할 수 있습니다.
다음부터는 아래의 예시 코드를 봅시다.
2D 게임에서 가장 가까운 적을 찾는 메서드입니다.
해당 게시글에서는 따로 설명을 드리지 않으나 주석을 다 달아놓았습니다.
해당 코드를 그대로 사용하기 위해서는 위의 Add Layer를 통해 Enemy 라는 이름의 레이어를 추가하고
찾고자 하는 적 오브젝트의 레이어를 Enemy로 변경해주어야 합니다
응용하여 적 뿐만이 아니라 가장 가까운 다른 무언가도 찾을 수 있겠지요.
// 반경 내 가장 가까운 적을 탐색하여 그 적의 트랜스폼을 반환
public Transform SearchClosestEnemy(float radius)
{
// 적들에게 부여한 레이어만을 포함한 레이어마스크
// 2. GetMask를 통한 아주 간편한 사용
LayerMask layer_Enemy = LayerMask.GetMask("Enemy");
// 3. 비트 연산자를 이용
// 타입을 int가 아닌 LayerMask로 써도 됩니다.
//int layer_Enemy = (1 << LayerMask.NameToLayer("Enemy"));
// 플레이어 기준, 원형으로 공격 가능 반경 내 있는 모든 적의 정보를 받아오기
// 원형 캐스트이기에 방향, 거리는 0으로
RaycastHit2D[] searched_Enemy = Physics2D.CircleCastAll(GameManager.instance.player.transform.position, radius, Vector2.zero, 0, layer_Enemy);
// 범위 내 적이 하나 이상 있다면
if (!searched_Enemy.Length.Equals(0))
{
// 거리 최소값
float distMin = 0;
// 가장 가까운 적의 인덱스
int distMinIndex = 0;
// 플레이어로부터 가장 가까운 적 탐색
for (int i = 0; i < searched_Enemy.Length; i++)
{
// 적 - 플레이어 간 2D 벡터
Vector2 posDif = searched_Enemy[i].transform.position - GameManager.instance.player.transform.position;
// sqrtMagnitude() 가 Distance() 보다 계산을 덜 하기에 성능을 덜 먹는다
float dist = Vector2.SqrMagnitude(posDif);
// 비교 대상이 없는 0번은 무조건 가장 가까운 적으로 써주기
// 이전보다 더 가까운 적이 있다면
if (i.Equals(0) || distMin > dist)
{
// 최소 거리, 가장 가까운 적의 인덱스 경신
distMin = dist;
distMinIndex = i;
}
}
// 가장 가까운 적의 트랜스폼 값을 반환
return searched_Enemy[distMinIndex].transform;
}
// 찾은 적이 없다면 null을 반환
else
return null;
}
2. LayerMask.GetMask(params string[] LayerName);
아주 쉽게 레이어 이름들만 적어넣어서 해당 레이어들만 적용되게 할 수 있습니다.
배열로 받을 수 있기에 여러 레이어 동시 지정이 가능합니다.
예시) LayerMask layer_Require = LayerMask.GetMask("Enemy", "Default");
3. 비트 연산자를 활용 LayerMask.NameToLayer()
1비트(bit)란 각 자리를 0과 1 이 둘로 2진수로 표현하는 데이터 형식입니다.
가장 작은 데이터 단위로 쓰이며 이를 8개 붙여서 1바이트 = 8비트가 됩니다.
1바이트로 풀어 쓴다면 숫자
1은 0000 0001
2는 0000 0010
3은 0000 0011
이런 식으로 쓰이게 됩니다.
각 자리 단위는 제일 오른쪽부터
0번째 자리는 2^0 = 1
1번째 자리는 2^1 = 2
2번째 자리는 2^2 = 4
이렇게 2^n으로 커집니다.
그리고 이렇게 증가/감소하는 숫자들의 자리를 이동시키기 위한 명령어로 비트 연산자가 생기게 됩니다.
비트 연산자는 << 혹은 >> 이 둘이 있습니다.
a << b 는 2진법으로 표현된 숫자 a를 b칸씩 왼쪽으로 밀어주며
a >> b 는 2진법으로 표현된 숫자 a를 b칸씩 오른쪽으로 밀어줍니다.
예를 들어
1 << 1 은 0000 0001 을 왼쪽으로 1칸씩 밀어준다고 생각하면 됩니다.
그러면 0000 0010 즉 2가 됩니다.(밀어내면서 새로 생긴 자리의 수는 0)
1 >> 1은 0000 0001 을 오른쪽으로 1칸씩 밀어준다고 생각하면 됩니다.
0000 0000 즉 0이 됩니다. (밀어내면서 새로 생긴 자리의 수는 0)
다시 레이어마스크로 돌아와보겠습니다.
해당 툴팁은 레이캐스트 중 원형캐스트의 매개변수 설명입니다.
제일 오른쪽에 int layerMask라고 써져 있습니다. 정수형이죠.
보기 쉽게 다시 제일 위의 스크린샷을 가져왔습니다.
LayerMask.NameToLayer("Enemy")
Enemy 레이어의 왼쪽에 보면 3 이라는 숫자가 적혀 있습니다.
해당 구문을 로그로 찍어 보아도 3이라는 숫자가 나옵니다.
많이들 실수하는 부분이,
아 그러면 3을 그대로 넣으면 되겠네
하며 그대로 쓰고 오류가 생깁니다.
3을 집어넣고 어떤 레이어를 지정하고 있는지 알아보면
Enemy 레이어가 아닌 Default, TransparentFX 레이어 둘을 지정하고 있습니다.
앞서 2진법 데이터 표현에 대해 설명한 이유가 여기서 나옵니다.
0번째 Default 레이어는 2^0 * 1 = 0000 0001
1번째 TransparentFX 레이어는 2^1 * 1 = 0000 0010
3을 2진법으로 쓴다면
0000 0011이 됩니다.
해당 자릿수 둘만 On 표시인 1로 찍어서
그 자리에 해당하는 레이어들만 포함하게 된 것입니다.
그러면 Enemy 레이어만 마스크에 포함하려면 어떤 수를 넣어야 하는가?
2^3 3번째 자리에 1을 넣어야 합니다 (제일 오른쪽 0번째부터 시작)
2^3 * 1 = 8 = 0000 1000 = 1 << 3 = 1 << LayerMask.NameToLayer("Enemy")
이렇게 위의 코드에서는 1을 3칸만큼 왼쪽으로 이동하여 해당 레이어를 지정하게 됩니다.