버튼 상태 생성기 · hover·pressed·disabled·focus
버튼 하나로 다섯 가지 상태. 상태마다 밝기·대비·채도·덧씌우기·오프셋·외곽선·알파 연산이 순서대로 보이고 수정할 수 있습니다. 픽셀 계산일 뿐이라 원본에 없는 것을 그리지 않습니다.
게임 버튼에는 보통 normal·hover·pressed·disabled·focus 다섯 장의 이미지가 필요합니다. 이 생성기는 PNG 한 장에서 읽고 고칠 수 있는 픽셀 연산으로 다섯 장을 만듭니다. 기본값은 hover 밝기 +10%·채도 +8%, pressed 밝기 −10%와 1px 아래로, disabled 채도 완전 제거와 알파 50%, focus 2px #3182f6 링입니다. states/*.png, 패킹된 스트립 하나, 사용한 모든 값을 기록한 states.json을 내보냅니다. 이미 있는 픽셀만 바꾸며 새 그림을 그리지는 않습니다.

- Nine-slice측정 확인· Pillow
- Bitmap font측정 확인· independent .fnt parser
- Button states측정 확인· Pillow
- Engine import미검증
상태 하나가 계산되는 방식
각 상태는 RGBA 픽셀에 적용하는 순서 있는 연산 목록입니다. 외곽선이 있으면 먼저 캔버스를 키우고, 밝기는 값 × 255를 R·G·B에 더하며, 대비는 중간 회색(128)을 기준으로 늘이고, 채도는 각 채널을 그 픽셀의 휘도(0.2126 R + 0.7152 G + 0.0722 B)에서 멀어지거나 가까워지게 합니다. 이어서 선택형 색 덧씌우기, 픽셀 이동, 모양을 네모로 팽창시킨 외곽선을 그리고, 마지막에 알파를 곱합니다. 완전히 투명한 픽셀은 다시 칠하지 않습니다.
엔진은 이 이미지들을 정해진 칸에 받습니다. Godot 4의 TextureButton에는 texture_normal, texture_hover, texture_pressed, texture_disabled, texture_focused가 있고, 자기 텍스처가 없는 disabled 버튼은 normal을 보여 주며, focused 텍스처는 기본 텍스처 위에 겹쳐 그려집니다. Unity UI Button은 Transition을 Sprite Swap으로 두면 Target Graphic의 기본 스프라이트와 Highlighted·Pressed·Disabled 스프라이트를 받습니다.
게임은 웹 페이지가 아니지만 WCAG 2.2의 두 규칙은 알아 둘 만합니다. 성공 기준 1.4.3은 비활성(disabled) 컴포넌트의 텍스트를 대비 최소 기준에서 제외하므로, 흐린 disabled 상태가 허용됩니다. 2.4.13 Focus Appearance(AAA)는 컴포넌트 둘레 2 CSS px 이상 크기의 포커스 표시와, 포커스 전후 픽셀 사이 3:1 대비를 요구합니다. 기본 링은 2px이고 #3182f6은 흰색 대비 3.71:1로 측정됩니다.
- 밝기
- 모든 채널에 더하는 값. +0.1은 +25.5단계입니다.
- 채도
- +0.08은 각 채널을 휘도에서 8% 더 멀리, −1은 픽셀을 회색으로 만듭니다.
- 오프셋
- 같은 캔버스 안에서 픽셀을 옮깁니다. 밖으로 나간 것은 버리고 들어오는 곳은 투명합니다.
- 외곽선
- 불투명한 모양 둘레의 지정 색·두께 링. 캔버스가 그 두께만큼 커집니다.
예시: 픽셀 하나가 기본 상태 다섯 개를 거치면
색 (200, 120, 40)으로 채운 40 × 16 버튼입니다. 내보낸 PNG에 실제로 들어가는 값입니다.
상태 픽셀 (R, G, B, A) 크기 연산
normal (200, 120, 40, 255) 40 × 16 없음
hover (231, 145, 58, 255) 40 × 16 밝기 +0.1 (+25.5), 채도 +0.08
pressed (174, 94, 14, 255) 40 × 16 밝기 −0.1, 오프셋 0,1 → 0번 줄이 투명해짐
disabled (131, 131, 131, 128) 40 × 16 채도 −1, 알파 × 0.5
focus (200, 120, 40, 255) 44 × 20 캔버스 변마다 +2px, 2px 링 (49, 130, 246)
disabled 회색 0.2126 × 200 + 0.7152 × 120 + 0.0722 × 40 = 131.2 → 131; 알파 255 × 0.5 = 127.5 → 128
hover 초록 휘도 156.7 + (145.5 − 156.7) × 1.08 = 144.6 → 145focus 이미지는 다른 상태보다 가로·세로 4px 크고, 외곽선이 네모 팽창이라 모서리 픽셀까지 링이 덮습니다. states.json에 상태마다 크기와 여백이 기록되므로 차이에 놀랄 일은 없습니다.
이렇게 작동합니다
- 기본값: hover 밝기 +10%, pressed −10%와 1px 아래로, disabled 채도 제거와 알파 50%, focus 2px 링.
- 상태마다 모든 연산 값이 보이고 수정할 수 있습니다.
- 출력: 상태마다 PNG 하나, 패킹된 스트립, 영역과 정확한 연산 값이 든 states.json.
- 투명 픽셀은 다시 칠하지 않습니다.
사용 방법
- 버튼 PNG를 끌어다 놓습니다.
- 상태별 연산을 조절합니다.
- 다섯 미리보기를 나란히 확인합니다.
- ZIP으로 내보냅니다.
Godot·Unity에서 상태 쓰기
- Godot 4:
TextureButton을 추가하고normal.png·hover.png·pressed.png·disabled.png를texture_normal·texture_hover·texture_pressed·texture_disabled에 지정합니다. - Godot 4는
texture_focused를 기본 텍스처 위에 그리며, 문서는 외곽선처럼 일부가 투명한 이미지를 권합니다.focus.png에는 버튼 전체가 들어 있으므로 이 칸에 쓰려면 이미지 편집기에서 링 안쪽의 버튼을 지우고 44 × 20 캔버스를 유지하세요. - Unity UI: Button의 Transition을 Sprite Swap으로 두고, Target Graphic의 Image에
normal.png를, Highlighted Sprite·Pressed Sprite·Disabled Sprite에hover.png·pressed.png·disabled.png를 넣습니다. - 버튼 크기가 바뀌어야 한다면, 상태에는 테두리가 없으므로 지정하기 전에 9슬라이스 편집기에서 상태 PNG마다 같은 나인 슬라이스 테두리를 주세요.
결과 확인
states.json의 모든rect가button-states.png에서 해당 상태를 정확히 잘라 내고,states.hover.ops에 사용한 값이 나열됩니다.normal.png는 원본과 바이트 동일합니다(Firefox에서는 반투명 픽셀이 알파 반올림 한 단계만큼 다를 수 있음).- 게임을 실행해 버튼에 마우스를 올리고, 누르고, 비활성화해 봅니다. 튀는 곳이 없고 pressed는 오프셋만큼만 움직여야 합니다.
문제 해결
| 증상 | 유력한 원인 | 확인 방법 | 해결 |
|---|---|---|---|
| pressed 이미지의 맨 아래 줄이 사라짐 | 1px 오프셋이 같은 캔버스 안에서 픽셀을 옮겨 마지막 줄이 밀려나고 0번 줄은 투명해짐 | pressed.png 아래 가장자리를 원본과 비교 | 그림 아래에 투명한 줄을 하나 남기거나, 오프셋을 0으로 두고 엔진에서 글자를 움직이기 |
| 포커스된 버튼이 튀거나 겹쳐 보임 | focus.png가 더 크고(40 × 16 버튼이면 44 × 20) Godot는 focus 텍스처를 기본 위에 그림 | states.json → states.focus.size | 외곽선 두께만큼 음수로 옮겨 가운데 맞추거나, 위 설명처럼 링만 남긴 이미지 사용 |
| 어두운 배경에서 disabled 버튼이 거의 안 보임 | 알파 0.5라 배경이 비쳐 보임 | 흰 바탕이 아닌 실제 배경 위에서 확인 | disabled 알파를 0.7로 올리거나, 알파는 1로 두고 채도만 빼기 |
| 밝은 버튼에서 hover 차이가 거의 없음 | 밝기는 더하는 방식이라 이미 255에 가까운 채널은 잘림 | 원본 픽셀 값이 약 230 이상 | hover에 밝기 대신 색 덧씌우기나 대비 사용 |
다른 방법
| 방법 | 이럴 때 더 낫습니다 |
|---|---|
| Unity Transition = Color Tint | 상태가 색만 다를 때. 이미지가 더 필요 없지만, 색을 곱할 뿐이라 픽셀을 옮기거나(pressed 오프셋) 링을 더할 수 없습니다. |
| 그림 프로그램에서 상태마다 직접 그리기 | 모양이 달라질 때(뒤집히는 베벨, 바뀌는 아이콘, 빛). 픽셀 계산은 원본에 없는 것을 그릴 수 없습니다. |
출력 파일과 검증 방법
"측정 확인"은 내려받은 파일을 페이지 밖에서(Pillow·numpy·별도 파서) 다시 열어 그 속성을 측정했다는 뜻입니다. 게임 엔진에서 불러오는 것은 시험하지 않았습니다.
| 대상 | 파일 | 검증 |
|---|---|---|
| Nine-slice | nine-slice.json · previews · SETUP.md | 측정 확인 Pillow크기를 바꾼 모든 미리보기에서 네 모서리 블록을 늘이지 않습니다. Chromium에서는 바이트 동일, Firefox에서는 반투명 픽셀이 알파 반올림 한 단계만큼 다를 수 있습니다. 빈틈·겹침 0. |
| Bitmap font | font.png · font.fnt (BMFont text) · font.json | 측정 확인 independent .fnt parser테스트에 따로 작성한 파서로 .fnt를 다시 읽어 헤더와 모든 글리프 영역이 일치했습니다. |
| Button states | states/*.png · strip · states.json | 측정 확인 Pillownormal은 원본과 같습니다(Chromium에서는 바이트 동일, Firefox에서는 반투명 픽셀의 색이 알파 반올림 한 단계 이내). states.json의 모든 영역이 스트립에서 해당 상태를 정확히 잘라 냅니다. |
| Engine import | Godot 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단계). 알파와 불투명 픽셀은 그대로입니다.
한계
- 상태별 나인 슬라이스 테두리는 없습니다. 9슬라이스 편집기에서 상태 PNG에 주세요.
- 엔진에서 시험하지 않았습니다. 위의 칸 이름은 Godot·Unity 공식 문서에서 가져왔습니다.
- 커닝 쌍 없음, 폰트 페이지 1장, MSDF 없음. SDF 출력은 베타입니다.
- TTF의 cmap을 해석하지 않으므로 누락 글리프 점검에는 .fnt나 랩의 폰트가 필요합니다.
- 아틀라스는 1페이지이고, 서로 닿은 요소는 하나로 셉니다.
- Godot·Unity에서 렌더링해 보지 않았습니다. 엔진용 값(patch margin, L·B·R·T 테두리)은 문서 기준이며 엔진에서 시험하지 않았습니다.
버전과 출처
docs/UI-LAB.md를 위해 Chromium에서 40 × 16 버튼으로 측정했습니다. normal은 원본과 바이트 동일, hover는 더 밝고, pressed는 오프셋되며 비워진 줄은 투명, disabled는 알파 128의 회색, focus는 #3182f6 링이 있는 44 × 20이고 그림은 그대로였으며, states.json의 다섯 영역이 스트립에서 각 상태를 정확히 잘라 냈습니다. 위 예시 값은 같은 모듈로 다시 계산했습니다. Godot·Unity 가져오기는 실행하지 않았습니다.
참고한 공식 문서
자주 묻는 질문
focus 이미지가 더 큰 이유는?
focus 링이 외곽선 두께만큼 캔버스를 키우며, 그 크기는 states.json에 기록됩니다.
normal 상태도 바뀌나요?
아니요. normal은 원본과 바이트 동일합니다(확인함).
무료인가요? 계정이 필요한가요?
이 페이지의 기능은 모두 무료이며 계정 없이 쓸 수 있습니다. 워터마크나 막혀 있는 내보내기가 없습니다.
스프라이트가 업로드되나요?
아니요. Studio는 이 브라우저 탭 안에서 디코딩·패킹·내보내기를 합니다. 프로젝트는 브라우저(IndexedDB)에 자동 저장되며 .nerulio 파일로 저장할 수 있습니다.
Aseprite나 엔진 플러그인을 설치해야 하나요?
아니요. .aseprite 파일은 브라우저에서 읽고 씁니다. 엔진별 번들에는 그 엔진에 필요한 것이 들어 있습니다(Godot는 가져오기 설정과 씬, Unity와 Godot 타일셋은 작은 편집기 스크립트).