LayerMask, 싱글톤

이준호·2024년 1월 6일
0

📌 LayerMask

유니티에서 레이어에 대한 처리를 훨씬 효율적으로 하는 방법으로 레이어 마스크를 활용한다

유니티에서는 총 32개(0~31)의 레이어를 활용할 수 있으며, 이를 한번에 처리하기 위해 정수형 변수의 각 비트(32비트)를 할당하여 처리한다. 1개의 변수를 여러개의 bool값처럼 처리하는 방법

예를 들어, 카메라에서 특정 레이어에 있는 객체만 촬영하는 Culling Mask를 생각해보자. 이를 이렇게 구현할 수도 있다.

// 1바이트인 bool로 저장하는 의사(pseudo)코드
bool[] layerList;
foreach(bool i in LayerList){ // bool : 1바이트
	if(i) Render();
}

하지만 이렇게 하는 경우, 32개의 데이터를 저장할 때 32바이트가 필요하며, 32개의 연산을 하게 되어 매우 불필요하게 연산량이 커질 수 있다.

그래서 이를 2진법으로 표현하여 훨씬 더 편리하게 효율적으로 처리할 수 있다.

즉, 하나의 큰 수에 여러 개의 0과 1값을 저장하는 기법이라고 생각하면 된다.






2진법 기초

2진법은 0~9로 숫자를 표현하는 10진법과 다르게, 0~1로 숫자를 표현하는 방법이다. 컴퓨터는 전기 신호의 켜짐(1)과 꺼짐(0)을 이용해 정보를 처리하고 저장한다.

따라서 2진법은 컴퓨터가 이해하고 처리할 수 있는 가장 디본적인 데이터 표현 방식이다.
그리고 이렇게 2진법으로 표현한 수를 2진수라고 한다.

코드로 나타내기

Debug.Log($"십진수 10을 이진수로 변경 : {Convert.ToString(10, 2)}")
class Program
{
    static string DecimalToBinary(int decimalNumber)
    {
        if (decimalNumber == 0)
            return "0";

        string binaryString = "";
        while (decimalNumber > 0)
        {
            int remainder = decimalNumber % 2;
            binaryString = remainder + binaryString;
            decimalNumber = decimalNumber / 2;
        }
        return binaryString;
    }

    static void Main()
    {
        int decimalNumber = 15; // 변환하고 싶은 십진수
        string binaryString = DecimalToBinary(decimalNumber);
        Console.WriteLine("Binary Representation: " + binaryString);
    }
}

출저






비트 연산

  • 비트 옮기기 (Shift)
    비트를 왼쪽으로 옮긴다는 의미에서 비트 시프트(Shift)연산은 << 처럼 나타내고, 오른쪽으로 옮기는 시프트 연산은 >> 처럼 나타낸다. // 1 << 2
    예를 들어, 1 << 8은 아래와 같이 나타낼 수 있다.

// 8번 레이어 활성화
// 8번 : 보스 6번 : 일반
int layerMask = 1 << 8;
layerMask |= 1 << 6;  	// |= 6번도 포함(OR)

// 사용 예시: 8번 레이어의 오브젝트만 감지하는 Raycast
if (Physics.Raycast(ray, out hit, 100, layerMask)) {
    // 8번 레이어의 오브젝트와 충돌했을 때의 처리
}
  • 비트를 1로 만들기 (OR)

OR연산은 한쪽이라도 1인 경우 1이 되도록 하는 연산이다.
이를 통해 어떤 한 비트가 1이 되도록 할 수 있다.

// 8번과 10번 레이어 결합
int layerMask = (1 << 8) | (1 << 10);
101000000000
// 사용 예시: 8번 또는 10번 레이어의 오브젝트만 감지하는 Raycast
if (Physics.Raycast(ray, out hit, 100, layerMask)) {
    // 8번 또는 10번 레이어의 오브젝트와 충돌했을 때의 처리
}
  • 비트가 1인지 확인하기 (AND)
    아래와 같은 코드 구성이 일반적으로 많이 활용된다.

AND는 A, B 모두 1이 아니면 0을 반환하므로,
자신이 1일 때 상대가 1인지 아닌지 판단 할 수 있다.

bool isLayer8Included = (layerMask & (1 << 8)) != 0;

// 사용 예시: layerMask에 8번 레이어가 포함되어 있는지 확인
if (isLayer8Included) {
    // 8번 레이어가 포함된 경우의 처리
}

// 예시
// 1000000000 & 1001010101 
// => 1000000000 != 0 => true
// 100000000 & 010101010 => 000000000 != 0 => false
  • 비트 뒤집기 (NOT)
// 8번 레이어 제외
int layerMask = ~(1 << 8);

