LayMask와 비트연산자 처리
유니티에서 Layer에 등록하여 처리하는 경우가 종종 있다. 이를 처리하는 방법으로 LayerMask를 활용한다.
유니티에는 총 32의 레이어를 활용할 수 있으며, 이를 한번에 처리하기 위해 정수형 변수의 각 비트(32비트)를 할당하여 처리한다.
bool값으로 먼저 true false구분하여 처리할 수 있다.
// 1바이트인 bool로 저장하는 의사(pseudo)코드
bool[] layerList;
foreach(bool i in LayerList){ // bool : 1바이트
if(i) Render();
}
하지만 bool값으로 처리하게 된다면 적은 수의 Layer면 크게 상관없지만, 32개를 모두 검사해야 하는 상황이라면 0부터 31까지 모조리 검사하여 진행해야하기 때문에 불필요하게 연산량이 커지게 된다.
그래서 지금부터 사용하는 것이 바로 비트연산이다.
1. 비트연산
1. 비트옮기기(shift)
비트를 왼쪽으로 옮긴다는 의미에서 비트 시프트(Shift)연산은 << 로 나타내고, 오른쪽은 >> 로 나타낸다.
1 << 8 은 100000000 을 의미한다.
Layer가 6에 위치한다면, 1 << 6 이며, 8에 위치한다면 1 << 8 로 하면 된다.
int layerMask = 1 << 8
if (Physics.Raycast(ray, out hit, 100, layerMask)) {
// 8번 레이어의 오브젝트와 충돌했을 때의 처리
}
2. 비트 OR 연산 ( | )
OR연산은 한쪽이라도 1인경우 1이되는 연산이다. 예를들어 1000 과 0001이 있다면 OR연산 할 시에 1001이 된다.
// 6번과 8번 레이어 결합
int layerMask = (1 << 6) | (1 << 8);
1001000000
// 사용 예시: 6번 또는 8번 레이어의 오브젝트만 감지하는 Raycast
if (Physics.Raycast(ray, out hit, 100, layerMask)) {
// 6번 또는 8번 레이어의 오브젝트와 충돌했을 때의 처리
}
3. 비트가 1인지 확인하기 (AND)
AND연산자는 모두 1이 아니면 0을 반환하기 때문에 사실상 자신이 1일 때 상대가 1인지 아닌지 분간할 수 있다고 보면된다.
예를들어 0001 이 있다면, 1111과 And연산을 해보면 0001이 나오기 때문에 판단이 가능하다.
bool isLayer8Included = (layerMask & (1 << 8)) != 0;
// 사용 예시: layerMask에 8번 레이어가 포함되어 있는지 확인
if (isLayer8Included) {
// 8번 레이어가 포함된 경우의 처리
}
// 예시
// 1000000000 & 1001010101
// => 1000000000 != 0 => true
// 100000000 & 010101010 => 000000000 != 0 => false
4. 비트 뒤집기(NOT)
~ 를 붙이면 반대로 뒤집는다. 즉, 0은 1로 1은 0이 된다.
~0001 이라면 1110 이 된다는것이다.
// 8번 레이어 제외
int layerMask = ~(1 << 8);
// 사용 예시: 8번 레이어를 제외한 모든 레이어의 오브젝트를 감지하는 Raycast
if (Physics.Raycast(ray, out hit, 100, layerMask)) {
// 8번 레이어를 제외한 오브젝트와 충돌했을 때의 처리
}
2. LayerMask.GetMask()
물론 위처럼 비트연산이 아닌 string값으로 Layer를 찾을 수 있다.
만약 8번에 Monster Layer가 있고, 9번에 Wall Layer가 있으며 Raycast와 충돌했을 때 몬스터와 벽 둘 다 뭔가가 일어나야 한다면 아래와 같다.
LayerMask mask = LayerMask.GetMask("Monster") | LayerMask.GetMask("Wall");
// int mask = (1 << 8) | (1 << 9); 와 동일
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100.0f, mask))
{
Debug.Log(hit1.collider.gameObject.name);
}
3. 비트마스크의 상태이상 시스템
아래 코드는 상태이상 시스템의 예시이다. 게임을 하다보면 캐릭터에 여러가지 상태이상이 걸리는데, 한번에 풀어버리는 경우도 있고, 하나씩 풀리는 경우도 있다.
상태이상이 걸리는 것도 한번에 여러개가 걸릴 수 도 있지만, 화상, 중독, 빙결 등등 들어오는 타이밍이 제각각이기 때문에 이럴때 사용할 경우 유용하다.
[System.Flags] // 비트연산자로 불러오게 할 수 있는 어트리뷰트
public enum StatusEffects
{
None = 0,
Poisoned = 1 << 0, // 0001
Burned = 1 << 1, // 0010
Frozen = 1 << 2, // 0100
Paralyzed = 1 << 3 // 1000
}
public class StatusEffectManager
{
private StatusEffects currentEffects = StatusEffects.None;
public void AddEffect(StatusEffects effect)
{
currentEffects |= effect;
}
public void RemoveEffect(StatusEffects effect)
{
currentEffects &= ~effect;
}
public void ClearEffects()
{
currentEffects = StatusEffects.None;
}
public bool HasEffect(StatusEffects effect)
{
// 0100 & 0100 => 0100 != 0000 -> true
return (currentEffects & effect) != StatusEffects.None;
}
public void PrintEffects()
{
Console.WriteLine("Current Status Effects: " + currentEffects);
}
}
class Program
{
static void Main()
{
StatusEffectManager manager = new StatusEffectManager();
// 상태 이상 추가
manager.AddEffect(StatusEffects.Poisoned);
manager.AddEffect(StatusEffects.Frozen);
// 현재 상태 이상 출력
manager.PrintEffects(); // Poisoned, Frozen
// 상태 이상 제거
manager.RemoveEffect(StatusEffects.Poisoned);
// 상태 이상 확인
if (manager.HasEffect(StatusEffects.Frozen))
{
Console.WriteLine("The character is frozen.");
}
// 모든 상태 이상 제거
manager.ClearEffects();
}
}
유니티에서 [System.Flags] 속성은 열거형(Enum)의 멤버가 비트 플래그로 사용될 수 있음을 나타내는 특별한 속성이다. 열거형 값들을 조합하여 단일값으로 표현할 수 있으며, 연산식도 간편하게 할 수 있다고 한다.
그래서 AddEffect로 중독과 빙결의 상태를 |= OR연산자로 추가해줬으며 0101이 되어있다. Remove 시에는 ~ NOT연산자를 이용하여 이를 제거해준다 위 사례에서는 RemoveEffect로 중독을 전달하고 있으므로 0001의 반대인 1110으로 나머지 있던 값들은 1로 찾아서 true를 반환해주고, 중독의 위치인 0001은 반대값인 0을 넣어서 false를 반환하여 상태이상을 제거해준다.