로그인
← 스킨 제작

스킨 편집기 조작 설명서

스킨 팩토리의 스킨 편집기를 ChatGPT·Codex·Claude 같은 AI 비서가 브라우저로 조작할 때 알아야 할 것: 화면, 좌표계, 보기, 사이트 도구(WebMCP) 목록, 텍스처 규칙, 저장 규칙. 사람이 읽어도 같은 내용입니다.

같은 내용을 텍스트로: /llms.txt. 편집기 안에서는 사이트 도구 editor_manual이 같은 글을 돌려줍니다. AI를 연결하는 방법은 AI로 편집기 조작하기에, 긴 머리를 투명 종이카드로 만드는 방법은 긴 머리 종이 헤어카드 가이드에 있습니다.

1. 편집기가 하는 일

  • 게임의 기본 파츠로 만든 캐릭터(작업)의 머리(head)·몸(body) 점을 옮기고, 세 부위(head·body·face)의 텍스처를 칠하거나 그림으로 바꿉니다. 얼굴 판(face)은 게임 공용 모양이라 옮길 수 없고 색과 그림만 바꿉니다.
  • 작업은 /studio/editor/<id> 에서 열립니다. ?mode=ai 를 붙이면 AI 편집 배치(원본·현재 3면 띠 + 상태 텍스트)가 되고, 두 배치는 같은 상태를 보여 줍니다.
  • 편집은 로그인한 계정의 작업만 열립니다. AI가 쓰는 브라우저에 그 사람의 계정으로 로그인돼 있어야 하고, 주소만으로는 남의 작업이 열리지 않습니다.
  • 조작 경로는 둘입니다. ① 화면의 패널·버튼(모두 이름이 붙어 있습니다) ② WebMCP 사이트 도구 editor_*(브라우저가 지원하면 페이지가 자동 등록). 둘 다 같은 엔진을 움직이므로 섞어 써도 됩니다.
  • 캐릭터가 어떤 파츠로 만들어졌는지는 나중에도 바꿀 수 있습니다(「파츠」 패널 · editor_parts·editor_change_parts). 바꾸는 파츠는 원래 모습으로 들어오므로 그 파츠에 한 편집은 사라지고, 나머지 파츠의 편집은 남습니다.
  • 게임이 받지 않는 파츠가 몇 가지 있어 목록에서 빼 두었습니다. 그 파츠로 만들어진 예전 캐릭터는 그대로 열리지만, 「파츠」 패널과 editor_parts 가 그 사실을 알려 주고, 바꾸지 않으면 게임에서 열리지 않습니다.
  • 결과 파일은 사람이 [스킨 파일 받기]로 받습니다(게시판에 올릴지는 그때 고릅니다). AI는 저장까지만 합니다.

2. 좌표계와 이름

  • 단위는 모델 좌표입니다. 캐릭터 키가 약 0.58이고, 모든 좌표는 |값| ≤ 4 안에 있어야 합니다(저장할 때 검사).
  • 축: 위가 +y, 얼굴이 향하는 쪽이 −z, 정면에서 봤을 때 화면 오른쪽이 +x (캐릭터 자신의 왼손 쪽).
  • 게임 크기 상자: 게임 편집기는 점을 이 상자 안에서만 움직일 수 있습니다. 머리 x −0.35~0.35 · y −0.30~0.64 · z −0.31~0.53, 몸 x −0.62~0.62 · y −0.52~0.32 · z −0.31~0.53. 이 편집기도 옮기는 점(끌기·editor_transform·editor_set_points)을 상자 가장자리에서 멈춥니다. 상자와 그 밖의 점 수는 editor_status 의 sizeBox, 화면의 「크기 상자」 패널(하늘색 머리 상자·주황 몸 상자·빨간 점)에 있고, 예전에 상자 밖으로 만든 점은 editor_fit_box 나 [상자 안으로 맞추기]로 가장자리에 모읍니다. 날개·후광처럼 큰 장식도 이 상자 안에서 설계합니다.
  • 점은 「용접점」입니다: 같은 자리에 겹친 정점들을 한 점으로 묶은 것. id는 0부터 N−1이고 editor_status·editor_points·editor_select·editor_set_points 가 모두 이 id를 씁니다. 분리(editor_separate)로 나뉜 뒤에는 한 자리에 id가 둘 이상 있을 수 있습니다.
  • 이름 붙은 대상: head:piece:N(이어진 머리 조각, N은 큰 조각부터 0), body:slot:<이름>(몸 부위, 예 body:slot:body_part_3), face(얼굴 판 전체). 목록은 editor_targets 또는 「선택」 패널에 있습니다.
  • 몸 부위 가운데 body_part_0·body_part_7 과 left_hand_1·right_hand_1·left_hand_2·right_hand_2 는 손 모양(handPose 0·1·2)에 따라 하나씩만 보입니다.
  • 삼각형과 UV 섬에도 번호가 있습니다(editor_mesh). 삼각형 번호는 파일의 면 순서이고, UV 섬은 UV에서 변을 맞대고 이어진 삼각형 묶음입니다(큰 섬부터 0). 두 번호는 그 파츠에서 절대 바뀌지 않지만 점 id는 분리·붙이기·다시 열기에 바뀝니다. 오래 쓸 설계(어느 면을 무엇으로 쓸지)는 삼각형·섬 번호로 적어 둡니다. 섬은 <part>:island:N 으로 선택합니다.

3. 보기(카메라)

