유니티/유니티 팁

"이거 다시 작업해주세요" 소리를 없애는, 2D 아트 작업자를 위한 실전 유니티 최적화 가이드

강목근 2025. 8. 30. 16:23

좋은 아티스트는 최적화까지 고려한다.

내 아트 작업물은 정말 기깔난데, 왜 게임에만 넣으면 사이즈 줄여오라고, 이렇게 넣으면 안된다고 할까요?

 

이 질문에 답할 수 있다면, 당신은 이 글을 그냥 지나쳐도 된다.

 

게임 개발에 있어서, 아트 작업자의 역할은 단순히 아름다운 작업물을 만드는 것에서 끝나지 않는다.

내가 만든 리소스가 게임 엔진 위에서 어떻게 그려지고, 성능에 어떤 영향을 미치는지 이해하는 것은 이제 필수 역량이다.

 

본 글에서는 "이거 다시 작업해주세요"라는 말을 듣지 않기 위해, 혹은 "최적화 때문에 디자인을 이렇게 바꿀 수 있을까요?"라는 슬픈 요청을 받지 않기 위한 내용이 포함되어 있다.

개발자와의 불필요한 커뮤니케이션 비용을 줄이고, 당신의 작업물 본연의 가치를 온전히 프로젝트에 기여하게 만드는 실전 지식을 담았다.


텍스처: 메모리와 렌더링 부하의 핵심

sRGB (Color Texture)

붉은 박스에 주목하라.

해당 옵션이 무엇인지 깊게 생각해본 사람은 별로 없을 것이다.

특히나 유니티 엔진을 만질 일이 많지 않은 아트 작업자라면 더욱 그럴 것이다.

 

해당 옵션은 컴퓨터가 모니터에 정확한 색을 표현하도록 보정해주는 감마 보정 옵션이다.

때문에 색상 정보가 담긴 텍스처(캐릭터, 배경 등)은 반드시 체크해야 한다.

 

반면, 노말 맵이나 마스크처럼 색이 아닌 데이터 정보로 사용되는 텍스처는 반드시 체크를 해제해야 원본 데이터가 손상되지 않는다.


Sprite Atlas

아틀라스의 단적인 예시

아틀라스 구성 전략: 어떻게 묶어야 하는가?

  • 함께 뜨고 함께 사라지는 것들끼리 묶는다.
    • 가장 핵심 원칙이다. 예를 들어, "로비 UI"에서만 사용되는 아이콘과 버튼 이미지는 "Lobby_Atlas"로 묶고, "상점 UI"에서만 사용되는 재화 아이콘은 "Shop_Atlas" 등으로 묶어야만 한다.
    • 잘못된 예시(가장 많이 하는 실수): 로비, 상점, 인게임 UI의 이미지를 전부 "Common_UI_Atlas"로 묶으면 안된다.
      게임 시작 시 로비 UI를 표시하기 위해 해당 아틀라스 전체를 메모리에 올리게 되는데, 이때 당장 사용하지도 않을 상점/인게임 리소스까지 메모리를 차지하게 되어 커다란 낭비를 유발한다.
    • 좋은 예시:게임 전체에서 항상 사용되는 공통 아이콘 등은 Common_UI_Atlas로 묶고, 각 화면 단위(Scene or Popup)로 아틀라스를 분리하는 것이 가장 이상적이다.

Compression과 메모리 대역폭

이미지 임포트 설정의 Compression 항목

Compression은 텍스처를 압축하여 용량을 줄이는 설정이다. 

High Quality로 갈수록 용량은 커지고 품질이 좋아진다. (아예 압축하지 않는 None도 있으나, High Quality만 해도 용량 차이가 크므로 추천하지 않는다.)

 

저사양 기기를 위한 고려사항: 메모리 대역폭

메모리 대역폭은 "데이터가 이동하는 도로의 넓이"에 비유할 수 있다.

저사양 기기는 이 도로가 매우 좁다.

단적인 예시, 좌측이 대역폭이 작은 기기이고, 우측이 대역폭이 큰 기기이다.

아무리 텍스처를 압축해서 파일 용량을 줄여도, GPU는 화면에 그림을 그리기 위해서 해당 텍스처를 메모리에서 읽어와야 한다.

그런데 도로(대역폭)가 좁아서 데이터가 한번에 많이 오가지 못하면, 병목 현상이 발생하여 게임이 버벅이게 된다.(차가 막히는 것처럼..)

 

