레벨 형식
레벨은 폴더입니다: 내용은 level.json 또는 level.blob, 메타데이터는 metadata.json 또는 metadata.blob, 그리고 커버와 미디어 파일. 백업, 기록, 플레이어 설정은 기기에 남습니다
레벨 폴더
레벨 폴더의 모든 파일 이름은 FileNames에 고정되어 있습니다:
| 파일 | 모델 | 내용 |
|---|---|---|
level.json 또는 level.blob | Level | 내용: 설정, 오브젝트와 이벤트, 오디오, 리소스, 힌트 |
metadata.json 또는 metadata.blob | LevelMeta | 제목, 설명, 제작자, 라이선스, 리소스마다 하나씩의 기록, 커버 참조 |
logo.png 또는 logo.jpg | - | 커버 |
| 트랙, 이미지, 폰트 | - | 레벨이 참조하는 미디어 파일 |
확장자가 곧 형식입니다. 형식은 파일 안 어디에도 기록되지 않습니다. 읽는 쪽은 어떤 파일이 있는지 봅니다: level.json인지 level.blob인지
레벨과 메타데이터는 각자 형식을 따로 고릅니다. 예를 들어 내장 레벨 new-zero-demo에는 level.blob 옆에 metadata.json 파일이 있습니다
metadata.json 파일은 일부러 분리되어 있습니다. 카탈로그는 작은 메타데이터 파일 천 개로 레벨 천 개를 보여 줍니다. 그러기 위해 레벨을 하나도 열지 않습니다
리소스
리소스는 uri, uri_type(ResourceUriType) 한 쌍으로 지정됩니다:
| 값 | 타입 | 파일 위치 |
|---|---|---|
| 1 | LevelPath | 레벨 폴더 안. 이 타입만 레벨을 이동 가능하게 만듭니다 |
| 2 | AbsolutePath | 이 기기의 다른 곳 |
| 3 | DirectUrl | 네트워크에서 다운로드 |
| 4 | StreamingAssets | 레벨이 아니라 게임과 함께 배포됨 |
AbsolutePath 타입은 곡을 중심으로 레벨을 만들 때 생깁니다. 아카이브로 내보내면 그런 파일은 안으로 복사됩니다. 자세히 - 아카이브와 보호
커버는 다른 리소스와 마찬가지로 LevelMeta.LevelLogo에서 얻을 수 있습니다. logo.png인지 logo.jpg인지는 원본 이미지의 이름이 아니라 파일 자체의 바이트로 결정됩니다
커버의 저작권 기록은 resource_type 9(LevelLogo)인 ResourceMeta입니다. 여기에는 resource_id도 resource_guid도 채워지지 않습니다: 레벨의 커버는 하나뿐이므로 타입만으로 주소가 됩니다
AI 사용 선언
LevelMeta 및 모든 ResourceMeta에는 AiGeneration 타입의 ai_generated 필드가 있습니다:
| 값 | 이름 | 의미 |
|---|---|---|
| 0 | NotSpecified | 선언 없음, 알 수 없음. 기본값이며, 키가 없으면 이 값으로 읽힙니다 |
| 1 | No | AI 생성 없이 만들었다고 선언됨 |
| 2 | Yes | AI로 생성했다고 선언됨 |
LevelMeta에서 이 값은 레벨 자체의 내용만 다룹니다. 각 리소스와 커버는 자기 기록에서 따로 선언합니다.
레벨에 AI 콘텐츠가 있는지는 저장되지 않습니다: 그 답은 LevelMeta.ContainsAiContent()에서 나오며, Yes 값만 계산에 들어갑니다
레벨과 함께 이동하지 않는 것
다음 파일은 레벨 폴더 안이 아니라 levels 폴더 옆에 있습니다. 레벨을 아카이브로 만들거나, 보내거나, 삭제해도 영향을 받지 않습니다:
| 폴더 또는 파일 | 내용 | 남는 이유 |
|---|---|---|
backups/ | 에디터 자동 저장, 레벨 ID마다 폴더 하나, backup_level_<time>.<extension> | 보호 대상 폴더 안의 사본은 그 폴더와 함께 삭제됩니다 |
stats/ | LevelId별 기록과 진행 상황, statistics.json | 그렇지 않으면 누군가 보낸 레벨이 이미 클리어된 상태로 도착합니다 |
settings.json | UserSettings, 기기 전체에 대한 플레이어 설정 | 레벨이 아니라 플레이어의 것입니다 |
resources/themes, effects, shapes, prefabs | 기기의 공유 라이브러리 | 기기의 모든 레벨이 함께 씁니다 |
reports/ | 진단 보고서 | - |
JSON 키
JSON은 항상 들여쓰기 없이 압축된 형태로 기록됩니다. 눈으로 읽으려면 텍스트 에디터에서 서식을 정리합니다
키의 길이는 레벨 안에서 그 키가 몇 번 나오는지에 따라 정해집니다:
- 파일당 한 번 나오는 키는
snake_case의 온전한 단어로 씁니다:level_id,min_generation - 수천 번 반복되는 키는 몇 글자로 줄입니다:
objs,f,e,v
키는 레벨 파일 바이트의 52%를 차지하므로, 이것은 취향이 아니라 규칙입니다. 모든 키는 Names.cs에 선언되어 있습니다
다형 값은 [tag, data] 형태로 기록됩니다. 일반 문자열은 [0,{"v":"Author"}], 현지화 문자열은 [1,{"strs":[...]}]
ObjectId 같은 식별자 래퍼는 숫자나 문자열 그대로 기록됩니다
식별자
정수 식별자의 부호에는 의미가 있습니다. 0 값은 항상 "설정되지 않음"을 뜻합니다:
| 식별자 | JSON 키 | 양수 | 음수 |
|---|---|---|---|
ObjectId | id, 부모는 pid | 레벨 오브젝트 | 게임 오브젝트: -1 카메라, -2 로컬 플레이어, -3 프리팹 템플릿의 루트 |
| 리소스 식별자 | txid, fnid, auid, byid, ttid | 게임과 함께 배포되는 리소스 | 레벨 자체 리소스 |
오디오 트랙의 AudioId | aid | 이 레벨의 트랙 | 사용하지 않음 |
pid 값이 0인 오브젝트에는 부모가 없습니다. -3보다 작은 수는 게임 실행 중에만 존재하며 파일에 기록되지 않습니다
Marker, Checkpoint, BeatSegment에는 식별자가 없습니다. 이들의 주소는 시간 위의 위치입니다: 마커와 체크포인트는 프레임 f, 비트 세그먼트는 구간 sp의 첫 프레임입니다(세그먼트는 겹치지 않습니다). 이런 오브젝트를 옮기면 주소가 바뀝니다
프리팹 오버라이드는 필드를 JSON 키가 아니라 번호로 지정합니다. 배치는 오버라이드를 mod 목록에 보관합니다. 각 오버라이드의 key에는 숫자 세 개가 들어갑니다:
id- 템플릿 안의 오브젝트이며, 레벨에 있는 그 사본이 아닙니다f- 필드의 영구 번호i- 목록 필드의 요소, 필드 전체라면-1
번호는 필드에 영구히 묶입니다. 그 필드의 JSON 키 이름을 바꿔도 레벨에 이미 저장된 오버라이드는 깨지지 않습니다
바깥쪽 "g"
모든 직렬화 루트는 키 두 개짜리 엔벨로프로 감싸집니다:
{"g":2,"v":{"level_id":"18df5f61-3aa4-4812-bf69-d357f2201bc3","vrs":"1.0", ... }}
g - 모델 세대(Names.Generation), v - 데이터
엔벨로프는 중첩됩니다. Level 안에서 LevelSettings, GameLevel, AudioLevel, LevelResources, LevelHints에 각각 자기 엔벨로프가 있습니다. 세대의 작동 방식 - 버전 관리와 마이그레이션
g, vrs 두 값은 서로 다른 수입니다. vrs - 제작자가 정한 레벨 버전(LevelMeta.LevelVersion)이며 형식과는 관계가 없습니다. g 값을 직접 바꾸지 마십시오: 빌드가 아는 것보다 높은 g 값을 가진 파일은 아예 읽히지 않습니다