프리셋이름단축키yawpitch카메라
front정면10°0°얼굴 쪽(−z)에서
side측면290°0°정면 기준 화면 오른쪽(+x)에서
back후면3180°0°뒤(+z)에서
quarter대각445°0°앞 오른쪽 45°에서
otherSide반대 측면5-90°0°정면 기준 화면 왼쪽(−x)에서
backQuarter뒤 대각6135°0°뒤 오른쪽 45°에서
top위70°-90°머리 위에서. 화면 아래쪽이 얼굴 쪽
bottom아래80°90°발 아래에서
  • yaw는 캐릭터를 세로축으로 돌린 각(도)입니다. 양수면 카메라가 정면에서 화면 오른쪽으로 돕니다. pitch는 음수면 위에서 내려다보고 양수면 아래에서 올려다봅니다(±90°까지).
  • zoom은 확대 배율입니다: 1 = 화면에 맞춤, 2 = 두 배 크게, 0.25~4. pan은 화면 평면에서 모델을 미는 양(모델 단위, 오른쪽·위가 양수)입니다.
  • editor_view 로 프리셋·각도·확대·이동·표정(faceFrame 0~6: 기본·공격·피격 1·2·3·다운·승리)·손 모양(handPose 0~2)·원본 보기(showOriginal)를 바꿉니다. 화면에서는 「보기」 패널의 버튼과 숫자 필드, 마우스(오른쪽 버튼 드래그 회전, 휠 확대, Shift 드래그 이동)입니다.
  • 「보기 → 얼굴 표시」 또는 editor_face_visibility {visible: true|false}로 얼굴을 켜고 끕니다. 동물형 헤어는 기본 꺼짐이며, 선택은 저장·버전 복원·다운로드에 반영됩니다. 꺼도 얼굴 그림은 지워지지 않습니다. 켜도 헤어의 불투명한 면에 가려진 부분은 그대로 가려집니다. 일부 동물형 헤어는 얼굴을 켜서 내보낼 때 선택한 게임 서버에서만 제대로 보입니다.
  • 자세(pose): 기본은 rest, 곧 파일이 담고 있는 T포즈이고 편집은 여기에서만 합니다. idle(대기)·walk(걷기)·run(달리기)은 게임이 그 몸통에 실제로 쓰는 동작으로 캐릭터를 움직여 보는 **보기 전용** 자세입니다: 옷자락이 팔에 박히는지, 머리카락이 달릴 때 어떻게 보이는지 확인할 때 씁니다.
  • 동작은 몸통이 정합니다. 대부분은 사람의 걸음이지만 동물 룩(고릴라·개·돼지·쥐·닭)은 게임에서 전용 동작을 써서 네발로 걷거나 날개를 펴고 걷습니다. 그 몸통을 골랐으면 여기에서도 그 동작이 나오고, editor_status 의 view.poseMotion 이 어느 룩의 동작인지 알려 줍니다.
  • editor_view {pose} 로 고르면 게임 속도로 재생됩니다. poseFrame 을 주면 그 프레임에서 멈추고, posePlay 로 다시 재생·정지합니다. 프레임 번호는 게임 모션 파일의 번호라 몸통마다 다릅니다(사람은 대기 0~2·걷기 5~9·달리기 10~14). 지금 자세의 범위는 editor_status 의 view.poseRange 에 있습니다. 움직이는 자세에서는 화면을 끌어도 회전만 되고, 도구를 고르면 T포즈로 돌아옵니다. 좌표를 다루는 도구(editor_set_points·editor_transform)는 자세와 상관없이 언제나 T포즈 좌표로 움직입니다.
  • AI 편집 배치의 3면 띠는 원본(열었을 때)과 현재를 나란히 그립니다. 세트는 둘입니다: basic = 정면·측면·후면, angles = 대각·반대 측면·위 (editor_view {strip} 또는 띠 위의 버튼).
  • 어느 각도든 그림으로 보려면 editor_capture 입니다. 결과는 「보기」 패널의 「마지막 캡처」에 표시되고, returnData: true 면 PNG data URL도 돌려줍니다. parts 로 부위만 골라 그릴 수 있습니다.

