[2026_01_07] Unity UI Mask를 활용한 역마스크(Cutout Mask)
2026. 1. 7. 21:19ㆍTIL
문제 상황
튜토리얼 시스템을 구현하면서 화면 전체를 어둡게(Dimmed) 처리하되, 특정 UI만 밝게 하이라이트하여 클릭 가능하게 만들어야 했다.
해결 방법
1. Stencil Buffer를 활용한 Cutout Mask
일반적인 Mask는 마스크 영역만 보이게 하지만, Stencil Buffer의 CompareFunction.NotEqual을 사용하여 역마스크(마스크 영역은 숨기고 나머지만 보임)를 구현했다.
csharp
public class CutoutMaskUI : Image
{
public override Material materialForRendering
{
get
{
Material material = new Material(base.materialForRendering);
material.SetInt("_StencilComp", (int)CompareFunction.NotEqual);
return material;
}
}
}
2. UI 계층 구조
TutorialOverlay
├── RaycastBlocker (투명, 전체 화면 클릭 차단)
├── MaskImage (타겟 UI 위치/크기로 이동)
│ └── Dimmed (CutoutMaskUI, 화면 전체 크기)
├── GuideText
└── Arrow
3. 핵심 문제와 해결
문제: Dimmed가 Mask의 자식이라 Mask가 이동하면 Dimmed도 따라 이동해서 화면 전체를 못 덮음
해결: Dimmed를 Mask 이동 방향의 반대로 상쇄 이동시켜 화면 중앙에 고정
csharp
// Mask가 (100, 50)으로 이동
maskImage.anchoredPosition = localPos;
// Dimmed를 반대 방향으로 (-100, -50) 이동
dimmedBackground.rectTransform.anchoredPosition = -localPos;
// 결과: Dimmed의 실제 화면 위치 = (100, 50) + (-100, -50) = (0, 0)
핵심 코드
csharp
private void UpdateMask(RectTransform target)
{
maskImage.gameObject.SetActive(true);
// 1. 타겟 UI의 월드 코너 좌표 가져오기
Vector3[] targetCorners = new Vector3[4];
target.GetWorldCorners(targetCorners);
// 2. Tutorial Canvas 로컬 좌표로 변환
RectTransform canvasRect = tutorialCanvas.GetComponent<RectTransform>();
Vector2[] localCorners = new Vector2[4];
for (int i = 0; i < 4; i++)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRect,
RectTransformUtility.WorldToScreenPoint(null, targetCorners[i]),
null,
out localCorners[i]
);
}
// 3. Mask 위치와 크기 설정
Vector2 localPos = (localCorners[0] + localCorners[2]) / 2f;
float width = Mathf.Abs(localCorners[3].x - localCorners[0].x);
float height = Mathf.Abs(localCorners[1].y - localCorners[0].y);
maskImage.anchoredPosition = localPos;
maskImage.sizeDelta = new Vector2(width, height);
// 4. Dimmed를 반대 방향으로 상쇄 이동 (핵심!)
dimmedBackground.rectTransform.anchoredPosition = -localPos;
}
추가 구현 사항
- 타겟만 클릭 가능하게: 타겟 UI에 별도 Canvas를 추가하여 sortingOrder를 높게 설정
- 다른 Canvas 간 좌표 변환: RectTransformUtility.ScreenPointToLocalPointInRectangle 활용
- 실제 렌더링 크기 계산: GetWorldCorners()로 LayoutElement, DOTween 등이 반영된 실제 크기 계산
배운 점
- Unity UI Mask는 Stencil Buffer 기반이라 역마스크 구현이 가능하다
- 부모-자식 관계의 RectTransform은 좌표가 상대적이므로 반대 이동으로 상쇄 가능하다
- 서로 다른 Canvas 간 UI 위치를 맞출 때는 월드 좌표를 거쳐 변환해야 한다
'TIL' 카테고리의 다른 글
| [2026_01_09] 사운드 고르기... (0) | 2026.01.09 |
|---|---|
| [2026_01_08] 버프/디버프 적용 시 실제 적용값(appliedValue) 저장 (0) | 2026.01.08 |
| [2026_01_06] 비대칭 디버깅(같은 로직인데 한 쪽에서만 작동) (0) | 2026.01.06 |
| [2026_01_02] 중간 발표 (0) | 2026.01.02 |
| [2025_12_31] Unity 게임 배속 증가 시 Projectile 충돌 감지 실패 해결 (1) | 2025.12.31 |