Перейти к содержимому
SheetForge

Синтаксис таблиц

Таблица SheetForge самоописываема: столбец A зарезервирован под маркеры, реальные данные начинаются со столбца B. Строки определяются по своему маркеру, а не по позиции, поэтому вы можете вставить строки-комментарии где угодно, и ничего не сломается.

Чтобы адаптировать существующую таблицу, вставьте один столбец маркеров перед вашими данными и добавьте три строки маркеров. Существующие столбцы данных остаются как есть.

Маркеры (столбец A)

Столбец AЗначение
#Строка-комментарий — полностью игнорируется, сохраняется дословно при round-trip.
@nameСтрока имён полей (одно имя на столбец).
@typeСтрока типов полей.
@descСтрока описаний — генерация кода запекает её в XML doc-комментарии и подсказки инспектора.
@overlap(необязательно) Политика дублирования для столбца — true (разрешить, по умолчанию) / false (требовать уникальности значений).
@style(необязательно) Метаданные отображения таблицы — метка группы и цвет для этой таблицы. См. ниже.
@enum(необязательно) Помечает всю таблицу как определения enum, а не как таблицу данных. См. ниже.
@loc(необязательно) Помечает таблицу как таблицу локализации — её ячейки называют код языка каждого столбца. См. ниже.
@yourMarker(необязательно, регистрируется плагином) Пользовательский структурный маркер — см. ниже.
(пусто)Строка данных.
  • @name, @type, @desc обязательны; @overlap, @style и любые пользовательские маркеры необязательны.
  • Строки маркеров могут располагаться в любом порядке, пока они находятся выше строк данных.
  • Неизвестный @marker — это ошибка с предложением ближайшего совпадения («вы имели в виду @desc?»). Зарегистрированные пользовательские маркеры также участвуют в пуле предложений.
  • Данные в столбце без заголовка @name/@type — это ошибка (защита от «осиротевших» данных — молчаливая потеря данных никогда не допускается).

Пример (столбцы показаны как A | B | C | D):

#        | Item definitions — hand-edited by design team
@name    | codeName      | displayName | price
@type    | RecordId      | string      | int=10
@desc    | unique key    | shown in UI | shop price (gold)
         | item.sword    | Sword       | 120
         | item.potion   | Potion      |

(Пустая ячейка price у item.potion материализует явное значение по умолчанию 10.)

Система типов

Каждый тип самоописываем — по одной лишь ячейке @type читающий видит, что содержит столбец.

