본문으로 건너뛰기

게임 UI 에셋 랩 · UI 시트를 여백 있는 아틀라스로

게임 UI 그림을 위한 한 작업 공간: 시트에서 요소를 찾고, 여백과 가장자리 확장으로 패킹하고, 패널에 나인 슬라이스 테두리를 주고, 실제로 그려질 해상도에서 점검합니다.

UI 시트(버튼·패널·아이콘을 투명 PNG 한 장에 그린 것)는 엔진이 그리기 전에 이름 붙은 사각형들로 나뉘어야 합니다. UI 랩은 알파 임계값을 넘는 픽셀 덩어리(섬)를 요소로 찾고, 합치기 거리보다 가까운 섬(버튼과 옆의 배지)을 하나로 묶은 뒤, 여백과 선택형 가장자리 확장을 넣어 ui-atlas.png에 패킹하고 각 영역과 나인 슬라이스 테두리를 ui-atlas.json에 씁니다. Godot·Unity 리소스 파일이 아니라 숫자를 쓰며, 9슬라이스·상태·폰트·점검 단계가 같은 이미지를 이어서 씁니다.

UI 패널·버튼·글리프 시트·UI 시트(PNG)를 끌어다 놓으세요
UI 랩 열기
브라우저에서 처리하며 파일은 업로드되지 않습니다.
UI 랩: 나인 슬라이스 가이드와 여러 크기 미리보기가 있는 CC0 UI 패널
CC0 에셋(Kenney)을 연 실제 UI 랩 화면입니다.
출력
  • Nine-slice측정 확인· Pillow
  • Bitmap font측정 확인· independent .fnt parser
  • Button states측정 확인· Pillow
  • Engine import미검증

UI 시트에서 아틀라스 사각형까지

요소는 손으로 윤곽을 그리듯 찾습니다. 알파가 임계값(기본 255 중 8)보다 큰 픽셀을 여덟 방향 이웃과 묶어 섬을 만들고, 최소 크기(기본 16픽셀)보다 작은 섬은 먼지로 보고 버립니다.

섬은 후보일 뿐 의미를 알지는 못합니다. 아이콘 옆에 떨어진 점이 있으면 섬이 둘입니다. 합치기 거리(기본 4px)는 틈이 그 이하인 두 상자를 합치고 더 합칠 것이 없을 때까지 반복하므로, 가까이 붙은 조각들이 merged 태그가 붙은 한 요소가 됩니다. 서로 닿아 있는 버튼 두 개는 처음부터 한 섬이라 합치기 거리와 상관없이 한 요소로 남습니다.

패킹은 MaxRects로 요소를 최대 4096 × 4096px 한 페이지에 놓고 주변에 여백(기본 2px)을 비워 둡니다. 가장자리 확장은 요소의 맨 바깥 픽셀을 N px만큼 바깥으로 복제해서, 필터링이나 밉맵이 요소 바로 바깥을 샘플링해도 요소 자신의 색을 읽게 합니다. JSON의 영역은 요소 자체이고 확장된 테두리는 그 바깥에 있습니다.

알파 섬
알파 임계값을 넘는, 8방향으로 이어진 픽셀 묶음.
합치기 거리
두 상자를 한 요소로 합치는 최대 틈(픽셀).
여백(padding)
패킹된 요소 사이에 비워 두는 픽셀.
가장자리 확장(extrude)
요소 영역 바깥에 둘러 놓는 가장자리 픽셀의 복사본.

예시: 256 × 128 시트 하나를 실제로 측정

투명 시트 위 도형 다섯 개를 랩과 같은 감지·합치기·패킹 코드로 처리한 결과입니다.

시트 256 × 128   알파 임계값 8   최소 16px   합치기 거리 4
섬      패널 64×64 @8,8   버튼 96×32 @96,8   아이콘 16×16 @200,12   점 4×4 @218,12   화살표 20×20 @96,64
아이콘 → 점 틈 = 218 − (200 + 16) = 2px ≤ 4   → 요소 하나 22×16 @200,12, 태그 "merged"
결과    섬 5개 → 요소 4개  (합치기 거리 0이면 5개 유지)
패킹    여백 2, 확장 1   → ui-atlas.png 102 × 108
영역    element-02 3,3 96×32   element-01 3,41 64×64   element-03 73,41 22×16   element-04 73,63 20×20

요소 번호는 패킹 전에 위에서 아래, 왼쪽에서 오른쪽 순으로 붙습니다. element-01이 패널, element-03이 합쳐진 아이콘입니다. 이름이 JSON frames의 키가 되므로 내보내기 전에 바꿔 두세요.

