Layer Mask

SV's DEV·2025년 7월 25일
0

Unity 최적화

목록 보기
3/3
  • 유니티로 최적화를 하다보면 Tags and Layers에서 다양한 Tag와 Layer를 추가해서 사용 합니다. 이번에는 그 중에서 다양한 물리연산과 관련된 Layer에 대해서 알아보겠습니다.
  • Layer Mask의 스크립트 코드와 Scripting API의 내용을 참고로 하여 작성하였습니다.

Layer Mask 내부구조

  • Edit->Settings->Tags and Layers에서 수정을 하고 추가하는데 Tags나 Sorting Layer는 가변길이를 갖기 때문에 계속 추가하여 사용하지만 Layer는 32개의 고정된 길이를 갖기 때문에 이미 셋팅된 값들을 제외하면 최대 27개의 레이어를 추가 할 수 있습니다.
    내부 구조를 보면 다음과 같이 LyaerMask는 struct이고 m_Mask라는 정수형 프로퍼티를 하나 가지고 있습니다.
public struct LayerMask
{
  [NativeName("m_Bits")]
  private int m_Mask;

  public static implicit operator int(LayerMask mask) => mask.m_Mask;

  public static implicit operator LayerMask(int intVal)
  {
    LayerMask layerMask;
    layerMask.m_Mask = intVal;
    return layerMask;
  }
  //...
}
  • 그리고 오퍼레이터를 통해 LayerMask를 int에 대입하면 m_Mask값이 대입되고 int를 LayerMask에 대입하면 LayerMask를 생성하고 m_Mask값에 int값을 대입하게 됩니다. m_Mask를 비트플래그로 활용하면 총 32개의 Layer를 플래그 연산을 통해 간편하게 비교하고 사용 할 수 있습니다.

Layer Mask활용

  • 유니티에서는 Physics옵션의 Layer Collision Matrix에서 필요없는 부분을 제거하여 물리연산을 쉽게 최적화 할 수 있습니다.
  • 그리고 다음과 같이 여러가지 물리연산에 활용 가능합니다.
LayerMask targetLayer = LayerMask.GetMask("Enemy");
var hitTargets = Physics2D.CircleCastAll(myPos, radius, direction, distance, targetLayer);

value, GetMask, NameToLayer

  public int value
  {
	get => this.m_Mask;
    set => this.m_Mask = value;
  }

  public static unsafe int NameToLayer(string layerName)
  {
    try
    {
      ManagedSpanWrapper managedSpanWrapper;
      if (!StringMarshaller.TryMarshalEmptyOrNullString(layerName, ref managedSpanWrapper))
      {
        ReadOnlySpan<char> readOnlySpan = layerName.AsSpan();
        fixed (char* begin = &readOnlySpan.GetPinnableReference())
          managedSpanWrapper = new ManagedSpanWrapper((void*) begin, readOnlySpan.Length);
      }
      return LayerMask.NameToLayer_Injected(ref managedSpanWrapper);
    }
    finally
    {
      // ISSUE: fixed variable is out of scope
      // ISSUE: __unpin statement
      __unpin(begin);
    }
  }
  
  public static int GetMask(params string[] layerNames)
  {
    if (layerNames == null)
      throw new ArgumentNullException(nameof (layerNames));
    int mask = 0;
    foreach (string layerName in layerNames)
    {
      int layer = LayerMask.NameToLayer(layerName);
      if (layer != -1)
        mask |= 1 << layer;
    }
    return mask;
  }
  • 우선 value는 m_Mask을 리턴 합니다. m_Mask는 1 << layer(0~31까지의 인덱스 값)와 같습니다.
    LayerMask.NameToLayer는 레이어 이름에 해당하는 레이어 인덱스 값을 리턴합니다. 그리고 LayerMask.GetMask()는 LayerMask.NameToLayer로 인덱스값을 받아 비트연산을 통해 결국 해당 레이어의 value값과 같은 값을 리턴하게 됩니다.
var targetLayers = LayerMask.GetMask("Layer1", "Layer2");

//var layerMask1 = LayerMask.GetMask("Layer1");
//var layerMask2 = LayerMask.GetMask("Layer2");
//var targetLayers = layerMask1 | layerMask2;

var hitTargets = Physics2D.CircleCastAll(myPos, radius, direction, distance, targetLayer);
  • 그리고 input값을 배열로 받기 때문에 LayerMask.GetMask("layer1", "layer2")와 같이 여러 레이어를 입력하면 각 레이어의 인덱스를 비트연산을 통해 합쳐서 하나의 int값으로 리턴하게 됩니다.
    주석에 해당하는 부분과 같이 각레이어의 value값을 저장해두고 필요할 때 합쳐서 사용할 수도 있습니다.
int layer1Number = LayerMask.NameToLayer("Layer1");
int layher2Number = LayerMask.NameToLayer("Layer2");
int targetLayers = 1 << layer1Number | 1 << layer2Number;
var hitTargets = Physics2D.CircleCastAll(myPos, radius, direction, distance, targetLayers);
  • 그리고 인덱스를 저장해두고 사용할 수도 있습니다.

주의 사항

  • GetMask도 결국 NameToLayer를 사용하기 때문에 자주 사용하면 CPU의 부하를 주게됩니다. 그래서 미리 저장 해 두는 것이 좋습니다. 저장할 때 int로 저장 후 필요 시 LayerMask로 대입하게 되면 구조체를 생성하게 되므로 주의가 필요합니다. Parameter가 LayerMask라면 의도치 않은 비용이 발생 할 수 있습니다.
  • 레이어를 저장해 둘 때 LayerMask layerMask = LayerMask.GetMask("LayerName");
    과 같이 선언과 동시에 불러오게 되면 UnityException: NameToLayer is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'ObjectManager' on game object 'ObjectPool'.와 같은 에러 메세지를 볼 수 있습니다. Construct에서 초기화 하지 말고 Awake나 Start에서 초기화 해야 합니다.
  • LayerMask는 오퍼레이터를 통해 int로 자동 변환 되기 때문에 다양한 연산이 가능합니다. 자주 사용하게 되면 작지만 프로퍼티 접근비용이 발생하기 때문에 주의할 필요가 있습니다. 이전 글 중 관리되는 힙에 배열기반 Unity API부분을 참고 해주세요. 그리고 사용하기 편해서 여려가지를 섞어 쓰다보면 박싱과 언박싱으로 cpu에 영향을 주기 때문에 자주 호출 되는 부분은 주의해서 사용해야 합니다.

0개의 댓글