НотацияЗначение
int float bool stringВстроенные скаляры.
Enum<DamageType>Enum C# — либо определённый в листе enum (см. ниже, код не нужен), либо зарегистрированный плагином (EnumRegistry). Имена членов проверяются, для опечаток предлагаются ближайшие совпадения.
List<T>Список — разделитель элементов ;, элементы обрезаются от пробелов, пустой элемент — это ошибка, пустая ячейка — это пустой список.
RecordIdКлючевой столбец этой вкладки — строковый самоидентификатор (например, item.sword). Всегда обязательный скаляр. Рекомендуемое имя столбца: codeName.
IntIdВторичный целочисленный ключ этой вкладки — не более одного на вкладку, обязательный скаляр, для id времени выполнения/сохранений/бэкенда. Рекомендуемое имя столбца: id. Вкладка может иметь ключ RecordId, IntId или оба сразу.
RecordId@EffectsСсылка на запись на вкладке Effects по её строковому ключу — проверяется целостность (целевая вкладка существует, имеет ключевой столбец, id разрешается; для опечаток предлагаются подсказки).
IntId@EffectsСсылка на запись на вкладке Effects по её целочисленному ключу — полный паритет с RecordId@Effects: проверяется целостность тем же способом (целевая вкладка существует, имеет столбец IntId, id разрешается), с предложением ближайшего целого числа при промахе. Значения канонизируются через int.ToString, поэтому вручную введённое 007 разрешается как 7.
AssetRef@IconsСсылка на ассет в группе Addressables Icons — существование проверяется по каталогу. Вложенный ассет (спрайт внутри текстуры, материал внутри шрифта) адресуется как parent[sub] — адрес, который Addressables даёт записи вложенного объекта, например atlas[sword], — и проверяется, запекается (SubObjectName) и экспортируется по этому ключу.
AssetRef@Icons<Sprite>Та же ссылка, ограниченная одним типом ассета: адрес проходит только тогда, когда ассет — или один из его вложенных ассетов — можно загрузить как Sprite. Имя — это любой производный от UnityEngine.Object тип ассета, известный проекту (движковый или ваш собственный): короткое имя, если ему соответствует ровно один тип, иначе полное имя (MyGame.ItemData). Генерация кода выводит AssetReferenceT<Sprite>; AssetRef@Icons без <…> остаётся неограниченным; AssetRef<Sprite>@Icons отклоняется с предложением правильного написания. См. Типизированные ссылки на ассеты ниже.
LocRef@StringsСсылка на ключ локализации в таблице локализации Strings — проверяется на целостность так же, как RecordId@Tab (существование, предложения по ближайшему совпадению, распространение переименования, пикер, выпадающие списки), с предпросмотром текста записи на исходном языке прямо в ячейке. Целевая вкладка должна нести @loc (иначе LocRefTargetNotLocalizationSheet), а голый LocRef без @Target отклоняется. List<LocRef@Strings> и LocRef@Strings? составляются как обычно. Генерация кода выводит простую структуру LocRef — см. Таблицы локализации.
Color · AnimationCurve · GradientВстроенные визуальные типы значений. У каждого есть компактная текстовая форма (ниже), которую Data Studio и веб-приложение редактируют нативным редактором цвета, кривой или градиента вместо необработанного текста; генерация кода выводит поля UnityEngine.Color / AnimationCurve / Gradient.
Modifier (пример)Пользовательский тип ячейки, зарегистрированный плагином (см. Создание плагинов) — например, мини-грамматика stat:op:value из примера. CustomType@Target также работает благодаря одной лишь регистрации. Когда плагин подключает IReferencingCellType, такой столбец ведёт себя точно как RecordId@Target — проверяется, получает подсказки, переименовывается, отрисовывается и выбирается точно так же.
Pair<T> (пример)Зарегистрированный плагином wrapper-тип — обобщённая форма значения MyWrapper<T>, упаковывающая несколько внутренних значений T в одну ячейку (например, Pair<int> = 1~2). Внутренний тип разрешается рекурсивно, поэтому Pair<RecordId@Effects>, Pair<Enum<DamageType>> и вложенный Box<Pair<int>> — всё это работает. См. Создание плагинов.

<> и @ означают разное и сосуществуют: <> = вид/wrapper (встроенный List или плагинный MyWrapper<T>), @ = цель. Так, List<RecordId@Effects> — это список ссылок, а Pair<RecordId@Effects> упаковывает две ссылки — обе на вкладку Effects. Целочисленный ключ составляется точно так же: List<IntId@Effects> — это список ссылок по целочисленному ключу.

Wrapper-типы (MyWrapper<T>)

Плагин может зарегистрировать wrapper — обобщённую форму значения, которая владеет внешним синтаксисом (разделитель, арность) и делегирует внутренний тип в Core. Wrapper комбинируется с любым внутренним типом. Любые ссылки внутри него по-прежнему проверяются, распространяются при переименовании ключа и переписываются при переименовании вкладки (полный сквозной проход).

Правила отклонения (согласованные с List):

НотацияРазрешено?Почему
Pair<RecordId@Effects> · Pair<Enum<E>> · Box<Pair<int>>ДаWrapper над скаляром, ссылкой, enum или другим wrapper'ом.
List<Pair<int>>ДаСписок составных значений. Собственный разделитель wrapper'а должен отличаться от ; (разделителя списка) — это ответственность автора плагина.
Pair<List<int>>НетСписок не может находиться внутри wrapper'а (List остаётся плоским и всегда самым внешним — то же правило, что и для List<List<T>>).
Pair<int>@EffectsНетWrapper — это форма значения; вместо этого ставьте @ на внутреннем листе (Pair<RecordId@Effects>).
Pair<int?> · Pair<int=1>НетНеобязательность/значения по умолчанию — это нотация уровня поля, а не часть внутреннего типа.