이렇게 작동합니다

  • 합치기 거리를 둔 알파 섬으로 요소 감지(버튼과 그 위 글자가 한 요소가 됨).
  • UI 아틀라스: 여백·가장자리 확장이 있는 MaxRects 패킹. 요소별 테두리가 JSON에 들어갑니다.
  • 나인 슬라이스·버튼 상태·비트맵 폰트·점검(크기와 앵커·안전 영역·현지화 넘침·WCAG 대비) 단계.
  • 설정 링크: 이미지가 아닌 모든 설정을 URL로 공유할 수 있습니다.

사용 방법

  1. UI 시트를 끌어다 놓습니다.
  2. 감지된 요소를 확인하고 이름을 바꾸고 합치기 거리를 조절합니다.
  3. 패널에 나인 슬라이스 테두리를 줍니다.
  4. 아틀라스 PNG + JSON을 내보냅니다.

결과 확인

  • ui-atlas.json을 엽니다. frames에 남긴 요소마다 항목이 하나씩 있고, 모든 rect가 meta.size 안에 있어야 합니다.
  • 아무 이미지 편집기에서 ui-atlas.png의 rect 하나를 잘라 원본 시트의 같은 요소와 비교합니다. 같은 픽셀이어야 합니다.
  • "tag": "merged"를 찾아보세요. 모두 일부러 합친 조각이어야 하며, 우연히 가까이 놓인 버튼 두 개면 안 됩니다.
  • 요소에 나인 슬라이스 테두리를 줬다면 SETUP.md에 그 이름의 절과 숫자 네 개가 있습니다.

문제 해결

증상유력한 원인확인 방법해결
버튼 두 개가 한 요소로 나옴서로 닿아 있거나 틈이 합치기 거리 이하임요소 상자가 두 버튼을 모두 덮고, 섬이 여럿이면 태그가 merged합치기 거리를 0으로. 닿아 있다면 그림에서 투명 픽셀을 한 줄 이상 띄우기
글자·빛·그림자가 따로 요소가 됨버튼과의 거리가 합치기 거리보다 멀거나, 픽셀이 알파 임계값보다 흐림예상한 요소 개수와 비교하고 틈을 확대해 보기합치기 거리를 몇 픽셀 올리고, 부드러운 그림자가 잘리면 알파 임계값을 낮추기
작은 점들이 요소로 잡힘흩어진 픽셀이 최소 크기를 넘는 섬을 만듦요소 목록에 아주 작은 영역이 있음최소 픽셀 수를 올리거나 그림에서 먼지를 지우기
엔진에서 요소 가장자리에 이웃 요소의 선이 보임엔진이 영역 경계를 넘어 필터링·밉맵하는데 여백이 0이라 이웃을 읽음실행 화면에서 가장자리를 확대여백 2와 확장 1~2를 쓰거나, 픽셀 아트라면 Nearest 필터
아틀라스 패킹이 실패함요소들이 4096 × 4096 한 페이지에 들어가지 않음요약에 크기 대신 오류가 표시됨시트를 나눠 아틀라스를 두 개 만들기. 여러 페이지 UI 아틀라스는 없음

다른 방법

방법이럴 때 더 낫습니다
낱장 PNG용 패커(스프라이트 시트 패커, TexturePacker)요소가 이미 파일로 하나씩 있을 때. 감지가 필요 없고 엔진별 아틀라스 형식을 씁니다. TexturePacker 없이 패킹하기도 참고.
Unity Sprite Editor의 슬라이스시트를 Unity에서만 쓰고 영역을 Unity 가져오기 설정에 저장하고 싶을 때.
Godot AtlasTexture를 직접 만들기요소가 두세 개뿐일 때. atlas에 시트를 지정하고 region을 입력하면 추가 파일이 전혀 없습니다.

출력 파일과 검증 방법

"측정 확인"은 내려받은 파일을 페이지 밖에서(Pillow·numpy·별도 파서) 다시 열어 그 속성을 측정했다는 뜻입니다. 게임 엔진에서 불러오는 것은 시험하지 않았습니다.