해결책 1. 압축 포맷의 중요성

압축 포맷의 안드로이드 설정에서, Format의 선택 가능한 옵션 목록

ASTC 압축 방식은 다양한 블록 크기를 지원하여 압축률과 품질을 세밀하게 조절할 수 있다.

특히, 저사양 기기에서는 품질을 조금 희생하더라도 압축률이 높은 옵션(예: ASTC 8x8)을 선택하여 GPU가 한 번에 읽어오는 데이터 크기 자체를 줄이는 것이 유리하다. 

좀 더 간단하게 설명하자면, ASTC 방식으로 압축한 것은 1대의 차량에 여러명이 탄 것이고, 압축하지 않으면 1명만 탄 것이다.

그러니 당연하게도 총 차량의 수가 줄어들고, 병목이 줄어들 것이다.

 

해결책 2. 아틀라스 최대 크기 제한

Max Texture Size가 바로 최대 크기를 제한하는 것이다.

거대한 4096x4096 사이즈의 아틀라스 한 장은 저사양 기기의 메모리 캐시에 한 번에 올라가지 못해 여러 번에 나누어 읽어야 할 수 있다.

이는 도로를 여러 번 왕복하는 것과 같아 비효율을 낳는다.

따라서 저사양 기기가 개발하는 게임의 타겟 목록에 있다면, 아틀라스의 최대 크기를 2048x2048 이하로 설정하여 메모리 접근을 효율적으로 만드는 것이 좋다.

 

Read/Write Enabled

텍스처 임포트 설정에 보이는 Read/Write 옵션

Read/Write 옵션을 체크한 상태로 두면, 스크립트 코드로 텍스처의 픽셀 정보를 읽고 쓸 수 있게된다.

하지만 그 대가로 메모리를 두 배로 차지한다. (CPU 및 GPU 메모리에 둘 다 상주하게 된다.)

아트 작업자가 이 옵션을 직접 킬 일이 없으므로, 혹여나 켜져있다면 반드시 꺼주자.

 

Platform-specific Overrides

아이콘 모양을 보면, Default, PC, Android, iOS인 것을 알 수 있다.

텍스처의 압축 포맷이나 방식 등을 각각의 기기에 따라 다르게 처리되게끔 오버라이드 할 수 있다.

PC 버전으로 할 땐 고화질 텍스처를 사용하게 하고(최근 PC는 대부분 모바일 기기보다 대역폭이 좋으며, 모니터의 크기가 모바일 기기에 비해 압도적으로 크므로..) 모바일 버전에서는 저화질 텍스처를 사용하게 하는 것처럼 플랫폼별로 각기 다른 임포트 설정을 적용할 수 있다.


한번 만들면 끝, 효율적인 UI 프리팹 제작법

프리팹은 "붕어빵 틀"과 같다. 잘 만들어진 프리팹 하나는 수백 수천 개의 UI 요소를 효율적으로 관리할 수 있게 해준다.

9 - Slicing

Sprite Editor를 열어서 Border를 반드시 설정해야 한다.

버튼이나 배경 패널처럼 크기가 유동적으로 변해야 하는 UI에 사용되는 기술이다.

작은 원본 이미지를 9개의 영역(모서리 4개, 테두리 4개, 중앙 1개)로 나누어, 크기가 변할 때 모서리는 그대로 두고 테두리와 중앙 영역만 늘려준다. (Border 값을 넣어줘야 9개의 영역이 나눠지므로 반드시 임포트 이후 수정이 필요하다.)

 

이를 통해 단 하나의 작은 이미지로 모든 크기의 버튼과 패널을 만들 수 있어 텍스처 메모리를 크게 절약할 수 있다.

9 Slice를 적용한 Image 컴포넌트(상), 일반적인 Image 컴포넌트(하)

Image Type을 Sliced로 변경하여 사용하면 된다.

 


숨겨진 최적화, Raycast Target

TMP의 Raycast Target 옵션의 위치, Extra Settings를 열어야 보인다.

Image, TMP 등 모든 UI 그래픽 컴포넌트에는 Raycast Target이라는 옵션이 존재한다.

해당 옵션은 눈에 보이지 않는 "클릭 감지 영역"을 만들지 여부를 결정하는 것인데, 배경 이미지, 아이콘, 텍스트와 같이 유저가 직접 클릭할 필요가 없는 모든 요소는 반드시 해당 옵션은 체크 해제해야 한다.