Обязательность / необязательность / значения по умолчанию

НотацияЗначение
float (без пометки)Обязательно — пустая ячейка является ошибкой (молчаливое загрязнение данных блокируется на входе).
float?Необязательно — пустая ячейка материализует значение по умолчанию для типа (0) с флагом IsDefaulted. Применяется к четырём скалярам (int / float / bool / string) и к трём визуальным типам: Color? → прозрачный чёрный #00000000, AnimationCurve? → кривая без ключей, Gradient? → белый градиент `#FFFFFF@0,#FFFFFF@1
RecordId@Effects? · IntId@Effects? · AssetRef@Icons?Необязательная ссылка — пустая ячейка материализует пустую ссылку: «указывает в никуда», при этом целевая вкладка/группа сохраняется, а ячейка помечается флагом IsDefaulted. Это не сломанная ссылка — проверка целостности ссылок и ключей ассетов пропускает её, холст не рисует для неё линию, а @overlap не считает две пустые ссылки дубликатами. Ячейка, которая действительно несёт значение, проверяется точно так же, как и раньше, поэтому опечатка в необязательном столбце по-прежнему отлавливается.
RecordId@Effects=То же самое, записанное явно: пустое явное значение по умолчанию эквивалентно голому ? выше. Непустое значение по умолчанию (RecordId@Effects=fire) по-прежнему разрешается и по-прежнему проверяется на целостность.
int=1Необязательно, с явным значением по умолчанию — пустая ячейка материализует 1.
List<T>Пустая ячейка всегда допустима (пустой список).

Там, где ? не принимается, причина всегда одна: Core не может изобрести значение из ничего, поэтому таким типам нужно явное =default. Это касается:

  • Enum<T>?
  • пользовательского типа плагина — Modifier?, включая Modifier@Tab?
  • wrapper'а — Pair<int>?

Ключевые столбцы исключены по другой причине: пустой ключ порождал бы дубликаты. Поэтому RecordId? (бесключевая форма самоидентификатора) и IntId? тоже отклоняются.

Другие намеренно отклоняемые нотации:

  • int?=1 и RecordId@Effects?=fire? и = оба означают «необязательно», так что выберите одно.
  • List<T>? — список уже допускает пустоту.
  • List<List<T>> — вложенные списки не допускаются.
  • Pair<int?> — необязательность — это уровень поля, а не часть внутреннего типа.

Правила значений

  • bool: только true / false, регистр при вводе не важен; каноническая форма — в нижнем регистре.
  • Числа: десятичным разделителем всегда служит . (независимо от локали). Десятичные с запятой, NaN и Infinity отклоняются на входе.
  • Round-trip для float: экспорт отображает кратчайший round-trip-формат, поэтому 1.0 может вернуться как 1значение сохраняется точно (семантический round-trip).
  • Сравнения маркеров и enum выполняются как Ordinal (без сюрпризов, связанных с локалью).

Типизированные ссылки на ассеты (AssetRef@Group<Type>)