4. 편집 흐름

  1. 캐릭터 그림(T포즈 정면, 흰 배경; 측면·후면이 있으면 훨씬 좋음)이 있으면 먼저 editor_build {front, side, back} 한 번입니다: 그림을 읽어 정렬하고(눈·발바닥을 스스로 찾음), 머리를 그림 윤곽대로 다시 짓고, 얼굴 표정 7칸을 그리고, 몸을 맞춰 옷을 칠합니다. 머리 파츠가 삼각형이 적으면(예: 헬멧 SP) 먼저 editor_change_parts {headId: "kingbfh3_1"}. 그림은 원본 크기 그대로(https 주소나 data:image URL) 줍니다. 정면·측면·후면이 한 장에 나란히 그려진 시트면 잘라 주지 말고 editor_build {sheet} 하나로: 인물마다 잘라(글자·확대 그림·팔레트는 버림) 맨 왼쪽의 넓은 T포즈를 정면, 다른 넓은 것을 후면, 좁은 것을 측면으로 씁니다(결과 sheet·notes에 배정). 결과의 found·notes로 무엇을 찾았는지 보고, editor_capture로 여러 각도를 확인한 뒤 아래 도구로 다듬습니다.
  2. editor_status 로 저장 상태·선택·보기를, editor_targets 로 조각·부위 목록을 읽습니다. 무엇을 고칠지는 사람이 적은 요청이 정합니다.
  3. editor_select 로 대상을 고릅니다: "all"·"none"·이름·좌표 범위·점 id. mode는 replace·add·remove.
  4. 옮깁니다: editor_transform(이동·회전·크기, pivot 기본은 선택 중심, falloff로 주변 점을 부드럽게) 또는 editor_set_points(점마다 절대 좌표). 얼굴 판은 옮길 수 없습니다.
  5. 붙어 있던 면이 따라오면 editor_separate: 떼어낼 부분의 점을 모두 선택한 뒤 부르면 그 부분만 움직입니다. editor_join 은 나뉜 점을 선택한 점 자리로 다시 붙입니다.
  6. 여러 면을 규칙으로 한꺼번에 옮길 때는 editor_shape 입니다: 쓰지 않는 면은 collapse 로 머리 안(또는 관절) 한 점에 접어 숨기고(몸 밖으로 끌어내지 않습니다), 머리카락 껍질은 wrap 으로 얼굴 판을 clearance 만큼 띄워 감싸고(onto silhouette 이면 참고 그림 윤곽대로), 종이카드는 card 로 UV 사각형을 3D 사각형에 펴고, 한쪽만 고친 뒤 mirror 로 반대쪽을 맞추고, 팔다리는 thicken 으로 굵기를 바꿉니다. 그다음 editor_paint 로 그림을 입힙니다.
  7. 색은 editor_recolor(선택한 부위의 텍스처만), 그림 전체는 editor_texture(data:image URL 또는 https 주소), 지금 그림을 읽으려면 editor_texture_read.
  8. 참고 그림(정면·측면·후면)이 있으면 머리는 editor_rebuild_head 로 한 번에 짓습니다: 그림의 윤곽(정면 ∩ 측면)대로, 얼굴 판에서 clearance 만큼 띄운 고른 껍질을 머리 파츠의 삼각형으로 만들고, 얼굴(코에서 이어진 피부)과 턱 아래는 열어 두고, 그림을 칠합니다. 삼각형이 많은 머리 파츠가 매끈합니다(페더칵투스 kingbfh3_1 권장; 헬멧 SP met2sp처럼 텍셀을 좌우가 나눠 쓰는 파츠는 쓸 삼각형이 거의 없습니다). 긴 머리는 bottom을 낮게(어깨 −0.05, 허리 −0.2), 그다음 editor_face 로 얼굴을 입힙니다.
  9. 몸은 editor_fit_body 한 번입니다: T포즈 정면(필수)·측면·후면 그림을 주면 그림과 모델의 발바닥·가랑이·팔 높이를 맞춰(그림의 다리가 길어도 바지가 다리에) 몸통·다리·팔 굵기를 그림 윤곽에 맞추고, 도너 옷의 리본·벨트 같은 작은 조각은 몸 안으로 접고, 윤곽 밖으로 튀어나온 점은 안으로 당긴 뒤 옷을 칠합니다. 손이 크면 hands: 0.8. 캐릭터에 꼬리가 있으면 등에 떨어진 조각이 있는 몸 파츠를 고르세요: 사람이면 sasupe5_1·delf6_1(짐승 몸 dog_sb·mouse_wh는 대기 자세가 네 발, tiger_w는 팔·다리가 텍스처를 같이 씀). 측면 그림의 꼬리 모양·굵기대로 구부립니다.
  10. 긴 머리: editor_build는 후면 그림에서 머리카락 색이 내려오는 만큼 재서 머리를 늘입니다(턱 아래는 머리카락 색만 머리로 봐서 몸을 감싸지 않음). 직접 하려면 editor_rebuild_head에 bottom(끝 높이), hairColors(머리카락 색), hairBelow(턱 높이)를 줍니다.
  11. 날개는 editor_wings(editor_build는 그림에 날개가 있으면 저절로): 후면 그림에서 머리·몸 밖의 큰 조각(등 위쪽에 뿌리, 몸통 폭 이상)을 날개로 찾아, 머리 파츠에서 머리카락이 안 쓰는 삼각형으로 날개 판을 만들고 그림을 칠합니다. 머리 파츠의 삼각형을 나눠 쓰므로 날개가 크면 머리카락 껍질이 거칠어지고, 게임의 머리 크기 상자에 맞춰 작아집니다.
  12. 카드 머리: 머리 파츠를 양면인 헬멧 SP(editor_change_parts {headId: "met2sp"})로 바꾸고 editor_build를 하면 머리카락이 카드로 만들어집니다 — 돔·챙이 머리 위 껍질(얼굴 둘레는 칠하지 않고 가장 가까운 머리카락 색), 턱끈이 얼굴 옆 머리, 긴 머리면 고글 판이 등 뒤 커튼(후면 그림 그대로, 머리카락 밖은 투명). 외곽선이 없어 머리끝이 깔끔하지만 삼각형이 110개라 둥근 정도는 페더칵투스(kingbfh3_1)의 차트 머리보다 거칠고, 좌우가 같은 텍셀이라 좌우가 같게 칠해집니다. 뿔·날개를 붙일 남는 삼각형은 없습니다.
  13. 마스코트(얼굴까지 머리로 덮음): editor_build는 머리가 게임 크기 상자보다 크면 머리 전체를 바닥 쪽을 중심으로 줄이고(칠도 같은 배율), 그림의 목이 몸 위에 오게 머리를 올리며, 경단·둥근 귀처럼 둥근 조각은 공으로 따로 만들어 머리가 경단 폭만큼 각지지 않게 합니다(lobes: false로 끔).
  14. 뿔·뾰족한 귀·바보털은 editor_horns(editor_build는 저절로): 머리카락 껍질은 점 간격이 8° 남짓이라 그보다 가는 것은 빌드마다 잡혔다 빠졌다 합니다. 정면 그림에서 머리에 붙은 가는 조각을 찾아 하나씩 마름모 단면 기둥으로 만들고(폭은 정면, 깊이·기울기는 측면 그림), 머리카락 외형에서는 뺍니다.
  15. 사람 캐릭터의 얼굴은 editor_face 한 번이면 됩니다: 정면 그림과 그 그림 속 두 눈동자 위치(원본 픽셀)를 주면, 게임 얼굴 판의 눈 자리에 맞춰 그림을 입히고 표정 7칸을 게임 얼굴처럼 그립니다. SD 캐릭터처럼 눈이 크고 턱이 짧으면 chin(턱 끝)도 주면 턱이 얼굴 판의 턱에 맞습니다. 입을 엉뚱한 곳에서 찾으면 mouth를 줍니다. 눈을 또렷하게 하려면 size: 256.
  16. 참고 그림을 캐릭터에 입히려면 editor_paint 의 project·views 입니다: 그림을 모델 위로 비춰서, 텍셀마다 모델에서 놓이는 자리의 그림 색을 칠합니다. UV를 몰라도 되고, 정면·측면·후면을 같이 주면 면마다 가장 잘 보이는 그림을 씁니다. 단색·그라데이션·명암·원래 그림 되돌리기도 같은 도구입니다.
  17. 도구로 안 되는 계산(삼각형을 하나씩 떼어 다시 놓기, UV 섬을 펼쳐 그림에 맞추기, 대기 자세에서 설계하기 같은 것)은 직접 짠 코드로 합니다: editor_script {code} 로 이 페이지의 격리된 곳에서 돌리거나, 작업 꾸러미를 받아 내 컴퓨터에서 계산한 뒤 editor_apply_result 로 넣습니다. 형식과 도우미는 「10. 직접 계산」.
  18. 확인합니다: 3면 띠, editor_capture, editor_status 의 selection(범위·중심). 잘못됐으면 editor_undo/editor_redo.
  19. 고칠 때마다 editor_compare {views, images: true} 로 그림과 겹쳐 보고(빨강 = 그림에만 있음, 파랑 = 모델이 넘침, 노랑 = 색이 다름) iou가 올랐는지 확인합니다. T포즈 그림의 다리 벌림과 팔 끝은 게임 대기 자세와 달라도 괜찮습니다.
  20. 저장 전에 editor_check 로 남은 문제(안 칠한 조각, 얼굴을 파고든 머리, 줄무늬, 크기 상자 밖)를 세고, problems의 fix대로 고친 뒤 ok가 될 때까지 다시 부릅니다.
  21. editor_save {note} 로 저장합니다. note는 이 버전의 한 줄 설명(약 150자)입니다. 게시는 하지 않습니다.