체크된 요소가 많을수록 매 프레임 불필요한 입력 감지 연산이 늘어나 성능을 저하시킨다.

또한, 막상 클릭이 되어야 하는 영역 위에 존재하는 UI 요소들이 해당 영역을 가려 클릭이 되지 않는 경우가 생길 수도 있다.


동적 UI와 구조 최적화

움직이거나 자주 바뀌는 UI는 특별한 관리가 필요하다.

Canvas의 작동 원리와 재빌드 (Rebuild)

Canvas는 UI가 그려지는 커다란 도화지의 역할을 한다.

하지만 문제는, 이 도화지 위에 있는 아주 작은 요소(예: 텍스트) 하나만 바뀌어도, 유니티는 도화지 전체를 다시 분석하고 그리는(Rebuild) 작업을 한다는 것이다.

그렇기 때문에, UI가 복잡하고 한 Canvas에 포함된 요소가 많을수록 이 비용이 엄청나게 커진다.

 

실전 테크닉: Canvas 분리

위 문제를 해결하는 가장 간단하고 좋은 방법은 "도화지 자체를 나누는 것"이다.

자주 바뀌는 동적 UI(HP바, 타이머, 팝업, 파티클 등)과 거의 바뀌지 않는 정적 UI(배경, 메뉴 버튼)를 서로 다른 Canvas 게임 오브젝트에 배치하면 된다.

이렇게 하면 동적 UI가 아무리 변해도 정적 UI는 다시 그릴 필요가 없어져 Rebuild 비용이 크게 감소한다.

 

이와 관련하여 Nested Canvas라는 방법이 있다.

캔버스를 분리할 때, 다 따로 두는 것이 아니라.. 하나의 큰 캔버스 객체 아래에 자식으로 존재하는 캔버스를 두는 것이다. (이 방법의 이름이 Nested Canvas이다.)

 

자식 캔버스를 두면, 해당 자식 캔버스는 자신만의 독립적인 Rebuild 영역을 가지게 된다. 즉, 자식 캔버스 안의 UI 요소가 변경되어도 오직 그 작은 자식 캔버스만 Rebuild되고, 커다란 부모 캔버스 전체가 Rebuild 되는 비효율을 막을 수 있다.

 

캔버스 아레에 존재하는 자식 캔버스에 Override Sorting 설정을 할 수 있다.

추가적으로, Override Sorting 옵션을 활성화 하면 부모와 상관없이 독자적인 정렬 순서(Sorting Order)를 가질 수 있다. 

이를 이용하여 팝업 Ui 그룹을 다른 UI보다 항상 위에 그리게 하거나, 복잡한 UI 계층 구조를 건드리지 않고도 렌더링 순서를 쉽게 제어하는 것이 가능해진다.

https://woodroot.tistory.com/21

 

Unity의 UGUI 최적화 공부하기(Overlay만 다룬다!)(1) - Dirty Flag까지, UGUI의 시스템과 구조를 파악하자.

본 포스트는 알쓸유잡을 보며 공부하고 배운 내용을 기록하는 포스트입니다.https://www.youtube.com/live/1e2mSCS7o1A?si=BAbKuXCTQBdKMMDb UI 객체도 메시다!2D게임, 3D 게임 구분 없이 UI도 객체로 취급되어 화면

woodroot.tistory.com

위 내용과 관련하여 본 블로그에서 깊게 다룬 게시글이 있으니 관심이 있다면 시리즈를 전부 읽어보는 것을 추천한다.


Layout Groups와 Infinite Scroll의 원리와 함정

편리한 시작: Layout Groups의 역할과 명확한 한계점

Horizontal/Vertical Layout Group은 자식 UI 요소들로 가로 또는 세로로 자동으로 정렬해주는 매우 편리한 기능이다.

이런 이유로 많은 UI 작업자들이 UI에 목록과 같은 것을 만들 때 가장 먼저 떠올리는 방법이기도 하다.

 

그러나 이 편리함에는 치명적인 함정이 있다. Layout Group은 목록에 1000개의 아이템이 있다면, 정말로 1000개의 게임 오브젝트를 전부 생성하여 화면에 보이지 않는 영역에까지 배치한다.

 