AssetRef@Icons принимает любой адрес в группе. AssetRef@Icons<Sprite> сужает его до одного типа ассета, и это сужение проверяется в трёх точках: при проверке, при генерации кода и на поверхности авторинга.

  • Какие имена разрешаются. Тип — это любой производный от UnityEngine.Object тип ассета, который может загрузить проект, — как движковые типы (Sprite, Texture2D, AudioClip, абстрактная база вроде Texture), так и ваши собственные ScriptableObject — списка разрешённых типов не существует. Компоненты и типы только для редактора кандидатами не являются. Пишите короткое имя, если ему соответствует ровно один тип, иначе — полное имя, включая пространство имён. Неоднозначные имена (AmbiguousAssetType, со списком всех кандидатов) и неизвестные имена (UnknownAssetType, с предложением ближайшего совпадения) сообщаются один раз на столбец, в строке @type.
  • Что проходит проверку. Адрес удовлетворяет ограничению, когда ассет по этому адресу — или любой из его вложенных ассетов — можно загрузить как этот тип, поэтому текстура, импортированная в режиме Sprite, проходит <Sprite>, а обычная текстура сообщается для каждой ячейки как AssetTypeMismatch. Сам вложенный ассет адресуется как parent[sub], и этот ключ проверяется только против своего собственного типа.
  • Тип, на который сгенерированный код не может сослаться, отклоняется. Тип, который живёт в предопределённой сборке (Assembly-CSharp и её аналоги — любая папка скриптов без определения сборки), находится, но сообщается как AssetTypeNotReferenceable, потому что сгенерированная сопутствующая сборка не может ссылаться на эти сборки, и AssetReferenceT<T> не скомпилируется. Перенесите тип в определение сборки либо уберите <…>.
  • Что выводит генерация кода. AssetReferenceT<global::UnityEngine.Sprite> для разрешённого типа, AssetReference для неограниченного столбца. Определение сопутствующей сборки автоматически ссылается на сборку, в которой живёт тип, а разрешённое полное имя входит в отпечаток схемы, поэтому изменение сопоставления имени перегенерирует код.
  • Составляется, как и любой другой тип: AssetRef@Icons<Sprite>?, List<AssetRef@Icons<Sprite>> и wrapper вроде Pair<AssetRef@Icons<Sprite>> — всё это работает; AssetRef@Icons<> (пусто), AssetRef@Ic<ons (угловая скобка в имени группы) и RecordId@Skills<X> (ограничение действует только для AssetRef) являются синтаксическими ошибками.
  • Форма столбца Data Studio содержит кнопку Тип…, которая перечисляет типы-кандидаты и переписывает ячейку @type за вас — см. Data Studio.

Визуальные типы значений (Color, AnimationCurve, Gradient)

Три встроенных типа несут значения, которые невозможно читать как необработанный текст. Их текстовая форма спроектирована так, чтобы человек мог вручную набрать короткую версию, а каждый инструмент — редакторы, Экспорт, Отправка, веб-приложение — всегда записывал каноническую, полную форму, и значение переживало путь таблица → Unity → таблица без потерь.

Разделители общие для всех трёх типов и находятся на один уровень ниже разделителя списка: внутри значения элементы разделяются ,, поля внутри элемента — :, секции|, а время ключа присоединяется через @. Элементы List<> по-прежнему разделяются ;, и ни одна из трёх нотаций никогда не содержит ; — поэтому List<AnimationCurve> = 0:0,1:1;0:1,1:0 разбивается однозначно. Числа везде используют . в качестве десятичной точки (запятая локали проявляется как неверное количество полей, а не как молчаливо неверное значение), пробелы вокруг разделителей обрезаются, а round trip parse(render(parse(x))) == parse(x) выполняется для любого принятого ввода.