대상파일검증
Nine-slicenine-slice.json · previews · SETUP.md측정 확인 Pillow크기를 바꾼 모든 미리보기에서 네 모서리 블록을 늘이지 않습니다. Chromium에서는 바이트 동일, Firefox에서는 반투명 픽셀이 알파 반올림 한 단계만큼 다를 수 있습니다. 빈틈·겹침 0.
Bitmap fontfont.png · font.fnt (BMFont text) · font.json측정 확인 independent .fnt parser테스트에 따로 작성한 파서로 .fnt를 다시 읽어 헤더와 모든 글리프 영역이 일치했습니다.
Button statesstates/*.png · strip · states.json측정 확인 Pillownormal은 원본과 같습니다(Chromium에서는 바이트 동일, Firefox에서는 반투명 픽셀의 색이 알파 반올림 한 단계 이내). states.json의 모든 영역이 스트립에서 해당 상태를 정확히 잘라 냅니다.
Engine importGodot NinePatchRect · Unity Sprite border · BMFont import미검증Godot·Unity에서 실행하지 않았습니다.

검증 근거

나인 슬라이스 모서리가 100×40·300×80·800×200·420×120에서 늘어나지 않음, 동일한 열 12개에서 테두리 6·6·6·6 제안. 버튼 상태: normal이 원본과 같음, disabled는 알파 128 회색, focus 링 정확. .fnt 글리프 레코드가 JSON과 일치. .po의 누락 글리프를 정확히 나열. 흰 바탕 검정 글자 21:1 측정. 이미지는 브라우저 캔버스를 거칩니다. Chromium은 바이트가 그대로이고, Firefox는 반투명 픽셀의 색을 미리 곱한 값 한 단계로 반올림합니다(Kenney 버튼에서 측정: 알파 63에서 최대 4단계). 알파와 불투명 픽셀은 그대로입니다.

한계

  • 아틀라스는 한 페이지, 최대 4096 × 4096이며 회전 패킹은 없습니다.
  • 엔진 리소스 파일(Godot .tres, Unity .meta)은 쓰지 않고 영역과 테두리 숫자만 씁니다.
  • 커닝 쌍 없음, 폰트 페이지 1장, MSDF 없음. SDF 출력은 베타입니다.
  • TTF의 cmap을 해석하지 않으므로 누락 글리프 점검에는 .fnt나 랩의 폰트가 필요합니다.
  • 아틀라스는 1페이지이고, 서로 닿은 요소는 하나로 셉니다.
  • Godot·Unity에서 렌더링해 보지 않았습니다. 엔진용 값(patch margin, L·B·R·T 테두리)은 문서 기준이며 엔진에서 시험하지 않았습니다.

버전과 출처

docs/UI-LAB.md를 위해 Windows 11의 Chromium(Playwright)에서 측정했습니다. 버튼 하나와 투명한 구멍이 있는 128 × 64 시트에서 요소 4개가 나오고 합치기 거리 40에서 하나로 합쳐졌습니다. 패킹된 픽셀은 원본 영역과 같고, 확장 1은 가장자리 픽셀을 영역 바깥에 복제합니다. 요소에 준 테두리 5/5/4/4가 ui-atlas.json에 들어갑니다. 요소 40개가 있는 1024 × 1024 시트는 감지·합치기·패킹·그리기가 76ms에 끝났습니다. Godot·Unity에서 아틀라스를 불러오지는 않았습니다. 위의 엔진 이름은 Godot 4.7 공식 문서를 따릅니다.

참고한 공식 문서

자주 묻는 질문

맞닿은 버튼 두 개가 한 요소가 되는 이유는?

요소는 알파 섬이므로 닿아 있는 그림은 한 섬입니다. 그림에서 떼거나 나인 슬라이스 단계에서 잘라 내세요.

Godot·Unity 리소스를 쓰나요?

아니요. 엔진마다 필요한 값(patch margin, L·B·R·T 테두리)이 든 PNG + JSON과 SETUP.md를 씁니다. 엔진 가져오기는 시험하지 않았습니다.

무료인가요? 계정이 필요한가요?

이 페이지의 기능은 모두 무료이며 계정 없이 쓸 수 있습니다. 워터마크나 막혀 있는 내보내기가 없습니다.

스프라이트가 업로드되나요?

아니요. Studio는 이 브라우저 탭 안에서 디코딩·패킹·내보내기를 합니다. 프로젝트는 브라우저(IndexedDB)에 자동 저장되며 .nerulio 파일로 저장할 수 있습니다.

Aseprite나 엔진 플러그인을 설치해야 하나요?

아니요. .aseprite 파일은 브라우저에서 읽고 씁니다. 엔진별 번들에는 그 엔진에 필요한 것이 들어 있습니다(Godot는 가져오기 설정과 씬, Unity와 Godot 타일셋은 작은 편집기 스크립트).