한계점에 대한 예시

상상을 해보자. 친구 목록이 500명인 유저가 친구 창을 열 때마다, 게임은 실제로 500개의 UI 프리팹을 인스턴스화하고, Layout Group은 이 500개의 위치를 전부 계산해야 한다. 이는 엄청난 메모리 낭비와 CPU 연산 부하를 유발하여 창이 열리는 속도를 매우 느리게 만들고, 심할 경우 게임이 잠깐 멈출 수도 있다.

 

이런 이유로 Layout Group은 아이템 개수가 수십 개를 넘어가는 목록에는 절대 사용해서는 안 된다.

 

Infinite Scroll의 등장, 왜 이걸 사용하는 걸까?

위에서 언급한 Layout Group의 치명적인 단점을 해결하기 위해 등장한 개념이 바로 Infinite Scroll이다. (다른 말로는 UI 가상화, Virtualization이다.)

*Infinite Scroll이나 UI Virtualization으로 구글링을 하면 많은 정보를 얻을 수 있을 것이다.

 

Infinite Scroll의 핵심 원리는 "속임수"이다. 1000개의 아이템이 목록에 있더라도, 실제로는 화면에 보이는 10~15개 정도의 UI 오브젝트만 생성한다.

그리고 유저가 스크롤을 내리면, 화면 위쪽으로 사라지는 UI 오브젝트를 삭제하지 않고 맨 아래로 순간이동 시킨 뒤, 내용만 새로운 데이터로 바꿔치기하여 재활용한다.

 

유저 입장에서는 수천 개의 목록을 끊김 없이 스크롤 하는 것처럼 보이지만, 실제로는 소수의 UI 오브젝트만 계속 재활용되므로 메모리 사용량과 CPU 부하가 Layout Group에 비하면 압도적으로 낮다.

친구 목록, 인벤토리, 상점, 랭킹 보드 등 데이터가 많은 거의 모든 UI에 이 기술이 사용된다.

이는 쾌적한 유저 경험을 제공하기 위한 선택이 아닌 필수 기술이다.

 

*주의점

Infinite Scroll을 사용하더라도, 재활용되는 개별 프리팹 자체가 무거우면(계층이 깊고, 불필요한 컴포넌트가 많으면) 스크롤 성능은 여전히 나쁘다.

핵심은 재활용되는 프리팹을 최대한 가볍고 단순하게 만드는 것이다.


고급 UI 효과, 파티클 최적화 가이드

화려한 UI를 만들기 위해서 많은 사람들이 파티클을 사용한다. 하지만, UI 파티클은 최적화의 가장 큰 적이 될 수 있다는 것을 알아야 한다.

UI 파티클이 게임을 느리게 만드는 주범인 이유

가장 큰 원인은 "엄청난 오버드로우"다. 반투명한 입자 수십 수백 개가 화면의 같은 픽셀 영역에 계속해서 덧칠을 하면서 GPU에 부담을 주기 때문이다. 

Window -> Rendering Debugger -> Overdraw Mode를 사용하면 화면에서 무엇이 겹치는지 볼 수 있다.

최적화 법칙 1: 파티클 전용 Canvas를 사용하라

앞에서 설명했듯이, 매 프레임 변하는 파티클은 반드시 별도의 Canvas에 분리해야 한다.

또한, 이 캔버스에는 클릭 감지가 필요 없으므로 Graphic Raycaster 컴포넌트를 삭제하여 불필요한 비용을 없애야 한다.

Canvas를 생성하면 자동으로 Graphic Raycaster 컴포넌트가 붙는다.

최적화 법칙 2: 가장 가벼운 셰이더(Sahder)를 사용하라

파티클의 Material에는 가장 가벼운 셰이더를 사용해야 한다.

유니티 기본 셰이더 중에서는 Unlit이 붙은 기본 셰이더들이 가장 좋다.

Unlit을 사용하는 이유는, 조명 계산을 전혀 하지 않기 때문에 다른 조명 계산을 하는 셰이더에 비해 연산 비용이 매우 저렴하기 때문이다.

 

또한, 같은 화면에 보이는 각각의 파티클에 되도록 같은 셰이더를 사용하는 것이 좋다.

드로우 콜을 최소화 할 수 있게 하는 첫 걸음이 될 것이다.

 

최적화 법칙 3: 텍스처는 작게, 아틀라스는 필수