ТипДопустимый вводКаноническая форма
Color#RGB, #RGBA, #RRGGBB, #RRGGBBAA (регистр не важен, # обязателен)#RRGGBB заглавными буквами, когда цвет непрозрачен, иначе #RRGGBBAA#FF8800, #FF880080
AnimationCurve`key,key,…[pre:post], где ключ — это t:v, t:v:in:out, t:v:in:out:inW:outW:wmилиt:v:in:out:inW:outW:wm:tm` (2, 4, 7 или 8 полей — 3, 5 и 6 являются ошибками)
Gradient`colorKeys[alphaKeys[

Цвет: значение хранится как четыре байта. HDR (каналы выше 1) не поддерживается — запечённый цвет ограничивается диапазоном 0…1 при экспорте. Тип по умолчанию — прозрачный чёрный, #00000000.

Кривая: wm — это флаг взвешенных касательных (0 нет · 1 входящая · 2 исходящая · 3 обе), а tm — пара режимов касательных Left/Right, за которой опционально следует /broken — каждая сторона — это одно из Free, Auto, Linear, Constant, ClampedAuto, те же имена, что использует редактор кривых Unity. Более короткие формы заполняют остальное: 2-польный ключ берёт в качестве касательных наклон к соседям (Linear/Linear), веса 0.33333334 и без взвешивания; 4-польный ключ сохраняет ваши касательные (Free/Free); 7-польный ключ добавляет веса. Поля касательных могут быть Infinity или -Infinity (шаг Constant); время, значение и вес должны быть конечными, время ключей должно быть различным (при импорте ключи сортируются по времени, поэтому порядок, в котором вы их вводите, значения не имеет), и ограничения на количество ключей нет. Режим побеждает число: для любой стороны, которая не Free, значение касательной пересчитывается из режима во время импорта — тем же вычислением, что выполняет Unity, — поэтому вручную введённое число, которое противоречит своему режиму, заменяется, и таблица, редакторы и игра показывают одну и ту же кривую. Режимы обёртки — это ClampForever, Loop, PingPong и Default; Once принимается как псевдоним ClampForever (Unity нормализует его) и никогда не записывается обратно. Кривая без ключей не имеет текстовой формы: она существует только как пустая ячейка необязательного столбца, а экспорт отображает её как пустую ячейку.

Градиент: ключи цвета не несут альфа-канал (#RRGGBBAA в секции цвета — это ошибка; у альфа-канала есть собственная секция); внутри секции времена @t либо присутствуют у всех ключей, либо отсутствуют у всех, а когда они отсутствуют, ключи распределяются равномерно (n = 10, n ≥ 2i/(n−1)); отсутствующая секция альфа-канала означает 1@0,1@1, отсутствующий режим означает Blend. Режимы — это Blend, Fixed (ступени) и PerceptualBlend; необязательное цветовое пространство (Gamma или Linear) влияет только на то, как интерполирует PerceptualBlend. Времена и альфа-значения — это 0…1; времена квантуются до 16 бит при импорте, точно как их хранит Unity, поэтому видимое вами значение — это то значение, которое хранит движок. Градиент с одним ключом проходит round-trip через Unity как два одинаковых ключа — картина не меняется, растёт только количество ключей.

Списки: List<Color> = #F00;#0F0, List<Gradient> = #F00,#00F;#0F0,#000 — разделитель списка не меняется.

Data Studio показывает эти ячейки как нативные поля цвета, кривой и градиента, а веб-приложение — как превью с полноценными редакторами — см. Data Studio и SheetForge Web. Оба записывают каноническую форму; минимальные формы предназначены для людей.

Ключи и уникальность

  • RecordId (без @) — это ключевой столбец: не более одного на вкладку.
    • Ноль ключевых столбцов допустим — пока другая вкладка не сошлётся на эту (TargetTabHasNoKey).
    • Два и более — ошибка (MultipleKeyColumns).
    • Дублирующиеся значения ключа (DuplicateRecordId) и пустые ячейки ключа — ошибки.
  • IntId — это вторичный целочисленный ключ: уникальность обеспечивается независимо, и другие вкладки могут ссылаться на него через IntId@Tab.
    • Ссылки по целочисленному ключу получают ту же проверку целостности, те же предложения ближайшего совпадения, распространение при переименовании и поддержку графа/холста, что и RecordId@Tab.
  • Вкладка может иметь ключ только RecordId, только IntId, либо оба сразу — и все три случая ведут себя симметрично во всех отношениях.
    • Когда у вкладки есть оба, RecordId — это значение для отображения/идентичности, а целое число показывается рядом с ним.
    • Другая вкладка может указывать на ту же запись любым из способов: RecordId@ThisTab по строковому ключу или IntId@ThisTab по целочисленному ключу.
  • @overlap: обычные столбцы по умолчанию допускают дублирующиеся значения. Поставьте false в ячейку @overlap столбца, чтобы потребовать уникальность на основе значений.
    • 1.0 и 1 считаются одним и тем же значением; два списка считаются дубликатами, если совпадают все элементы и их порядок.
    • Две пустые ссылки никогда не считаются дубликатами друг друга (пустое значение скаляра по умолчанию по-прежнему остаётся обычным значением).
    • Ключевые столбцы всегда уникальны; указание @overlap true на ключевом столбце — это ошибка противоречия.

Метаданные отображения таблицы (@style)

@style позволяет таблице указать, к какой группе она относится и какого она цвета, так что группировка и раскраска живут в самой таблице, а не только в редакторе. Это единственный маркер, который описывает саму таблицу, а не её столбцы. Поэтому его ячейки не привязаны к столбцам — это свободный список пар key=value, начинающийся со столбца B.

@style   | title=Combat  | color=#4D8FF0
@name    | codeName      | displayName | power
@type    | RecordId      | string      | int
@desc    | unique key    | shown in UI | attack power
         | skill.fire    | Fireball    | 12
КлючЗначениеЭффект
titleЛюбой текстТаблицы с одинаковым title объединяются под этим заголовком в боковой панели Data Studio. Разделы появляются в порядке первого появления, а таблицы сохраняют свой порядок внутри раздела; таблицы без title остаются в разделе по умолчанию.
color#RRGGBB (шесть шестнадцатеричных цифр)Окрашивает эту таблицу везде, где она отображается: точка в боковой панели, рамка узла на холсте и каждый порт и линия, указывающие на эту таблицу.
  • Оба ключа необязательны, и порядок не важен; можно написать один, оба или ни одного. Пустая ячейка игнорируется (ячейки-заполнители допустимы).
  • Ошибки проверки все сообщаются как MarkerCellInvalid, с координатой ячейки и конкретным исправлением:
    • неизвестный ключ (с предложением ближайшего совпадения),
    • повторяющийся ключ,
    • отсутствующее значение,
    • цвет не в формате #RRGGBB.
  • Трёхзначное сокращение (#4AF) и именованные цвета намеренно отклоняются, чтобы значение проходило round-trip в виде одной нотации.
  • Только для отображения: генерация кода, запекание и отпечаток схемы никогда не читают @style. Изменение цвета таблицы не вызывает перегенерацию кода и повторное запекание ScriptableObject.
  • Безопасен для round-trip: строка @style сохраняется как строка-комментарий. Добавление, удаление, перемещение и переименование столбцов её не затрагивают, поскольку её ячейки не принадлежат столбцам. Редактирование происходит через форму Group & color ✎ (правый клик по таблице в боковой панели Data Studio), которая переписывает строку в канонической форме.
  • Таблица, у которой есть только @style (плюс комментарии), считается «таблицей без данных пока что»: импорт пропускает её с предупреждением, вместо того чтобы завершиться ошибкой из-за трёх отсутствующих обязательных маркеров. Как только вы добавите @name/@type/@desc, она разбирается обычным образом. См. Возможности и ограничения.
  • style — зарезервированное имя маркера: плагин, пытающийся его зарегистрировать, отклоняется, а опечатка вроде @styl получает @style в качестве предложения.

Листы определений enum (@enum)

Столбцу Enum<T> нужен T. Вы можете зарегистрировать его из C# плагина (EnumRegistry), но можно и просто написать его в таблице — без кода, без плагина. Таблица читается как определения enum, если верно любое из условий:

  • она несёт строку маркера @enum (вкладка может называться как угодно), либо
  • вкладка называется ровно Enum (с учётом регистра) и не имеет строки @type.

Второе правило намеренно требует отсутствия @type: таблица данных всегда его имеет, поэтому уже существующая таблица, которая случайно называется Enum, продолжает оставаться таблицей данных. Таблица, несущая одновременно @enum и @type, противоречива и сообщается как EnumSheetMarkerConflict, а не угадывается.

@desc не участвует в этом решении — она допустима на листах обоих видов, а на листе enum она описывает enum в этом столбце (см. ниже).

Лист enum не имеет таблицы — ни схемы, ни ключевого столбца, ни записей. Один столбец — это один enum: ячейка @name содержит имя enum, и каждая строка под ней (пустой столбец A) — это один член.

@enum    | byte       |
@desc    | Damage kind| Elemental affinity
@name    | DamageType | Element
         | Physical   | Fire
         | Magical=10 | Ice
         | True       | Lightning

Эта таблица определяет два enum, и Enum<DamageType> / Enum<Element> теперь разрешаются в любой ячейке @type — синтаксис столбца не меняется. Все три дополнения, показанные выше, необязательны; голая строка @name плюс члены — уже полноценный лист enum.

Вам не нужно набирать этот каркас вручную: Создать лист поставляется с шаблоном Enum definitions, который сам раскладывает таблицу за вас, — это один из двух встроенных шаблонов (см. Data Studio ▸ Sheet create / delete).

  • Порядок и есть значение, а Name=value его фиксирует. Ячейка члена — это либо просто имя, либо Name=value с явным целым числом — точно по правилам enum в C#: у члена без номера значение равно предыдущему плюс один, а у первого — 0.
    • Normal / Rare=10 / Epic компилируется в 0 / 10 / 11. Генерация кода выводит = value только там, где вы его написали.
    • Ячейки данных и выпадающие списки всегда используют имя (Rare, никогда не Rare=10).
    • Значение, не являющееся простым целым числом, либо выходящее за диапазон базового типа (в том числе из-за автоинкремента), — это InvalidEnumMemberValue.
    • Именно поэтому Data Studio никогда не переставляет члены и никогда не заполняет пропуск задним числом: сдвиг члена молча изменил бы значения, уже запечённые в ассетах и сохранённые в файлах сохранений.
  • @desc описывает enum. Ячейка @desc столбца становится XML-<summary> этого enum в сгенерированном коде (подсказки в IDE) — в том же духе, что и @desc поля таблицы данных. Пустая ячейка = нет описания; сама строка маркера необязательна.
  • Ячейки @enum выбирают базовый тип. Ячейка строки @enum в столбце может называть базовый тип C# для этого enum — один из byte, sbyte, short, ushort, int, uint, long, ulong.
    • Пустая ячейка (или отсутствие строки @enum вовсе, на вкладке с именем Enum) означает int. Всё остальное — это InvalidEnumUnderlyingType.
    • Генерация кода выводит public enum Grade : byte { … }.
    • Для ulong явные значения выше long.MaxValue из таблицы не поддерживаются — зарегистрируйте такой enum из C#-плагина.
  • Пустые ячейки пропускаются, а не читаются как члены, поэтому столбцы могут иметь разную длину, а пропуск в середине просто игнорируется.
  • Строки-комментарии (#) игнорируются в любом месте таблицы. Допустимо много enum на одну таблицу и много листов enum. Имена должны быть уникальны среди всех них, и имя, которое плагин уже зарегистрировал из C#, побеждает — определение из таблицы отклоняется с DuplicateEnumName.
  • Имена и члены должны годиться как идентификаторы C#: ASCII-буквы, цифры и _, не начинающиеся с цифры, и не зарезервированное ключевое слово (InvalidEnumIdentifier).
    • Не-ASCII символы отклоняются намеренно, потому что похожие друг на друга Unicode-идентификаторы создавали бы тип, который никто не смог бы отличить от другого.
    • Объявленное имя без членов под ним — это EnumSheetEmptyColumn.
    • Если хотя бы один член одного столбца не проходит проверку, весь этот enum отбрасывается целиком, а не регистрируется наполовину.
  • Что генерирует импорт. Один файл SheetForgeEnums.cs на весь проект — enum является результатом уровня проекта, а не отдельной вкладки. Он записывается в папку сгенерированного кода из настроек, в том же пространстве имён, что и сгенерированные типы вкладок. Первый импорт создаёт тип, компилирует его и завершает запекание после перезагрузки домена без единого лишнего клика.
  • Добавление члена без открытия таблицы: выпадающий список ячейки Enum<T> в Data Studio содержит пункт «Add a new member…», который добавляет член в подготовленные изменения листа enum одним шагом отмены. Enum, зарегистрированный из C# плагина, такой строки не предлагает — им владеет код.
  • У листов enum нет записей, поэтому они никогда не запекаются в ScriptableObject, а экспорт/отправка оставляют их текст нетронутым; импорт сообщает о них отдельно от пропущенных вкладок.
  • См. Возможности и ограничения о двух границах: enum, зарегистрированный плагином, нельзя расширить из таблицы, а сгенерированный файл enum всегда попадает в папку настроек.

Таблицы локализации (@loc)

Строка-маркер @loc превращает таблицу в таблицу локализации: строки — это ключи, столбцы — это языки, а ячейка @loc каждого языкового столбца называет его код языка.

  • Ключевой столбец RecordId обязателен — значение ключа и есть ключ локализации.
  • Первый языковой столбец — это исходный язык.
  • Языковые столбцы — это строковые столбцы. string? — рекомендуемая форма: тогда пустая ячейка — это пробел в покрытии, а не ошибка.
  • Два необязательных столбца зарезервированы по имени: smart (bool) и comment (string).
@loc     |            | en          | ko    |
@name    | codeName   | en          | ko    | comment
@type    | RecordId   | string?     | string? | string?
@desc    | key        | source text |       |
         | ui.ok      | OK          | 확인  | Confirm button

Для редактирования, экспорта, отправки, xlsx и веб-приложения таблица остаётся обычной таблицей. Что меняется — так это результат: ни класса записи, ни SO базы данных, зато константы ключей на вкладку и — если установлен пакет Unity Localization — синхронизация StringTable. @enum и @loc на одной и той же таблице — это ошибка конфликта.

Полная история — ссылки LocRef, чеканка ключей, мост, рабочие процессы перевода — на странице Таблицы локализации.

Пользовательские структурные маркеры (регистрируются плагином)

@overlap — это встроенный пример поколоночного маркера: строка маркера, чьи ячейки несут по одному значению на столбец, проверяемому по отдельности. Плагин может зарегистрировать собственные маркеры таким же образом — например, маркер @curve, который указывает, как интерполируется каждый числовой столбец.

Значение хранится как метаданные, не зависящие от домена (FieldSchema.MarkerValues), которые могут читать валидаторы, поставщики рёбер и подсказка заголовка столбца в окне авторинга. Core никогда не интерпретирует само значение — проверка делегируется определению маркера.

  • Зарегистрированные пользовательские маркеры принимаются точно так же, как @overlap: любой порядок выше данных, дубликаты отклоняются, маркер ниже данных — это ошибка.
  • Каждый маркер отвечает только за проверку значения по столбцу (включая то, что означает пустая ячейка) — он не берёт на себя разбор всей строки. «Формы» данных по-прежнему выражаются через нормализацию (ссылки, List<T>, столбцы type).
  • Пользовательские маркеры предназначены для метаданных на уровне столбца, а не для новых форм данных. См. Создание плагинов, §4.5 — пример регистрации.
  • @style — единственный встроенный маркер, который не является поколоночным (он описывает саму таблицу), поэтому не он служит образцом для подражания — им служит @overlap.

Составление сложных данных: сначала нормализация

Рекомендуемый способ выразить сложные структуры — это сборка через ссылки («собирайте, а не программируйте»):

  • Атомы живут как строки на собственной вкладке.
  • Комбинации — это списки ссылок: List<RecordId@Effects>.
  • Столбец type (enum) связывает строку данных с атомом кода — ваш runtime делает по нему switch, чтобы диспетчеризовать поведение. Встроенный язык сценариев не нужен.

Мини-грамматики (пользовательские типы ячеек вроде attack:add:10) предназначены для небольших кортежей — Core предоставляет соглашения ; и :; не злоупотребляйте ими.

Для по-настоящему процедурной одноразовой логики ссылайтесь на ассет скрипта точно так же, как вы ссылаетесь на изображение: List<AssetRef@Scripts>. SheetForge проверяет ссылку и запекает addressable; выполнение скрипта — задача вашей игры.

Особые «формы» данных: даже данные, выглядящие хитро (кривые уровней и т. п.), чисто нормализуются (List<float>, сборка через ссылки). Пользовательский структурный маркер добавляет метаданные на уровне столбца (проверяемые по столбцам), а не новую форму данных — сначала нормализуйте данные и прибегайте к пользовательскому маркеру только для по-настоящему объёмных поколоночных аннотаций. См. Создание плагинов.

Похожие страницы