스프라이트 시트 프레임 크기 · 칸·여백·간격 측정
Phaser도 Godot도 유니티도 Defold도 시트를 자르기 전에 같은 네 가지를 묻습니다. 프레임 하나의 너비와 높이, 첫 프레임이 가장자리에서 떨어진 거리, 프레임 사이의 픽셀 수입니다. 스프라이트 작업 공간은 이 숫자를 픽셀에서 재어 신뢰도와 함께 보여 주므로, 32였다가 48이었다가 하며 찍어 볼 필요가 없습니다.
스프라이트 시트의 프레임 크기는 칸 하나의 너비와 높이입니다. 테두리와 틈이 없으면 시트 너비 ÷ 열 수, 시트 높이 ÷ 행 수이며, 384 × 64를 6열로 나누면 64 × 64입니다. 양쪽 여백이 m, 프레임 사이 간격이 s라면 프레임 너비 = (시트 너비 − 2m − (열 수 − 1) × s) ÷ 열 수입니다. Phaser, Godot, 유니티 모두 이 네 숫자(프레임 너비·높이·여백·간격)를 묻습니다. Nerulio는 픽셀에서 이 값을 재어 신뢰도와 함께 보여 주거나, 프레임마다 정확한 사각형을 아틀라스 데이터로 써서 입력 자체를 없애 줍니다.