// 사용 예시: 8번 레이어를 제외한 모든 레이어의 오브젝트를 감지하는 Raycast
if (Physics.Raycast(ray, out hit, 100, layerMask)) {
    // 8번 레이어를 제외한 오브젝트와 충돌했을 때의 처리
}





LayerMask 메소드들

  • LayerMask.GetMask(params string[] layerNames);

    LayerName들을 넣어서 비트마스크를 만든다.
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);
}
  • LayerMask.value

    비트마스크값을 도출한다. (실제 이진수를 십진수로 바꿔서 계산) 예) 1025 = 1024 + 1

  • LayerMaks.NameToLayer(string layerName)

    레이어의 이름을 통해 레이어의 인덱스(비트마스크값 아님)을 도출한다. 예) 9

  • LayerMask.Contains(int layerIndex)

    LayerMask가 특정한 레이어 인덱스를 포함하고 있는지 확인한다.






비트마스크 상태이상 시스템

System.Flags를 사용해서 중첩 비트마스크를 부여할 수 있다.
이를 활용해 아래와 같은 상태이상 시스템을 구현할 수 있다.

[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();
    }
}











📌 싱글톤

싱글톤 패턴은 한 개의 인스턴스만 생성하고, 어디서든 그 인스턴스에 접근할 수 있는 디자인 패턴이다.

대부분 사람들이 싱글톤을 쓰는 이유 : 객체 간 접근이 편해서 / 중앙 집중식 관리가 머리 안아픔

  • 전역적인 상태나 리소스에 접근 : 싱글톤은 어디서든 접근할 수 있는 전역적인 상태나 리소스에 대한 중앙 집중적인 접근을 제공한다. 예를 들어, 게임의 설정, 오디오 관리자, 이벤트 매니저 등을 싱글토으로 구현할 수 있다. 한 가지의 상태로, 하나가 주가 되어서 관리

  • 중복 인스턴스 방지 : 싱글톤 패턴을 사용하면 오직 한 개의 인스턴스만 생성되므로, 중복 인스턴스를 방지할 수 있다. 이는 리소스 낭비나 예기치 않은 동작을 방지하는 데 도움이 된다.

  • 객체 간 편한 통신 : 싱글톤 인스턴스는 어디서든 접근할 수 있으므로, 객체 간의 통신이 편리해진다. 다른 객체에서 싱글톤 인스턴스를 사용해 데이터를 공유하거나 메서드를 호출할 수 있다. (!!싱글톤에 너무 의존하면, 단일책임원칙이 망가지고, 유지보수성이 떨어지고, 결합도가 높아진다.)

  • 유지보수 및 확장성 : 싱글톤 패턴은 코드의 유지보수성과 확장성을 향상시킨다. 인스턴스에 대한 접근이 중앙 집중화되므로, 코드의 변경이나 기능의 추가/변경이 용이해진다. 또한, 싱글톤 인스턴스를 사용하는 객체들 사이의 결합도를 낮출 수 있어 유지보수성을 향상시킨다.






국룰 싱글톤

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject); // 중복 인스턴스가 생성될 경우 제거
        }
    }
}





씬 전환 싱글톤

씬 전환시 주의점 : 라이프사이클은 씬마다 발생하지 않는다.

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    void Awake() // 게임오브젝트가 처음으로 켜진 그 순간
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject); // 씬 전환 시 파괴되지 않도록 설정
        }
        else
        {
            Destroy(gameObject); // 중복 인스턴스가 생성될 경우 제거
        }
    }

		public void Init(){
				// 초기 세팅
		}
}





Generic 싱글톤

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance; // Lazy Loading

    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
                if (_instance == null)
                {
                    GameObject obj = new GameObject();
                    // obj.name = typeof(T).Name;
                    _instance = obj.AddComponent<T>();
                }
            }
            return _instance;
        }
    }
}

public class AudioManager : Singleton<AudioManager>
{
    public void PlaySound(string soundName)
    {
        // 사운드 재생 로직
    }
}





주의사항

  • 상호 의존성 증가 : 싱글톤 인스턴스가 여러 부분에서 사용될 경우, 클래스 간의 상호 의존성이 증가하며, 이는 시스템의 결합도를 높이고 응집도를 낮출 수 있다.

  • 코드의 복잡성 : 싱글톤을 과도하게 사용하면 시스템 전반에 걸쳐 복잡도가 증가할 수 있으며, 이는 유지보수를 어렵게 만든다.






잘못된 싱글톤 케이스

  • 여러 개 존재하는 싱글톤 (static, 중복검사 등을 제외하여 중복이 발생하는 싱글톤 지양)

  • 일반적인 클래스에 Manager라는 이름 붙이기 (싱글톤은 대부분 XManager 처럼 작성)

  • 모든 문제를 싱글톤으로 해결하려고 하는 경우 (SRP)

profile
No Easy Day

0개의 댓글

관련 채용 정보