파티클에 사용되는 이미지는 되도록 작은 이미지를 사용하는 것이 당연하게도 좋다.

또한, 파티클에 사용되는 이미지를 같은 화면에 보일 수 있는 것들로 묶어서 Sprite Atlas에 포함시켜야 드로우 콜을 최소화할 수 있다.

 

최적화 법칙 4: 파티클 개수와 크기를 줄여라 (Overdraw 줄이기)

파티클 자체의 입자 개수를 줄여 적은 수의 입자로 최대 효과를 내는 것이 매우 중요하다.

크고 투명한 입자 50개보다, 작고 불투명한 입자 10개가 시각적으로나 성능적으로 더 나을 수 있으므로..

항상 아트적 관점과 최적화적 관점을 같이 가지고 파티클을 제작하는 습관이 중요하다.

 

최적화 법칙 5: 최종 수단 - 시퀀스 이미지(Sprite Sheet)로 제작하기

버튼 클릭 이펙트처럼 항상 동일한 패턴으로 재생되는 효과는, 파티클 시스템을 실시간으로 연산하는 대신, 미리 영상처럼 녹화하여 이미지 시퀀스(스프라이트 시트)로 만드는 것이 성능 면에서 압도적으로 유리하다. 이는 복잡한 연산을 단순한 "이미지 넘기기"로 바꾸는 최고의 최적화 기법이다.


UI 애니메이션 최적화 - Animator의 함정과 대안

Animator 컴포넌트의 함정

유니티의 Animator 컴포넌트는 캐릭터나 복잡한 오브젝트의 상태(State)에 따른 애니메이션을 관리하기 위한 강력한 시스템이다.

하지만 이 강력함은 UI에 사용하기에는 너무 무겁고 비효율적인 경우가 훨씬 더 많다.

 

가장 큰 문제점

Animator는 애니메이션이 재생되는 동안 매 프레임 UI 요소의 속성(위치, 크기, 색상 등...)을 변경한다.

앞선 글을 정독했다면 여기서 문제점을 유추할 수 있는데, 이는 Canvas의 Rebuild를 매 프레임 유발하는 최악의 결과를 낳는다.

 

예를 들어, 배경 패널에 Animator로 '반짝이는 효과'를 넣으면, 그 애니메이션 때문에 UI 전체가 매 프레임 다시 그려지는 대참사가 발생할 수 있다.

또한, Animator는 재생 중이 아닐 때도 일정량의 오버헤드를 가지므로, 단순히 나타나고 사라지는 효과를 위해 수십 개의 UI에 Animator를 붙이는 것은 심각한 낭비이다.

 

대안 1: 트위닝 라이브러리 활용

트위닝은 A상태에서 B상태로 일정 시간 동안 부드럽게 값을 변화시키는 애니메이션 기법을 말한다.

팝업이 커지면서 나타나거나, 버튼이 눌릴 때 살짝 작아지는 등의 단순하고 일회성인 애니메이션에는 Animator 대신 트위닝을 사용하는 것이 훨씬 가볍고 효과적이다.

 

DOTween과 같은 유명 에셋을 사용하거나, 개발자에게 간단한 트위닝 스크립트를 요청하여 사용하는 것을 권장한다. 이는 필요한 순간에만 작동하고 끝나므로 불필요한 성능 부하가 없다.

 

대안 2: Canvas Group 오브젝트

UI 그룹 전체(예: 팝업 창)를 부드럽게 나타나거나 사라지게 만들 때, 개별 이미지와 텍스트의 투명도를 일일이 애니메이션 하는 것은 최악의 방법이다.

 

이때 사용하는 것이 바로 CanvasGroup 컴포넌트이다. 제어하고 싶은 UI 그룹의 최상위 부모 오브젝트에 CanvasGroup을 추가하면, 단 하나의 Alpha 값 조절로 모든 자식 요소들의 투명도를 한 번에 제어할 수 있다.

 

성능상 이점

수십 개의 UI 요소를 변경하여 Canvas Rebuild를 수십 번 유발하는 대신, 단 한 번의 변경으로 처리하므로 훨씬 효율적이다. 트위닝으로 CanvasGroup의 Alpha 값을 0에서 1로 바꾸는 것이 UI 페이드 인/아웃 효과의 정석적인 구현 방법이다.

 