5. 사이트 도구(WebMCP) 목록

지원되는 브라우저에서 편집기 페이지가 등록합니다. 입력은 JSON 객체이고 결과도 JSON입니다. 모든 도구는 화면의 버튼과 같은 엔진 메서드를 부르므로 되돌리기·저장에 그대로 잡힙니다.

도구입력결과·비고
editor_manual—이 설명서 텍스트
editor_buildfront(필수), side, back(그림 주소·data URL), head·faceDraw·body(false로 건너뜀), face("cover" 마스코트), hairBottom, hands{found, views, notes, head, face, body}. 그림으로 머리·얼굴 7칸·몸을 한 번에(단계마다 되돌리기 단계)
editor_referencefront(필수), side, back{front, side, back: 그대로 쓸 수 있는 보기(view·image·place), found(피사체·눈·측면 눈과 방향·확신도), notes}. 읽기만 함
editor_skills—이 캐릭터에 사용자가 등록한 스킬·버전과 전체 작업 지침. 편집 전 읽고 이번 요청의 범위 안에서 적용한다. 등록만으로는 스킨이 바뀌지 않는다.
editor_status—work(id·name·serverRevision·unsaved·saveState), view(yaw·pitch·zoom·pan·preset·faceFrame·handPose·showOriginal·strip·pose·poseMotion·poseFrame·poseRange·posePlaying), tool, selection(part·points·ids 최대 200개·min·max·center·size), separatedPlaces, textures(크기·fileTier), history, lastAction, capture
editor_targets—head: [{id: "head:piece:N", points, center, size, placement}], body: [{id: "body:slot:이름", label, hidden, center, size}], face
editor_meshpart; detail: summary(기본)·islands·triangles; 거르기 islands [N…]·triangles [N…]·target("<part>:island:N"·"head:piece:N"·"body:slot:이름")·range; limit(기본 300, 최대 1000)summary = 그룹, 텍스처 사용률, 큰 UV 섬 20개, 같은 텍셀을 쓰는 섬 쌍(mirror 좌우 짝·duplicate 같은 면 두 겹·overlap), UV 넓이 0인 면. 섬마다 center·normal(바깥쪽)·texels·density(1cm²당 텍셀)·disk·mirror·shares. triangles = 삼각형마다 points(지금 점 id)·uv(텍셀 좌표, 위가 0)·island·area·texels·zeroUv
editor_selecttarget: "all"·"none"·"head:piece:N"·"body:slot:이름"·"<part>:island:N", 또는 part + range {x: [min, max], y, z}(null = 제한 없음), 또는 part + points [id…], 또는 part + triangles [삼각형 번호…]; mode선택 요약. "all" 에는 part가 필요합니다
editor_pointspart; 그리고 ids / target / range / selection: true 가운데 하나(없으면 전체); original: true 면 열었을 때 좌표{part, count, points: [[id, x, y, z], …]}
editor_set_pointspart(head·body), points: [[id, x, y, z], …]{moved, clampedToBox?}. 한 번의 되돌리기 단계. |좌표| ≤ 4, 게임 크기 상자 밖은 가장자리로 잘림
editor_transformtranslate [dx, dy, dz], rotate [도x, 도y, 도z], scale [sx, sy, sz], pivot [x, y, z], falloff{moved}. 선택이 있어야 합니다. 옮긴 점은 게임 크기 상자 안으로 잘립니다
editor_shapepart(head·body), op: collapse·wrap·card·mirror·thicken, where {islands, triangles, target, selection}, separate; collapse: at; wrap: centre, onto("face"·"current"·{ellipsoid}·{superellipsoid}·{silhouette: {views}}), offset, clearance, scale, amount; card: corners [4점], bend; mirror: from(x+·x-); thicken: across, along, axis{moved, triangles, separated?, blocked?, clampedToBox?, missed?, unmatched?}. 한 번의 되돌리기 단계(떼어내기 포함). 삼각형·섬 번호로 고르므로 분리 후에도 그대로 쓸 수 있습니다
editor_rebuild_headviews(editor_paint와 같은 보기: 정면 필수, 측면·후면 권장), eyes(정면 그림의 두 눈동자), face("open"·"cover"), bottom(머리카락 끝 높이), hairColors?, clearance?, seats("charts" 기본·"apart"·"all"), budget?, paint?, size?{shell {triangles, columns, rings, seatsClean, seatsApart, kept}, folded, hair, paint}. 머리 파츠로 참고 그림 윤곽의 머리카락 껍질을 새로 짓고 그림을 칠함(모양·칠하기 되돌리기 단계). 얼굴 창은 열어 둠
editor_fit_bodyviews(정면 필수·측면·후면), reshape?(기본 true), hands?(손 배율), extras?("fold"·"keep"), tail?("auto"·"none"·"keep"), claws?("keep"·"fold"), centre?("torso"·"given"), anchors?("auto"·"none"), limits?, paint?, size?{marks {model, picture}, rows, slices, foldedPieces, pulledInside, moved, tail, paint}. 꼬리 있는 몸 파츠는 측면 그림의 꼬리대로 구부림. 발바닥·가랑이·팔 높이를 그림과 맞추고, 몸통·다리·팔을 그림 윤곽대로, 옷을 칠함(모양·칠하기 되돌리기 단계)
editor_hornsviews(editor_reference의 결과), eyes?, budget?, apart?, paint?{horns, triangles, seatsFree, found: [{root, tip, angle, length, depth: side|width, scale}], paint}. 정면 그림 머리의 가는 조각(눈 간격 절반 원판이 닿지 못하는 곳, 얼굴 쪽 머리에 붙은 것, 이마 아래는 위로 솟은 것만)을 뿌리→끝 마름모 기둥으로: 앞면은 정면 그림, 뒷면은 후면 그림으로 칠함. 뿌리는 머리 껍질 1 cm 안쪽
editor_wingsviews(정면 필수·후면 권장), sweep?(뒤로 젖힘, 기본 45°), budget?, apart?, paint?{wings, triangles, scale, below, roots, found, paint}. 후면(없으면 정면) 그림에서 머리·몸 밖의 큰 조각을 날개로 찾아 머리 파츠의 남는 삼각형으로 앞뒤 두 겹 판을 만들고 칠함. 머리와 몸을 만든 뒤에 부름
editor_compareviews(editor_reference의 결과), images?{iou, views: [{view, iou, missing, extra, colourError, regions, diff?}]}. 만든 캐릭터를 참고 그림과 같은 배율·위치로 그려 겹쳐 봄: 윤곽 겹침·빠진 곳·넘친 곳·색 차이와 그 위치. 읽기만 함
editor_check—{ok, problems: [{part, kind, count, where, says, fix}]}: unpainted·transparent-outline·stripes·hair-in-face·folded-outside·outside-box. 읽기만 함, 저장 전에 부름
editor_fit_boxpart(head·body, 없으면 둘 다){moved, sizeBox} — 상자 밖의 점을 모두 상자 가장자리로. 한 번의 되돌리기 단계
editor_separate—{separatedPoints, selection}
editor_join—{joinedPlaces}
editor_recolorhue(도), saturation·lightness(배율) 또는 fill [r, g, b] + keepShading; allFrames(얼굴){changedTexels}
editor_paintpart; frames(얼굴 필수: "all" 또는 [0..6]); where {islands, triangles, target, selection, box, sphere, facing {toward, min}}; source 하나: color · gradient {along, from, to, stops} · original("opened"|"part") · project {view, image, place, background, backgroundAs, fitWidth, symmetric, visibleOnly, useAlpha} · views [보기…] + mix·power·fallback·outlines(보기 윤곽 안쪽 띠, 피사체 키의 비율: 거기 떨어지는 점은 그 보기가 먼저; 보기에 outline: false면 빠짐); blend(over·replace·multiply); light {direction, strength}; shared(average·first·skip); bleed; size(64·128·256로 먼저 바꿈){painted, skippedHidden, outsideImage, transparent, bled, conflicts, alsoShownOn, coverage, place [{view, pxPerUnit, pxPerTexel, residualPx}], warnings}. 한 번의 되돌리기 단계. 정렬이 3% 넘게 어긋나거나 그림 좌우가 반대면 거절
editor_faceimage(정면 얼굴 그림), eyes [[X, Y], [X, Y]](그림 속 두 눈동자, 원본 픽셀), chin?, mouth?, background?, expressions?(기본 true), size?(64·128·256), wrap?, paintNeck?(기본 false: 아래턱 안쪽 목 판은 뒤에서만 보여 원래 색을 둠){painted, expressionTexels, plate {eyes, mouth, chin}, placed {pxPerUnit, eyes, mouth, eyeSpacingVsGame}, colours, neckKept, warnings}. 표정 7칸: 0은 그림 그대로, 1 공격·2 피격 1(한쪽 찡그림)·3 피격 2(><)·4 피격 3(놀람)·5 다운(감은 눈)·6 승리(^^)는 눈·입만 다시 그림. 한 번의 되돌리기 단계
editor_texturepart, url(data:image/… 또는 https://…){applied, textures} 또는 {error}
editor_texture_readpart{dataUrl, width, height, frames, frameSize} — 저장 전의 지금 그림
editor_viewpreset, yaw, pitch, zoom, pan [x, y], faceFrame, handPose, showOriginal, strip, pose(rest·idle·walk·run), poseFrame, posePlayview
editor_capturepreset 또는 yaw·pitch(둘 다 없으면 지금 보기 그대로), zoom, pan, parts [head|body|face…], showOriginal, outline, selection, box(게임 크기 상자), width, height(기본 480×540, 최대 1024), returnData; 또는 uvMap: head|body|face + withTexture, highlight [섬…]{label, width, height, dataUrl?}. 「보기」 패널의 「마지막 캡처」에도 표시. uvMap은 캐릭터 대신 UV 배치도(섬마다 색·번호, 두 섬이 같이 쓰는 텍셀 빗금, UV 넓이 0인 면 빨강)
editor_scriptcode(아래 함수의 본문), pictures {이름: https 주소·data:image URL}(6장까지), timeout(ms, 기본 20000, 최대 120000), dryRun, label{applied, parts {head·body: movedSlots·separatedPoints·clampedToBox·points, texture}, report, logs, ms} 또는 {error, line, logs}. 코드는 async (input, lib, log) => { … } 의 본문으로, 계정·서버·인터넷에 닿지 못하는 격리된 곳에서 돕니다. 결과는 한 번의 되돌리기 단계. 「10. 직접 계산」
editor_apply_resultresult(getskin-result 객체), dryRun{applied, parts, report} 또는 {error}. 내 컴퓨터에서 계산한 결과를 넣음. 한 번의 되돌리기 단계
editor_bundle—{file, bytes, parts, welds}. 지금 상태의 작업 꾸러미(<이름>-r<버전>.getskin.json)를 내려받기 폴더에 저장. 내 컴퓨터에서 계산할 때
editor_undo / editor_redo—{undone} / {redone}, lastAction
editor_savenote{saved, revision, message}
editor_parts— 또는 find {kind: hair|body|eye|nose|mouth, query, length, limit}{parts, names, unusable[], found?} — 지금의 파츠와 게임이 받지 않는 파츠, 그리고 목록 검색
editor_change_partsheadId, bodyId, face {eye, nose, mouth} 중 바꿀 것만{changed, parts, revision}. 바꾼 파츠의 편집은 사라지고 나머지는 남습니다. 저장하지 않은 변경은 먼저 저장되고, 바꾸기 전 상태는 버전으로 남습니다. 형태가 통째로 바뀌므로 선택과 되돌리기 기록은 초기화됩니다

6. 텍스처(그림) 규칙

  • 텍스처는 셋입니다: 머리·몸은 정사각형 한 장, 얼굴은 표정 7칸(기본·공격·피격 1·2·3·다운·승리)을 가로로 이어 붙인 7:1 아틀라스. 표정 칸 안의 눈·코·입 위치는 게임이 정한 것이라 그 자리에 맞춰 그립니다.
  • 허용 크기: 머리 64·128·256, 몸 128·256, 표정 한 칸 64·128·256 (최대 256). 그림을 넣으면 그림이 담기는 가장 작은 허용 크기가 되고, 그보다 크면 256으로 줄입니다. 크기가 파일 종류를 정합니다: 머리·얼굴이 128을 넘으면 .hhskin, 64를 넘거나 몸이 128을 넘으면 .hskin, 아니면 .skin.
  • 비율: 머리·몸 1:1, 얼굴은 7:1(아틀라스 전체) 또는 1:1(보고 있는 표정 한 칸만 교체). 둘레에 단색 여백이 붙은 그림은 여백을 잘라 쓰고, 그 밖의 비율은 거절합니다(늘리면 UV가 어긋납니다).
  • https 주소의 그림은 서버가 대신 받습니다(사설 주소·localhost 불가, 12MB, PNG/JPG/WEBP/GIF). data:image URL은 브라우저가 바로 읽습니다.
  • 그림 투영(editor_paint project·views)의 정렬: place.pairs 는 그림의 점 [X, Y](원본 그림의 픽셀)와 모델의 점 [x, y, z] 쌍 둘 이상입니다. 얼굴이면 두 눈동자가 가장 정확합니다. 보기는 편집기 카메라와 같습니다: front 는 +x가 화면 오른쪽, side 는 +x 쪽에서 본 모습(얼굴이 화면 왼쪽), otherSide 는 −x 쪽(얼굴이 화면 오른쪽), back 은 +x가 화면 왼쪽. 그림의 몸이 게임 몸보다 길거나 머리 비율이 다르면 place.rows [[모델 높이, 그림 행], …]로 세로를 구간별로 맞추고, fitWidth 로 행마다 폭을 맞춥니다.
  • 흰 종이 위에 그린 그림은 background: "auto" 를 줍니다. 테두리와 이어진 배경색만 빠지고(검은 선 안의 흰 옷은 남음), 배경에 떨어진 텍셀은 다른 보기에 맡깁니다. 모델이 그림 윤곽보다 튀어나온 곳은 backgroundAs: "nearest" 로 윤곽의 색을, 종이 헤어카드는 "transparent" 로 투명을 줍니다. 결과의 pxPerTexel 이 1보다 작으면 그림이 텍스처보다 거친 것이니 원본 크기 그림을 씁니다. 텍스처가 128이면 size: 256 으로 올리면 그림이 더 선명하게 들어갑니다(파일 종류가 .hhskin 이 될 수 있습니다).
  • 그림을 만들기 전에 editor_mesh {part} 와 editor_capture {uvMap: part, withTexture: true} 로 어느 섬이 어느 면인지, 어느 섬끼리 텍셀을 같이 쓰는지 봅니다. 같이 쓰는 섬(좌우 팔다리, 두 겹 판, 손 모양 3종)은 한쪽만 다르게 칠할 수 없습니다. UV 넓이 0인 면은 한 줄의 텍셀이 면 전체에 늘어나 줄무늬가 되니 단색으로 둡니다.
  • 그릴 때: 알파 10% 미만 텍셀은 그려지지 않지만 외곽선 껍질은 알파를 무시하므로, 투명한 부분이 넓은 삼각형은 뒤에서 보면 어두운 테두리로 남습니다. 불투명한 그림은 삼각형 안에 맞춥니다. UV에서 맞닿은 영역은 경계 1텍셀이 서로 번지니 한 텍셀 여유를 둡니다. 「아틀라스」 패널의 UV 선(주황 = 선택한 부위)으로 어느 텍셀이 어느 면인지 봅니다.

7. 저장·버전

  • 저장은 바뀐 것이 있을 때만 서버에 갑니다. 서버 버전은 r1, r2…로 오르고 최근 20개를 보관합니다. 「저장한 버전」 패널의 [복원]은 새 버전으로 되돌립니다(되돌리기 단계가 아닙니다).
  • 저장하지 않은 상태는 새로고침하면 사라집니다. 다른 탭에서 먼저 저장했으면 버전이 어긋나 저장이 거절되니, 새로고침한 뒤 다시 합니다.
  • editor_change_parts 로 파츠를 바꾸면 그 자리에서 새 버전이 됩니다. 바꾸기 전 버전에는 그때의 파츠가 적혀 있어, [복원]하면 파츠도 함께 돌아옵니다.
  • 저장된 상태는 GET /api/editor?model=<id>(JSON: head·body·face 의 positions·unique·uvs·groups, versions)와 GET /api/editor?texture=<part>&id=<id>(PNG, &which=original 은 열었을 때 그림)로도 읽을 수 있습니다. 저장 전의 상태는 editor_points·editor_texture_read 로 읽습니다.

8. 화면으로만 조작할 때 (WebMCP 없이)

  • 패널 id: #editor-status(상태 텍스트, JSON 펼침), #editor-selection(목록·범위 선택), #editor-separate, #editor-transform(dx·dy·dz / rx·ry·rz / sx·sy·sz + [이동][회전][크기], 기준점), #editor-color, #editor-atlas(그림 붙여넣기·주소로 넣기·PNG 내려받기), #editor-tools, #editor-view(프리셋·각도·확대·캡처), #editor-parts(지금의 파츠 · [파츠 바꾸기]), #editor-versions, #editor-help. 마지막 동작은 #editor-last-action, 저장 상태는 #editor-save-state.
  • 단축키: 도구 Q 선택 · W 이동 · E 회전 · R 크기 · T 칠하기 · C 카메라. 보기 1~8. V 분리. Esc 선택 지우기. 화살표·PageUp/PageDown 은 선택을 0.005씩 옮깁니다(Shift 0.02). Ctrl+Z / Ctrl+Y 되돌리기·다시 실행, Ctrl+S 저장.
  • 화면 선택: 클릭 = 가까운 점 하나, 드래그 = 상자 안의 보이는 점, 더블클릭 = 이어진 조각·부위, Shift 추가, Alt 제외. 「뒷면 점도 선택」을 켜면 상자가 가려진 점도 고릅니다.
  • 그림 넣기: 파일 선택 창을 못 쓰면 「아틀라스」 패널의 주소 필드에 data:image URL이나 https 주소를 넣고 Enter, 또는 페이지에 붙여넣기(Ctrl+V).

9. 주의

  • 스킨 파일 받기와 게시판 게시·삭제는 사람이 합니다. 로그인 정보나 API 키는 어디에도 넣지 않습니다.
  • 스킨 파일(.skin·.hskin·.hhskin)이나 OBJ는 편집기에 넣을 수 없습니다. 그림(PNG/JPG/WEBP)만 들어갑니다.
  • 큰 편집은 저장 사이에 나눠 되돌릴 수 있게 하고, 저장할 때 note에 무엇을 했는지 적습니다.
  • 긴 머리·앞머리·땋은 머리를 투명 종이카드로 만들 때는 먼저 /guide/hair-cards(긴 머리 종이 헤어카드 가이드)를 읽습니다. 파츠 고르기, 원본 그림 투영, 얼굴 외곽선 틈을 피하는 규칙이 있습니다.

10. 직접 계산: 작업 꾸러미와 계산 코드

편집기 도구로 안 되는 계산은 직접 짠 코드로 합니다. 길은 둘이고 주고받는 데이터는 같습니다. 어느 길이든 결과는 한 번의 되돌리기 단계로 들어가고, 저장해야 서버에 남습니다.

  • 이 페이지 안에서: editor_script {code} 또는 「직접 계산」 패널의 코드 칸. 코드는 출처 없는 격리 프레임 속 worker에서 돌고 네트워크가 막혀 있어(로그인 정보·서버·인터넷에 닿지 못함) 받은 데이터로 계산한 결과만 돌려줍니다. 기본 20초, 최대 120초가 지나면 멈춥니다. 메모리를 아주 많이 쓰면 탭이 멈출 수 있으니 큰 계산 전에 저장합니다.
  • 내 컴퓨터에서: 「직접 계산」 패널의 [작업 꾸러미 받기] 또는 editor_bundle 이 <이름>-r<버전>.getskin.json 을 내려받습니다(텍스처는 PNG). 파이썬이든 무엇이든 계산해서 결과 JSON을 [결과 올리기] 또는 editor_apply_result {result} 로 넣습니다. Codex·Claude Code처럼 내 컴퓨터에서 코드를 돌리는 AI의 길입니다.
  • 바뀌지 않는 번호는 슬롯(OBJ 정점)과 삼각형입니다. 점 id는 분리·붙이기에 바뀌니, 결과는 슬롯으로 주는 것이 안전합니다. 한 점에 묶인 슬롯을 서로 다른 곳에 두면 그 점은 나뉘고(editor_separate 와 같음), 저장할 때 분리로 기록됩니다.
  • 남이 준 코드는 무엇을 하는지 알 때만 돌립니다. 계정에는 닿지 못하지만 작업 모양은 망칠 수 있습니다(되돌리기로 복구).
꾸러미(getskin-work)뜻
parts.<head|body|face>.positions[slot]슬롯의 지금 좌표 [x, y, z](T포즈). original 은 열었을 때. face는 editable: false(모양 고정, 텍스처만)
point[slot], points, welds슬롯이 묶인 점 id(editor_points 의 id)와 그 수, 묶음의 이름
triangles[t]삼각형 t의 세 슬롯 [a, b, c]. t는 editor_mesh 의 삼각형 번호
uv[t]세 꼭짓점의 텍셀 좌표 [[x, y], …](한 칸 크기 기준, 왼쪽 위가 0)
group[t], groups[g]{name, label, joint, triangles: [처음, 끝), hidden(손 모양), doubleSided}
island[t], islands[i]{triangles, texels, uvBox, center, normal, disk, mirror(좌우 짝), shares(같은 텍셀을 쓰는 섬: 한쪽만 칠할 수 없음)}
zeroUvUV 넓이가 0인 삼각형(칠하면 줄무늬)
texture{width, height, frames, frameSize, sizes(결과가 쓸 수 있는 칸 크기), png(파일) 또는 data(RGBA, 코드)}. 얼굴은 표정 7칸이 가로로 이어짐
pose.idle.joints[joint], pose.rootJoint대기 자세 첫 프레임의 4×4 행렬(열 우선, T포즈 → 대기). 삼각형은 그 그룹의 joint로 움직이고, 목록에 없으면 rootJoint로
sizeBox, coordinateLimit게임 크기 상자(밖의 좌표는 가장자리로 잘림)와 좌표 한계 ±4
결과(getskin-result)뜻
format: "getskin-result", version: 1형식 표시(코드에서 돌리면 생략해도 됨)
parts.<head|body>.slots[[슬롯, x, y, z], …]. 주지 않은 슬롯은 그대로
parts.<head|body>.points[[점 id, x, y, z], …]: 그 점의 모든 슬롯. 꾸러미의 welds 를 그대로 돌려줘야 받습니다(그사이 분리·붙이기가 있으면 거절)
parts.<part>.texture텍스처 전체: PNG data URL 또는 {width, height, data(RGBA)}. 크기는 texture.sizes 가운데 하나(얼굴은 7칸 전체 폭). 늘이거나 잘라 넣지 않습니다
label, report되돌리기 기록에 붙는 이름, 결과로 돌려받을 짧은 메모(JSON 4000자)
도우미 `lib` (코드 칸·`editor_script`)하는 일
lib.part(name)꾸러미의 부위 (input.bundle.parts[name])
lib.texture(name, size?)쓰기용 텍스처 사본(같은 객체가 돌아옴). size를 주면 그 크기의 빈(투명) 텍스처
lib.getTexel(tex, x, y), lib.setTexel(tex, x, y, [r, g, b, a])텍셀 하나 읽기·쓰기
lib.forEachTexel(name, t, (x, y, w0, w1, w2) => …, {frame, size})삼각형 t가 그리는 텍셀마다(4×4 표본 중 하나라도 안이면): 텍스처 좌표(얼굴은 frame 칸 위치를 더함)와 텍셀 중심의 무게중심 좌표
lib.pointAt(name, t, w0, w1, w2), lib.centreOf(name, t), lib.normalOf(name, t)삼각형 위의 3D 점·중심·법선((b−a)×(c−a)). lib.move로 옮긴 좌표 기준
lib.sample(picture, x, y)그림의 픽셀 좌표 색 [r, g, b, a](이중 선형). 그림은 input.pictures[이름] = {width, height, data}
lib.move(name, slot, [x, y, z]), lib.movePoint(name, id, [x, y, z]), lib.positionOf(name, slot)슬롯·점 옮기기(모아 두었다가 결과로)
lib.toIdle(name, t, p), lib.fromIdle(name, t, p), lib.idleMatrix(name, t)삼각형 t의 관절로 T포즈 ↔ 대기 자세. 대기 자세에서 설계하고 fromIdle로 T포즈 좌표를 얻습니다
lib.result({label, report})옮긴 슬롯과 lib.texture 로 받은 텍스처로 결과를 만듦. 코드가 아무것도 return하지 않으면 이것이 들어갑니다
log(…)결과의 logs로 돌아오는 출력(console.log도 같음)

예: 정면 그림을 머리의 앞쪽 면에 비춰 칠하고, UV 섬 3을 1 cm 바깥으로 밉니다(editor_script {code, pictures: {front: "https://…"}}).

const head = lib.part('head');
const tex = lib.texture('head');
const pic = input.pictures.front;
// 그림의 px/모델 단위와 눈 높이의 픽셀 행은 editor_reference의 결과에서 옮겨 적습니다.
const scale = 900, centreX = pic.width / 2, eyeRow = 300, eyeY = 0.155;
head.triangles.forEach((_, t) => {
  if (lib.normalOf('head', t)[2] > -0.3) return; // 앞(−z)을 보는 면만
  lib.forEachTexel('head', t, (x, y, a, b, c) => {
    const p = lib.pointAt('head', t, a, b, c);
    const rgba = lib.sample(pic, centreX + p[0] * scale, eyeRow - (p[1] - eyeY) * scale);
    if (rgba[3] > 128) lib.setTexel(tex, x, y, rgba);
  });
});
// 섬 3을 점마다 그 섬 면들의 평균 법선으로 1 cm 밉니다. 같은 점의 슬롯은 같은 곳에 두어야 섬이 붙어 있고,
// 섬 밖 면과 같이 쓰던 점은 섬 쪽 슬롯만 옮겨지므로 그 자리에서 떨어집니다(분리).
const byPoint = new Map();
for (const t of head.islands[3].triangles) {
  const n = lib.normalOf('head', t);
  for (const slot of head.triangles[t]) {
    const e = byPoint.get(head.point[slot]) ?? {slots: new Set(), n: [0, 0, 0]};
    e.slots.add(slot);
    e.n = e.n.map((v, k) => v + n[k]);
    byPoint.set(head.point[slot], e);
  }
}
for (const {slots, n} of byPoint.values()) {
  const l = Math.hypot(...n) || 1;
  for (const slot of slots) lib.move('head', slot, head.positions[slot].map((v, k) => v + (n[k] / l) * 0.01));
}
return lib.result({label: '정면 그림 칠하기 + 끝 밀기'});