- Godot 4검증됨· Godot 4.7.2
- Unity 6검증됨· Unity 6000.5.3f1
- Phaser 3 / 4검증됨· Phaser 3.90 + 4.2
- PixiJS 8검증됨· PixiJS 8.21
- Aseprite JSON검증됨· Phaser load.aseprite · PixiJS 8.21
- .aseprite검증됨· Aseprite 1.3.18
- Spine / libGDX검증됨· spine-canvas 4.2
- Starling / Sparrow XML검증됨· Phaser 4.2
- LÖVE검증됨· LÖVE 11.5
- Defold빌드 확인· Defold bob.jar 1.13.1
- GameMaker미검증
- CSS sprites검증됨· Chromium
- GIF / APNG디코딩 확인· Pillow
격자를 설명하는 네 숫자
격자 시트는 프레임(칸) 너비와 높이, 시트 가장자리와 첫 프레임 사이의 여백, 이웃 프레임 사이의 간격만 알면 완전히 정해집니다. 한 행의 n번째 프레임은 x = 여백 + n × (프레임 너비 + 간격)에서 시작하고, 프레임 너비 + 간격이 그림이 반복되는 주기(피치)입니다.
시트 크기를 짐작한 열 수로 나누는 방법은 여백과 간격이 0일 때만 맞습니다. 결과가 정수가 아니면(398 ÷ 6 = 66.33) 여백이나 간격이 있거나 열 수가 틀렸다는 신호입니다. 정수라고 해서 맞다는 증거도 아닙니다. 256px 시트는 8, 16, 32, 64, 128로 모두 나누어떨어지니, 열 수는 계산이 아니라 그림을 보고 세야 합니다.
Nerulio는 "나누어떨어진다"를 믿지 않습니다. 틈이 있어야 할 자리의 완전히 투명한 구분선, 열과 행이 반복되는 주기, 칸 내용이 서로 닮았는지, 칸 경계가 스프라이트를 가르는지를 봅니다. 후보 크기는 8~128px, 측정한 주기에서 나온 크기, 시트를 딱 나누는 크기이며, 여백은 64px, 간격은 16px까지 찾습니다. 후보마다 높음·보통·낮음 신뢰도와 근거, 픽셀이 든 칸 수가 표시됩니다.
크기가 제각각인 스프라이트(또는 패킹된 아틀라스)로 된 시트에는 하나의 프레임 크기가 없습니다. 스프라이트 윤곽으로 자르고 프레임마다 사각형을 저장하는 아틀라스 데이터로 내보내면 격자 숫자가 아예 필요 없습니다.
- 프레임(칸) 크기
- 프레임 하나의 너비와 높이(픽셀).
- 여백(margin)
- 첫 프레임 앞의 빈 테두리. Godot·유니티는 Offset, Phaser는 margin.
- 간격(spacing)
- 두 프레임 사이의 빈 픽셀. Godot는 Separation, 유니티는 Padding, Phaser는 spacing.
- 피치
- 프레임 크기 + 간격. 한 프레임의 왼쪽 끝에서 다음 프레임까지의 거리.
- 열 × 행
- 가로·세로로 들어가는 프레임 수: 내림((시트 − 여백 + 간격) ÷ 피치).
예시: 여백과 간격이 있는 시트
시트 398 × 134 px, 그림을 보면 6열 × 2행
398 / 6 = 66.33 정수가 아님 → 여백이나 간격이 있음
첫 불투명 열은 x = 2, 프레임 사이 투명한 틈 = 2 px
프레임 너비 = (398 − 2 × 2 − 5 × 2) / 6 = 384 / 6 = 64
프레임 높이 = (134 − 2 × 2 − 1 × 2) / 2 = 128 / 2 = 64
피치 = 64 + 2 = 66 → 프레임 시작 x = 2, 68, 134, 200, 266, 332
Phaser frameWidth 64, frameHeight 64, margin 2, spacing 2
유니티 Grid By Cell Size: Pixel Size 64 × 64, Offset 2 × 2, Padding 2 × 2
Godot Add Frames from Sprite Sheet: Size 64 × 64, Separation 2 × 2, Offset 2 × 2검산: 마지막 프레임은 332 + 64 = 396에서 끝나고, 오른쪽 여백 2px를 더하면 398입니다. 숫자가 시트 너비에 정확히 떨어지지 않으면 어딘가가 틀린 것입니다.
이렇게 작동합니다
- 투명한 구분선, 그림이 반복되는 주기, 칸 경계를 넘는 픽셀을 보고 칸 크기·여백·간격을 재며, 후보 격자마다 높음·보통·낮음 신뢰도와 근거가 붙습니다.
- 가장 나은 격자를 시트 위에 미리보기로 그리고, 적용을 누르기 전에는 아무것도 자르지 않습니다. 다른 후보는 클릭 한 번이면 됩니다.
- 측정이 안 되는 시트는 사용자 격자에 칸 너비·높이, 오프셋 X/Y(여백), 간격 X/Y를 직접 넣습니다. 엔진이 묻는 네 숫자와 같습니다.
- 크기가 제각각인 시트는 격자 대신 덩어리 단위로 자를 수 있습니다. 이런 시트에는 하나의 프레임 크기가 없으니 아틀라스로 내보내 엔진에 넣으세요.
- 입력이 귀찮다면 패킹 & 내보내기가 프레임마다 정확한 사각형을 담은 아틀라스를 씁니다. 검증에서 Godot 4.7.2, Unity 6, Phaser 3.90·4.2가 불러왔습니다.
사용 방법
- 시트를 스프라이트 작업 공간에 넣습니다.
- 가져오기 패널의 격자 결정에서 칸 크기·오프셋·간격과 신뢰도를 읽습니다.
- 틀렸다면 다른 후보를 고르거나 사용자 격자를 입력하고 적용을 누릅니다.
- 아래 표대로 엔진에 숫자를 옮기거나, 패킹 & 내보내기에서 그 엔진용으로 내보냅니다.
재고 나서 입력하기
- 시트를 Nerulio 스프라이트 작업 공간에 넣고 가져오기 패널의 격자 결정을 읽습니다. 칸 크기·오프셋·간격, 신뢰도, "M칸 중 N칸에 픽셀"이 나옵니다.
- 오버레이가 어긋나면 다른 후보를 누르거나 사용자 격자에 칸 너비·높이, 오프셋 X/Y, 간격 X/Y를 넣습니다. 자르기 전에 미리보기가 온전한 칸과 남는 픽셀을 보여 줍니다.
- Phaser:
this.load.spritesheet('hero', 'hero.png', { frameWidth: 64, frameHeight: 64, margin: 2, spacing: 2 }). 마지막 칸이 비어 있으면endFrame으로 개수를 줄입니다. - 유니티: Sprite Mode를 Multiple로 하고 Sprite Editor를 열어 Slice › Grid By Cell Size를 고른 뒤 Pixel Size, Offset, Padding을 넣고 Slice, Apply를 누릅니다.
- Godot 4: SpriteFrames 패널 › Add Frames from Sprite Sheet에서 Size, Separation, Offset을 넣습니다. Godot에서 스프라이트 시트 쓰기 참고.
- 숫자 입력을 건너뛰려면 패킹·내보내기가 Godot·유니티·Phaser 등에 프레임마다 사각형을 써 줍니다.
잰 숫자를 넣을 곳
| 엔진 | 필드 이름 | 넣을 측정값 |
|---|---|---|
| Phaser 3 / 4 · this.load.spritesheet | frameWidth, frameHeight, margin, spacing | 칸 너비, 칸 높이, 오프셋(값 하나), 간격(값 하나) |
| Godot 4 · Sprite2D | hframes, vframes | 열 수와 행 수 — 오프셋과 간격이 모두 0일 때만 |
| Godot 4 · SpriteFrames | 프레임마다 AtlasTexture 영역 | Godot 4 내보내기가 대신 씀(4.7.2에서 검증) |
| 유니티 · Sprite Editor › Slice › Grid By Cell Size | Pixel Size, Offset, Padding | 칸 크기, 오프셋 X/Y, 간격 X/Y |
| Defold · 타일 소스 | tile_width, tile_height, tile_margin, tile_spacing | 칸 크기. Studio의 .tilesource는 프레임을 여백·간격 0으로 다시 배치(bob.jar 1.13.1로 빌드) |
필드 이름은 각 엔진의 것입니다. 손으로 넣는 이 설정을 엔진에서 돌려 보지는 않았고, 확인된 경로는 패킹 & 내보내기의 아틀라스입니다.
문제 해결
| 증상 | 유력한 원인 | 확인 방법 | 해결 |
|---|---|---|---|
| 열이 넘어갈수록 프레임이 조금씩 밀림 | 간격을 0으로 뒀거나, 간격을 포함한 너비를 프레임 너비로 넣음 | n번째 프레임 시작 = 여백 + n × 피치인지 그림과 비교 | 잰 간격과 순수한 프레임 너비를 입력 |
| 마지막 열이나 행이 빠짐 | 프레임 크기나 여백이 조금 커서 마지막 칸이 들어가지 않음 | 내림((시트 − 여백 + 간격) ÷ 피치)가 보이는 열 수와 같아야 함 | 여백과 간격을 다시 확인. 오른쪽 여백은 어느 칸에도 속하지 않음 |
| 프레임마다 스프라이트가 반쪽이거나 두 개 | 칸 크기가 실제의 절반이나 두 배(48 × 48을 48 × 96으로 읽음) | 칸 경계에 잘린 스프라이트, 한 칸에 발이 두 쌍인지 확인 | 맞는 크기의 후보 선택. Nerulio 가져오기는 아무것도 가르지 않는 후보를 우선함 |
| 왼쪽과 위쪽 여백이 달라 Phaser에서 어긋남 | Phaser load.spritesheet는 두 축 공통의 여백·간격 하나씩만 받음 | Nerulio는 X와 Y를 따로 보고함 | Phaser 아틀라스로 내보내기. 유니티 Offset·Padding은 X·Y를 따로 받음 |
| 애니메이션 끝에 빈 프레임이 붙음 | 시트에 쓰지 않은 칸이 있음 | 채워진 칸 수 확인. Nerulio는 "M칸 중 N칸에 픽셀"로 표시 | Phaser endFrame, 유니티 Keep Empty Rects 끄기, 또는 Nerulio(빈 칸을 건너뜀) |
엔진별 내보내기와 검증 방법
"검증됨"은 실제 CC0 에셋으로 내보낸 파일을 해당 엔진·도구가 직접 불러와 원본과 비교했다는 뜻입니다. 불러오지 않은 형식은 그렇게 표시합니다.
| 대상 | 파일 | 검증 |
|---|---|---|
| Godot 4 | .tres SpriteFrames · .tscn AnimatedSprite2D · .png.import | 검증됨 Godot 4.7.2Godot가 불러와 모든 애니메이션 프레임을 그려 원본과 비교했습니다. 씬은 Nearest 필터를 쓰고 회전은 쓰지 않습니다. |
| Unity 6 | .unity.json · Editor/NerulioSpriteImporter.cs · AnimationClip | 검증됨 Unity 6000.5.3f1배치 모드에서 스프라이트 영역·피벗·픽셀·클립 키·길이를 확인했습니다. |
| Phaser 3 / 4 | atlas JSON (multiatlas) · .anims.json | 검증됨 Phaser 3.90 + 4.2두 버전 모두에서 프레임과 애니메이션을 그렸습니다. |
| PixiJS 8 | spritesheet JSON · animations · anchor · meta.scale | 검증됨 PixiJS 8.21회전·여러 페이지 아틀라스도 포함해 확인했습니다. |
| Aseprite JSON | hash / array · frameTags · slices | 검증됨 Phaser load.aseprite · PixiJS 8.21태그의 방향과 반복 횟수가 유지됩니다. |
| .aseprite | layers · tags · durations · slices | 검증됨 Aseprite 1.3.18Aseprite 명령줄로 열어 렌더링했습니다. |
| Spine / libGDX | .atlas | 검증됨 spine-canvas 4.2Spine 런타임이 회전·트림 영역을 모두 그렸습니다. libGDX 자체는 실행하지 않았습니다. |
| Starling / Sparrow XML | XML per page | 검증됨 Phaser 4.2Phaser 3.90은 트림된 XML 프레임을 잘못 그립니다(Phaser 버그). "Sparrow XML (Phaser 3)" 프리셋은 트림을 끄며 3.90·4.2 모두 통과합니다. |
| LÖVE | Lua quads · nerulio_atlas.lua · main.lua | 검증됨 LÖVE 11.5포함된 헬퍼로 모든 프레임을 그리고 모든 애니메이션을 재생했습니다. |
| Defold | .atlas · .tilesource | 빌드 확인 Defold bob.jar 1.13.1Defold 명령줄 빌더로 빌드하고 빌드된 텍스처의 UV를 다시 읽어 확인했습니다. |
| GameMaker | spr_<name>_stripN.png · gamemaker.json | 미검증GameMaker에서 불러오지 않았습니다(헤드리스 실행 불가). 스트립은 GameMaker 스트립 가져오기 방식대로 자릅니다. |
| CSS sprites | .css · preview .html | 검증됨 Chromium포함된 스타일시트로 모든 클래스를 그렸습니다. |
| GIF / APNG | one file per animation | 디코딩 확인 Pillow내보낸 뒤 모든 프레임과 지연 시간을 다시 디코딩했습니다. APNG는 원본과 동일합니다. |
검증 근거
GIF 디코더: 실제 GIF 29개 408/408프레임이 Pillow와 동일, APNG 16개 132/132프레임. .aseprite: 코퍼스 231개 파일을 가져와 다시 쓰고 Aseprite 1.3.18로 열었을 때 태그·길이·픽셀 231/231 일치. 내보내기: 실제 CC0 에셋으로 실제 엔진에서 49회 실행해 47회 통과. 실패 2회는 Phaser 3.90의 트림 XML 버그이며 Phaser 3 프리셋으로 피합니다.
한계
- 격자·섬 인식은 신뢰도가 표시된 미리보기일 뿐이며 적용을 누르기 전에는 아무것도 자르지 않습니다. 모든 선택에는 한 번에 바꿀 수 있는 대안이 있습니다.
- 레이어가 정확히 합성되지 않는 .aseprite 파일(그룹 불투명도 등)은 합쳐서 가져오고, 가져오기 패널에 이유를 표시합니다.
- 가져온 아틀라스에서 회전된 프레임은 회전된 채로 남으며 그 내보내기는 미검증입니다.
- 이미지는 한 변 32,768px(2억 6,800만 픽셀)까지이며 브라우저 메모리에 따라 달라집니다.
버전과 출처
격자 측정은 Nerulio 자체 구현(src/game/grid-detect.js)이며, UI로 가져온 실제 에셋 16개에서 기본 선택으로 16개 모두 프레임이 맞았습니다. 필드 이름은 Phaser, 유니티 6, Godot 4.7 문서와 편집기 소스를 따랐고, 손으로 넣는 이 설정을 엔진에서 돌려 보지는 않았습니다. Godot 4.7.2, Unity 6000.5.3f1, Phaser 3.90·4.2에서 실제로 돌린 경로는 아틀라스 내보내기입니다.
참고한 공식 문서
자주 묻는 질문
열이 갈수록 프레임이 점점 밀려요.
시트에 프레임 간격이 있는데 엔진에는 0을 넣었거나, 간격까지 포함한 너비를 프레임 크기로 넣은 경우입니다. 잰 간격을 Phaser의 spacing이나 유니티의 Padding에, 잰 칸 크기를 프레임 크기에 넣으세요.
Godot hframes·vframes로 자르면 어긋나요.
hframes와 vframes는 텍스처 전체를 똑같이 나눌 뿐 여백과 간격을 모릅니다. 그래서 너비가 정확히 열 수 × 프레임 너비인 시트에만 맞습니다. 그 밖의 시트는 패킹 & 내보내기에서 SpriteFrames로 내보내면 AtlasTexture 영역이 프레임마다 정확한 픽셀 사각형을 가집니다.
왼쪽 여백과 위쪽 여백이 달라요.
Studio는 X와 Y를 따로 잽니다. 유니티 Offset은 둘 다 받지만, Phaser load.spritesheet는 margin과 spacing이 두 축 공통으로 하나씩뿐입니다. 이런 시트는 Phaser 아틀라스로 내보내세요.
잰 크기가 틀릴 수도 있나요?
있습니다. 그래서 조용히 자르지 않고 신뢰도와 대안을 함께 보여 줍니다. 실제 에셋 16개를 UI로 가져왔을 때 기본 선택으로 16개 모두 프레임이 맞았습니다.
무료인가요? 계정이 필요한가요?
이 페이지의 기능은 모두 무료이며 계정 없이 쓸 수 있습니다. 워터마크나 막혀 있는 내보내기가 없습니다.
스프라이트가 업로드되나요?
아니요. Studio는 이 브라우저 탭 안에서 디코딩·패킹·내보내기를 합니다. 프로젝트는 브라우저(IndexedDB)에 자동 저장되며 .nerulio 파일로 저장할 수 있습니다.
Aseprite나 엔진 플러그인을 설치해야 하나요?
아니요. .aseprite 파일은 브라우저에서 읽고 씁니다. 엔진별 번들에는 그 엔진에 필요한 것이 들어 있습니다(Godot는 가져오기 설정과 씬, Unity와 Godot 타일셋은 작은 편집기 스크립트).