Interactable과 Blocks Raycasts 옵션도 제어할 수 있어 UI 그룹을 효율적으로 켜고 끄는 데 매우 유용하다.

 

결론, 언제 무엇을 써야 하는가?

트위닝 + CanvasGroup - 팝업 노출/숨김, 개별 UI 요소의 움직임/크기/회전 변화 등 95% 이상의 모든 UI 애니메이션에 사용한다.

Animator - 버튼의 Normal, Pressed, Disabled 상태가 각기 다른 복잡한 애니메이션을 가져야 하는 것과 같이 반드시 상태 머신이 필요한 경우에만 제한적으로 사용한다.


마스킹(Masking) 최적화

UI의 특정 영역을 잘라내어 보이지 않게 하는 경우가 굉장히 많을 것이다.

이때, 많은 아트 작업자들이 Mask 컴포넌트를 아무렇게나 사용하는 경우가 잦다.

하지만 이건 성능 저하를 일으킬 수 있으므로, 주의해서 사용해야 한다.

 

RectMask2D

오직 사각형 형태로만 자를 수 있지만, CPU 기반으로 단순하고 매우 가벼운 연산을 사용하여 마스킹을 한다.

좌표를 기준으로 범위를 벗어나면 그리지 않게 하는 방식이다.

스크롤 뷰처럼 사각형 영역을 마스킹 할 때는 반드시 RectMask2D를 사용해야만 한다. (연산량 차이가 많이 난다.)

 

Mask

원형, 별 모양 등 복잡한 형태로 자를 수 있는 강력한 기능이다. 하지만 GPU의 스텐실 버퍼라는 무거운 기능을 사용하며, 드로우 콜을 증가시키는 주범이다. (Mask로 인해 동일 아틀라스에 묶여있어도 Batch가 깨지는 경우가 생긴다.)

꼭 필요한 경우가 아니라면 사용을 지양해야 하며, 사용하더라도 마스킹이 적용되는 UI 요소의 개수를 최소화해야 한다.

 

스텐실 버퍼란 무엇인가?

학창 시절에, 미술 시간에서 가끔 등장하던 "스텐실 기법"을 기억하는가?

아트 전공자라면 더 익숙할 수 있겠다.

 

간단하게 설명하자면..

1. 먼저, 원하는 모양(예시: 별 모양)이 뚫린 판(이 판이 Mask 컴포넌트의 이미지와 동일한 역할이다.)을 도화지 위에 올린다.

이 과정이 "스텐실 버퍼"에 '이 영역만 그림을 그릴 것이다.'라고 기록하는 단계이다.

 

2. 그 다음, 판 위에 물감을 마구 뿌린다. (이것이 Mask 컴포넌트가 붙은 오브젝트의 자식 UI 요소들)

 

3. 마지막으로 판을 떼어내면, 처음에 모양을 냈던 별 모양으로만 물감이 묻어있는 것을 볼 수 있다.

 

Mask 컴포넌트는 GPU에게 바로 이 작업을 시키는 것이다.

GPU는 "기록된 영역(별 모양)"에만 자식 UI를 그리도록 명령받아서 처리한다.

 

이처럼 여러 단계를 거쳐 GPU를 제어하기 때문에, 단순히 좌표로만 계산하는 RectMask2D보다 훨씬 무거운 작업이 된다.


최적화는 이 글을 보고있는 당신의 경쟁력이다.

이 가이드에서 다룬 내용을 체크리스트로 만들어 작업할 때 확인해보는 것을 권장한다.

  • 텍스처 임포트 시, sRGB, Read/Write, Compression 설정을 확인했는가?
  • UI 이미지들은 화면/기능 단위로 분리된 Sprite Atlas로 묶여있는가?
  • 클릭되지 않는 모든 UI 요소의 Raycast Target을 껐는가?
  • 자주 변하는 UI나 파티클은 별도의 Canvas로 분리했는가?
  • 단순한 UI 애니메이션에 Animator 대신 트위닝과 CanvasGroup을 사용했는가?
  • 파티클에 가장 최적화된 셰이더를 사용하고, Overdraw를 확인했는가?
  • 사각형 마스킹에 RectMask2D를 사용했는가?

위 체크리스트를 계속 참고하며 작업을 진행하면, 개발자와의 불필요한 커뮤니케이션이 줄어들고 작업 효율이 훨씬 좋아지는 경험을 할 수 있을 것이다.