К содержимому
Формат уровня
На этой странице

Формат уровня

Что лежит в папке уровня, какие файлы едут вместе с ним, какие остаются на устройстве и как устроен JSON внутри

Папка уровня

Уровень - это папка. Каждое имя файла в ней зафиксировано в FileNames:

ФайлМодельЧто внутри
level.json или level.blobLevelсодержимое: настройки, объекты и события, аудио, ресурсы, подсказки
metadata.json или metadata.blobLevelMetaназвание, описание, авторы, лицензия, запись о каждом ресурсе, ссылка на обложку
logo.png или logo.jpg-обложка
трек, картинки, шрифты-медиафайлы, на которые ссылается уровень

Расширение и есть формат. Внутри файлов оно нигде не записано. Читатель смотрит, какой из файлов существует: level.json или level.blob

Уровень и метаданные выбирают формат независимо. Например, встроенный уровень new-zero-demo хранит level.blob рядом с metadata.json

metadata.json - отдельный файл специально. Каталог показывает тысячу уровней по тысяче маленьких файлов метаданных. Ни один уровень при этом не открывается

Ресурсы

Ресурс адресуется парой uri и uri_type (ResourceUriType):

ЗначениеТипГде файл
1LevelPathвнутри папки уровня. Только этот тип делает уровень переносимым
2AbsolutePathгде-то ещё на этом устройстве
3DirectUrlскачивается по сети
4StreamingAssetsпоставляется с игрой, а не с уровнем

AbsolutePath получается, когда уровень создают вокруг песни. Экспорт архива копирует такой файл внутрь. Подробнее - Архивы и защита

Обложка доступна через LevelMeta.LevelLogo, как любой другой ресурс. logo.png или logo.jpg - решают байты самого файла, а не имя исходной картинки

Что не едет вместе с уровнем

Эти файлы лежат рядом с папкой levels, никогда не внутри папки уровня. Архивирование, отправка или удаление уровня их не трогают:

Папка или файлЧто этоПочему остаётся
backups/автосохранения редактора, по папке на id уровня, backup_level_<время>.<расширение>копия внутри папки, которую она защищает, удаляется вместе с ней
stats/рекорды и прогресс по LevelId и statistics.jsonприсланный уровень приходил бы уже пройденным
settings.jsonUserSettings, настройки игрока на всё устройствоони принадлежат игроку, а не уровню
resources/themes, effects, shapes, prefabsобщие библиотеки устройстваони общие для всех уровней на устройстве
reports/диагностические отчёты-

Ключи JSON

JSON всегда пишется компактно, без отступов. Чтобы читать глазами, отформатируйте его в текстовом редакторе

Длина ключа зависит от того, сколько раз он встречается в уровне:

  • ключ, который встречается раз на файл, пишется полными словами в snake_case: level_id, min_generation
  • ключ, который повторяется тысячи раз, сокращается до нескольких букв: objs, f, e, v

Ключи занимают 52% байтов файла уровня, поэтому это правило, а не вкусовщина. Все ключи объявлены в Names.cs

Полиморфное значение пишется как [тег, данные]. Простая строка - [0,{"v":"Author"}], локализованная - [1,{"strs":[...]}]

Обёртки идентификаторов вроде ObjectId пишутся голым числом или строкой

Идентификаторы

Знак целочисленного идентификатора имеет смысл. 0 всегда значит "не задан":

ИдентификаторКлюч JSONПоложительныйОтрицательный
ObjectIdid, и 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":1,"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 выше, чем знает сборка, не читается целиком