Возможности и ограничения
Эта страница перечисляет всё, что SheetForge не делает, пока не может делать, или делает иначе, чем вы могли бы ожидать, — с указанием причины, обходного пути и того, есть ли пространство для развития в будущем.
Формат каждой записи: Что / Почему / Обходной путь (+ пространство для развития в будущем, где это уместно).
1. Платформа и зависимости
Addressables обязателен — без него конвейер заблокирован
- Что: загрузка по адресу — это путь времени выполнения, а типу
AssetRef@Groupнуженcom.unity.addressables, так что для использования ассета пакет должен быть установлен. Сам ассет компилируется и без него — весь код, использующий Addressables, находится за версийным define'омSHEETFORGE_ADDRESSABLES, который включается только когда пакет присутствует. - Поведение при отсутствии: весь конвейер (импорт · экспорт · отправка · запись авторинга обратно) заблокирован, а не деградирован — запуск любой точки входа показывает уведомление об установке и останавливается. Здесь нет частичного или тихого пути (никакого отката вида «пропустить проверку ключей ассетов»). Поскольку редактор компилируется без пакета, он никогда не переходит в Safe Mode. Окно «Начало работы» открывается как обычно, а его строка Addressables показывает ✗ с кнопкой Открыть Package Manager. Ни от чего не зависящий бутстрап
SheetForge.Setupостаётся страховочной сетью для других, не связанных с этим сбоев компиляции. - В Data Studio без пакета: кнопка-пикер ⊙ у ячейки ассета и её зона для перетаскивания отключены, а причина показана в подсказке при наведении; ручной ввод адреса в ячейку по-прежнему работает.
- Почему не двойная абстракция Resources/Addressables: намеренно не построена — расценена как избыточное усложнение.
- Обходной путь: установите Addressables. Диалог импорта Asset Store делает это до компиляции; если вы его пропустили, компилирующийся редактор направит вас к установке.
- Проверка: обе ветки проверяются вживую. С удалёнными версийными define'ами (имитация «Addressables отсутствует») продукт и тестовые сборки компилируются с 0 ошибок; при восстановленных define'ах — 0 ошибок и 0 предупреждений. Независимо проверено end-to-end путём импорта ассета в новый проект без установленного Addressables: проект компилируется, и появляется окно с рекомендацией по установке, как и задумано.
Unity Localization необязателен — ждёт только синхронизация StringTable
- Что: таблицы локализации (
@loc), ссылкиLocRef, константы ключей, отчёты о покрытии, экспорт, отправка, xlsx и веб-приложение — всё это работает без установленногоcom.unity.localization. Единственное, что ждёт, — это выход синхронизации StringTable: он показывает уведомление об установке (один раз за сессию) и останавливается. Весь код, затрагивающий пакет, находится за версийным define'омSHEETFORGE_LOCALIZATION, так что каждая сборка и каждая строка сгенерированного кода компилируются без пакета — сгенерированные поля представляют собой простую структуруLocRef, а не тип из пакета. - Поведение при отсутствии: ничего не деградирует, и нигде ничего не пропускается молча — таблицы остаются полноценными таблицами; заблокирован только выход синхронизации, с указанием причины.
- Поддерживаемая версия: 1.5 или новее.
- Обходной путь: установите пакет, когда вам понадобятся таблицы; всё, что было создано до этого момента, синхронизируется при следующем завершённом импорте. См. Таблицы локализации.
Нет программной установки в один клик
- Что: окно страховочного механизма лишь направляет вас; оно не устанавливает пакет само. То же правило действует и для уведомления об установке Unity Localization.
- Почему: правила публикации в Asset Store ограничивают программное изменение пакетов; окно с рекомендациями — это безопасный вариант, соответствующий правилам.
Define обнаружения продукта SHEETFORGE не удаляется автоматически
- Что: сборка Editor самостоятельно регистрирует символ scripting-define
SHEETFORGEдля каждой платформы сборки, чтобы другие ассеты могли обнаружить, что SheetForge установлен, во время компиляции (см. Создание плагинов ▸ Обнаружение SheetForge из другого ассета). Регистрация идемпотентна (добавляется только при отсутствии — при уже установленном символе повторной перекомпиляции не происходит). - Ограничение: если впоследствии вы удалите ассет, этот define остаётся — код, который мог бы заметить удаление, исчезает вместе с ассетом.
- Обходной путь: удалите его вручную в Project Settings ▸ Player ▸ Scripting Define Symbols (для каждой платформы). Мы намеренно не держим фоновый наблюдатель запущенным только ради очистки одного символа. Это отдельно от
SHEETFORGE_ADDRESSABLES— внутреннего версийного define'а, который лишь отражает, присутствует ли пакет Addressables.
2. Источник Google Таблиц
Режим ExportUrl доступен только для чтения
- Что: отправка, отражение, правка структуры и удаление — всё это отключено в режиме ExportUrl.
- Почему: это неаутентифицированный путь экспорта, расшаренный по ссылке, — только для чтения по своей природе. Отправка всегда требует учётных данных SheetsApi, что проверяется ещё до какого-либо сетевого вызова.
- Обходной путь: используйте режим SheetsApi (сервисный аккаунт — см. Настройка Google Таблиц) для любой записи обратно.
ExportUrl требует карту gid
- Что: пустая карта gid приводит к неудаче импорта; дублирующиеся gid отклоняются.
- Почему: URL экспорта без gid молча возвращает только первую вкладку — ловушка молчаливого повреждения данных, в которую продукт отказывается попадать. SheetsApi обнаруживает вкладки автоматически.
- Обходной путь: зарегистрируйте значение
#gid=для каждой вкладки, либо используйте SheetsApi.
Push удаляет строки по ключу, и только проверенные
- Что: запись, удалённая локально, убирается из живой таблицы при отправке — после того как повторное получение перед отправкой подтвердит, что её ключ всё ещё находится в строке, которую видел ваш импорт. Строка, которой уже нет, считается выполненной (идемпотентная повторная отправка); ключ, найденный в другой строке, пропускается с уведомлением и никогда не удаляется по позиции. Удаления перечисляются в собственном разделе сводки подтверждения и отправляются последними, снизу вверх по каждой вкладке.
- Почему: сопоставление по ключу с живой таблицей — это то, что делает удаление безопасным на таблице, которая могла сместиться; всё, что сопоставление не может подтвердить, остаётся нетронутым.
- Ограничение: источник без возможности удаления строк (пользовательский провайдер, который её так и не получил) откатывается к старому поведению — удаление сообщается, а живая строка остаётся для вашего удаления.
Отправка пропускает конфликтующие ячейки (по замыслу)
- Что: ячейки, отредактированные кем-то третьим с момента вашего импорта, строки, чей ключ переместился неоднозначно, отсутствующие строки или дублирующиеся живые ключи пропускаются с предупреждениями — они не перезаписываются.
- Почему: это работа страховочного механизма: отправленные ячейки корректны; пропуски защищают чужие изменения и предотвращают запись не в ту строку.
- Обходной путь: проверьте в отчёте количество применённых/пропущенных ячеек; выполните повторный импорт для согласования, затем отправьте снова. (Интерфейс для разрешения конфликтов был бы отдельной функцией — не планируется.)
Отправка требует ключевой столбец
- Что: изменённую вкладку без ключевого столбца
RecordIdнельзя отправить — это ошибка плана, которая блокирует всю отправку целиком (ни одна ячейка ни на одной вкладке не отправляется; никаких частичных отправок). - Почему: отправка заново находит строки по ключу в живой таблице; без ключа защита от записи не в ту строку не может работать.
- Обходной путь: добавьте ключевой столбец, либо экспортируйте в файл и вставьте вручную.
3. Источник xlsx
-
Переименование вкладки исключает вкладки, происходящие из xlsx — защита многолистовой книги. Переименуйте в самой книге, выполните повторный импорт.
-
Распространение переименования ключа во вкладку, происходящую из xlsx, блокирует весь пакет целиком — путь xlsx не может безопасно выполнять точечные обновления ячеек, а частичное отражение никогда не допускается. Отредактируйте эту вкладку напрямую, выполните повторный импорт.
-
Непредставимые ячейки отклоняются — ячейки с формулами без закешированных значений, ячейки с ошибками, а также табуляции/переносы строк внутри ячейки. Встроенный читатель OOXML намеренно минимален (ноль стороннего кода). Материализуйте формулы; используйте
;для списков. -
Только значения — формулы, даты и форматирование трактуются, и честно об этом сказано — ячейка с формулой отдаёт своё закешированное значение (никогда не пересчитывается), ячейка с форматом даты читается как отображаемый текст
yyyy-MM-dd, а другие числовые форматы, объединённые ячейки и диаграммы не импортируются. Диалог импорта веб-приложения называет, что реально произошло, в примечании «Как была прочитана эта книга»; в редакторе та же политика применяется молча, по каждой ячейке (отклонения, указанные выше, по-прежнему сообщаются по каждой ячейке). -
Некоторые экспортируемые правила выпадающих списков перенести нельзя — экспорт представляет собой одну книгу, поэтому выпадающий список ссылочного столбца записывается как настоящий диапазон по ключевому столбцу целевого листа — то же значение, что несёт правило Google. Три случая всё же остаются за бортом и называются вместе в едином предупреждении
DropdownNotSupportedByFormat:- член списка содержит запятую (встроенный разделитель разбил бы его);
- встроенный список превышает лимит формата в 255 символов (включая кавычки);
- диапазон, чья целевая вкладка не входит в книгу.
Значения в любом случае экспортируются полностью. См. Источники, экспорт и отправка.
4. Авторинг — Data Studio
Вкладка без ключевого столбца не получает новых записей, а её правки значений теряют якорь
-
Что: вкладка без ключевого столбца
RecordIdимпортируется и отображается как обычно, и правка структуры работает полностью — добавление, удаление, переименование, изменение порядка столбцов и маркеров, а также переименование и удаление на уровне таблицы. Чего она не может получить, так это новую запись, поскольку запись без ключа нельзя ни назвать, ни сослаться на неё:- элемент управления добавлением строки отключён;
- пикер ссылок отказывается создавать запись там («… не имеет ключевого столбца, поэтому новую запись там нельзя создать»);
- действие на холсте или в инспекторе, пытающееся записать в неё, ничего не делает.
Ячейки значений можно редактировать — но без ключа для адресации строки правка подготавливается только относительно позиции строки.
-
Почему: каждая подготовленная правка обычно адресуется логически, как
(tab, record key, field), и заново разрешается относительно таблицы непосредственно перед записью. Именно это позволяет правке пережить повторный импорт, изменение порядка строк или вставку кем-то ещё строк выше неё. Без ключевого столбца такого адреса нет, поэтому правка Studio проходит вместо этого закреплённой за номером строки — вне этой защитной сети.Так что правка, привязанная к позиции, может попасть не в ту строку, если строки таблицы сдвинутся под вами до того, как вы запишете обратно (повторный импорт, либо кто-то редактирует источник напрямую). Подготавливайте и отражайте такие правки короткими пакетами.
-
Обходной путь: добавьте столбец
RecordId(правка структуры доступна, так что это можно сделать в том же окне), отразите — и вкладка станет полностью доступной для авторинга, с восстановленным логическим якорем. Вкладки без ключа остаются полностью пригодными для импорта — это ограничение авторинга, а не схемы.
Переименование столбца / изменение @type ломает ссылающийся код игры; необратимо после отражения
- Что: имя/тип сгенерированного поля меняется; код игры, ссылающийся на него, нужно обновить вручную. Ctrl+Z работает только до отражения.
- Почему: строгая типизация — поле является частью сгенерированной схемы. Разрыв компиляции перехватывается безопасным прерыванием автоцепочки с предложением конкретных действий. Значения столбца полностью сохраняются (изменяются только ячейки маркеров).
- Обходной путь: диалог подтверждения сначала предупреждает об этом; обновите свой код и дайте следующему импорту продолжить.
Переименование вкладки ломает ссылающийся код игры; необратимо после отражения
Та же механика, что и выше — меняется имя сгенерированного класса (FooDatabase → BarDatabase); повторный импорт автоматически берёт на себя всю очистку на стороне ассетов (старый класс, SO, адрес).
Взаимные (обмен) и циклические переименования вкладок поддерживаются
- Что:
Alpha→Beta+Beta→Alpha(обмен), а также более длинные циклы (A→B→C→A), можно подготовить и отразить в одном пакете. Подготовка любой из половин первой работает одинаково, и панель вкладок сразу показывает обменянные имена (WYSIWYG, с возможностью отмены). Проверка в интерфейсе использует уникальность итогового набора имён — отклоняется только настоящий конфликт, два переименования, нацеленные на одно и то же имя. Отражение обеспечивает это строго. - Ссылки следуют за данными (идентичностью вкладки), а не за именем: после обмена A↔B
RecordId@Aатомарно переписывается вRecordId@B(за один проход — никогда не применяется дважды), поэтому ссылка продолжает указывать на те же данные, которые переместились в B. - Локально: обмен меняет содержимое двух файлов местами за один проход записи. Цепочка, повторно использующая имя с другим расширением, удаляет устаревший файл со старым расширением (защита удаления на основе пути), поэтому повторный импорт никогда не видит дублирующуюся вкладку.
- Google: изменения заголовков упорядочиваются топологически и разрывают любой цикл с помощью временного заголовка (
A→tmp, B→A, tmp→B), поэтому живая таблица никогда не содержит даже кратковременного дублирующегося заголовка. Если изменение заголовка завершается неудачей в середине последовательности, о вкладке, оставшейся под временным именем, сообщается вместе с рекомендациями по восстановлению.
Особый случай только для Google: обменявшиеся вкладки, ссылающиеся друг на друга, не перенаправляются
- Что: когда две обменивающиеся вкладки ссылаются друг на друга — вкладка
Aимеет столбецRecordId@B, а вкладкаB— столбецRecordId@A— путь Google сохраняет их содержимое через изменение заголовка на месте и не переписывает их собственные ячейки@type. Поэтому эта взаимная самоссылка на Google не перенаправляется. - Почему: Google переименовывает вкладку, меняя её заголовок (содержимое намеренно не затрагивается); переписывание собственной таблицы переименованной вкладки свело бы это на нет. Локальные источники переписывают проекцию переименованной вкладки, поэтому локально это обрабатывается полностью. Ссылки с третьей вкладки перенаправляются на обоих путях.
- Обходной путь: в Google направьте взаимную ссылку через третью вкладку, либо отразите обмен через промежуточное имя.
У предпросмотра подготовленных значений на SO больше нет кнопки
- Что: оверлей «предпросмотр подготовленных значений на SO» (
EphemeralSoApply) приводился в действие кнопкой Workbench, а этого окна больше нет. Тип остаётся публичным API для инструмента, которому он нужен; переключатель тестовая правка в инспекторе SO покрывает повседневный случай — опробовать число во время выполнения. - Ограничение, если вы всё же вызовете его: оверлей отклоняет — со значком — (а) столбцы в ожидании/новые столбцы и (б) ячейки с ошибкой разбора. Новые строки поддерживаются. Он повторно использует настоящий путь разбора+запекания, поэтому то, что не может честно вычислить, отклоняет, а не подделывает.
- Обходной путь: это лишь предпросмотр; для настоящего изменения отражайте как обычно. Повторный импорт всегда восстанавливает истину.
Подготовленные правки изолируются, если их строка была переименована извне, удалена, или конфликтует ключ
- Что: подготовленная правка, чья строка была переименована извне, удалена извне, или чей ключ вступил в конфликт между подготовкой и отражением, исключается из отражения и помечается значком «изолирована».
- Почему: её логический адрес невозможно разрешить заново — но она не отбрасывается молча и не блокирует сессию.
- Обходной путь: отмените её по отдельности (после подтверждения) и подготовьте заново.
Распространение переименования ключа охватывает только ячейки baseline
- Что: текст, который вы только что набрали в том же пакете и который ссылается на старый ключ, автоматически не переписывается.
- Почему: молчаливая перезапись свежего ввода пользователя запрещена; вместо этого предварительная проверка отлавливает повисшую ссылку.
- Обходной путь: исправьте подготовленную ссылку самостоятельно, либо сначала отразите переименование.
Все оставшиеся пункты — о Data Studio, единственном окне авторинга. Более старое окно Workbench удалено; три возможности, которые предлагало только оно, были перенесены в Studio и в инспектор настроек заранее — см. что случилось с Workbench.
Таблица, не прошедшая проверку, открывается для редактирования — но экспорт, отправка и сборки остаются заблокированы
- Что: если источник был прочитан полностью, его таблицы сохраняются как baseline, даже если проверка провалилась, — так что Studio может их открыть, и вы можете исправить ошибки на месте. Генерация кода и запекание не выполняются, пока количество ошибок не станет нулевым, и экспорт, отправка в живую таблицу и сборки игрока полностью отклоняются, пока таблица находится в этом состоянии, каждый раз с объяснением причины.
- Почему: все три этих выхода объединяют последние успешно запечённые значения с более новыми таблицами. Выполнение любого из них сейчас вставило бы устаревшие значения поверх ячеек, которые кто-то уже исправил, — молчаливый откат. Блокировка выходов — это то, что позволяет входу оставаться открытым.
- Отражение исправления спрашивает один раз: на таблице в карантине запись обратно показывает дополнительное подтверждение, потому что предварительная проверка не может быть там жёстким шлюзом (у таблицы уже есть ошибки). Всё найденное сообщается как предупреждение в этом отражении, а автоматический повторный импорт заново проверяет всю таблицу. Здоровые таблицы не затрагиваются — предварительная проверка по-прежнему отказывается писать.
- Обходной путь: исправьте каждую сообщённую ошибку и получите данные снова. Блокировка снимается сама, в единственном месте, где это происходит, — при прогоне, который проходит весь путь до запекания.
Сортировка и фильтр Data Studio — только для отображения, и отключают изменение порядка строк, пока активны
- Что: сортировка по таблице (любой столбец, по возрастанию/убыванию, сохраняется для каждого проекта) и текстовый фильтр Studio изменяют только порядок отображения. Поле нумерации сохраняет настоящие номера строк таблицы, и ни то, ни другое не влияет на подготовку, отражение, отправку или экспорт. Пока сортировка или фильтр активны, инструменты изменения порядка строк ▲▼ отключены, с подсказкой при наведении.
- Почему: изменение порядка по «видимому соседу», пока представление отсортировано или отфильтровано, молча переместило бы строки рядом со строками, которые пользователь не видит. Настоящие изменения порядка строк — это операция структуры — сначала сбросьте сортировку/фильтр.
- Примечание: «сортировка по новизне» существует, только если в вашей таблице есть столбец, который её кодирует (например,
IntIdили строковый столбец, похожий на дату) — сама таблица не хранит временных меток.
Проблемы Data Studio — черновик, пока подготовлено переименование ключа
- Что: пока у ячейки ключа (
RecordId) есть подготовленная правка, панель Problems несёт значок draft, а записи о неразрешённых ссылках в ней могут быть ложными тревогами. - Почему: предпросмотр в памяти не применяет распространение переименования ключа — оно выполняется во время отражения, по всем вкладкам. Вместо того чтобы скрыть диагностику или подделать распространение, окно сообщает вам, что список — черновик, пока переименование не записано.
- Обходной путь: отразите переименование (распространение выполняется с собственным подтверждением), затем прочитайте обновлённый список.
Таблица виртуализирована по строкам свыше 200 строк — с двумя нюансами, о которых стоит знать
-
Что: свыше 200 строк таблица строит элементы строк только для видимого окна (плюс двенадцать строк оверскана), с распорками сверху и снизу, удерживающими настоящую суммарную высоту, чтобы полоса прокрутки не лгала. Прокрутка через границу окна переиспользует строки, которые уцелели, и строит только те, что вошли в него. Браузерная таблица делает то же самое при том же пороге.
Два случая всё равно строят всё целиком. При 200 строках или меньше каждая строка строится в точности как раньше, бит в бит. То же касается таблицы, чью высоту области просмотра вообще нельзя запросить, — стоящей вне окна, куда раскладка никогда не приходит, — потому что там честный запасной вариант — «построить всё». Крупная таблица, которая просто ещё не была размещена, ждёт один кадр вместо этого, поэтому она виртуализируется с первой же отрисовки, а не строится целиком лишь для того, чтобы быть отброшенной.
-
Строка, которую вы редактируете, остаётся живой даже после того, как она прокручивается за пределы окна, так что каретка, фокус и то, что вы набрали, сохраняются. У этого «поддержания в живых» есть предел дистанции, и за его пределами открытый редактор фиксирует значение и снимает фокус, а не удерживается бесконечно. Ничего не теряется, когда это происходит, — значение уже находится в сессии подготовки.
-
Виртуализируется только создание элементов. Замер ширины столбцов, поиск, сортировка, координаты и оверлей подготовки по-прежнему учитывают каждую строку, потому что каждый из них дал бы другой ответ, если бы смотрел только на то, что на экране. Так что переключение на очень крупную таблицу всё равно выполняет работу, пропорциональную её размеру; чего оно больше не делает — так это не строит тысячи виджетов.
-
В браузере виртуализированный режим измеряет столбцы сам, вместо того чтобы поручать это раскладке. Ширины авто-раскладки вычислялись бы по тому, какие строки случайно оказались в окне, поэтому столбец подёргивался бы при прокрутке. В виртуализированном режиме ширины берутся из оценки на основе данных по всем строкам и затем фиксируются. Режим полной отрисовки (≤ 200 строк) по-прежнему использует авто-раскладку без изменений.
Холст панорамируется только в пределах диапазона прокрутки, а его пунктирные линии циклов становятся грубее по мере роста
-
Что:
- Ctrl/Cmd + колесо мыши масштабирует холст записи между 25 % и 200 %, удерживая точку под курсором неподвижной — масштабирование с привязкой к центру сдвинуло бы за экран карточку, на которую вы смотрели. Процент в заголовке холста — это кнопка, возвращающая к 100 %. Обычное колесо по-прежнему прокручивает.
- Перетаскивание средней кнопкой мыши — или Alt + левая кнопка для устройств без средней — панорамирует, и курсор отмечает захват, пока он удерживается. Перетаскивание левой кнопкой оставлено выбору и связыванию, поэтому оно не может одновременно означать «переместить вид».
- Панель — это scroll view, поэтому диапазон панорамирования — это диапазон прокрутки: он останавливается на краю содержимого, а не уходит в пустое пространство, а когда содержимое меньше области просмотра, он вообще не двигается. Это не бесконечный холст.
- Пунктирная линия, отмечающая цикл, ограничивает количество отрисовываемых штрихов и удваивает период штриха на длинном пути, поэтому очень длинная петля читается грубее, а не чётче.
-
Почему: Unity выделяет вершины меша на каждый вызов отрисовки с жёстким потолком в 65,535, и превышение этого предела заставляет рисунок исчезнуть полностью, при этом всё равно расходуя ресурсы на тесселяцию. Ограничение штрихов намеренно удерживает один
Strokeв пределах этого бюджета.Фоновая сетка точек раньше упиралась в тот же потолок, а теперь — нет. Это небольшая повторяющаяся фоновая плитка, которая стоит ноль вершин и перерисовывается за постоянное время независимо от того, насколько вырастет холст. (Одна точка, нарисованная как путь, стоит измеренных 28 вершин, а не своих четырёх углов — это арифметика, стоящая за бюджетом в 1,800 точек запасного варианта отрисовки, и причина, по которой плитка стала реализованным путём.)
-
Обходной путь: для сетки не требуется. Для большой окрестности уменьшите масштаб, сузьте сегмент направления, либо откройте соседа как новый терминус, вместо того чтобы пытаться уместить всё на одном экране.
Таблица без данных пока что пропускается, а не импортируется
- Что: вкладка, у которой нет ни одного из трёх обязательных маркеров и нет ни одной строки данных — совершенно новая таблица, содержащая только комментарии или строку
@style, — пропускается с предупреждениемEmptyTabSkipped. Это не приводит к провалу импорта из-за трёх отсутствующих маркеров. Её уже сгенерированный код, запечённый ассет и адрес сохраняются, а не удаляются, как будто вкладка была удалена. Экспорт и отправка симметрично пропускают её, поскольку все три спрашивают один и тот же предикат. - Почему: одна незаконченная таблица не должна иметь возможность остановить импорт всех остальных вкладок, а автор обычно создаёт таблицу раньше, чем её строку заголовка.
- Граница: наполовину заполненная таблица (присутствует хотя бы один обязательный маркер) не пропускается — она честно проваливается, поскольку молчаливый пропуск скрыл бы настоящую работу. Таблица, размеченная от столбца A вместо столбца B, точно так же передаётся парсеру, так что её настоящая диагностика («столбец A — это столбец маркеров, данные начинаются с B») сохраняется.
Enum, зарегистрированный из C# плагина, не может получить членов из таблицы
- Что:
Enum<T>, чейTплагин зарегистрировал черезenums.Register<T>(), принадлежит коду. Лист определений enum не может претендовать на это имя (DuplicateEnumName), а строка «Add a new member…» в выпадающем списке ячейки на таком столбце просто отсутствует. - Почему: таблица канонична лишь для того, что она сама определяет. Запись члена в таблицу, которая больше не решает скомпилированный тип, привела бы к появлению члена, который никогда не появится в коде, — обещания, которое продукт не может сдержать. Отсутствующая строка — это способ, которым интерфейс об этом сообщает, вместо того чтобы предлагать действие, которое неминуемо провалится.
- Обходной путь: перенесите enum в лист enum, если таблица должна им владеть, либо добавьте член в C# вашего плагина и перекомпилируйте.
- Структура следует той же линии владения: структура листа определений enum полностью авторится в обоих хостах — определение, переименование, удаление, изменение порядка столбцов, правка базового типа и описания, — но ничто из этого не может затронуть имя enum, которым владеет код, и лист определений тоже не может претендовать на такое имя. Отказ называет причину.
Листы определений enum: члены только добавляются, и сортировки нет
- Что: структура листа enum авторится на месте и в редакторе, и в веб-приложении одинаково, но строки членов только когда-либо добавляются в конец — пробел никогда не заполняется задним числом, — а представление не предлагает ни сортировки, ни фильтра.
- Почему: позиция члена и есть его целочисленное значение. Заполнение пробела или перестановка членов молча перенумеровала бы значения, уже запечённые в ассеты и сохранённые в сохранениях. Порядок столбцов, напротив, не несёт смысла — именно поэтому изменение порядка столбцов всегда разрешено.
- Обходной путь: чтобы явно закрепить значение, используйте синтаксис
Name=value; порядок отображения где-либо ещё — забота потребителя, а не листа.
Enum, определённый в таблице, всегда генерируется в папку настроек
- Что: сгенерированные типы вкладок перегенерируются на месте, в какой бы папке они уже ни находились. У файла enum (
SheetForgeEnums.cs) нет вкладки, к которой можно было бы привязаться, поэтому он всегда записывается в папку сгенерированного кода, указанную в настройках. Если вкладка, чей сгенерированный код живёт в собственной папке пакета, использует определённый в таблице enum, эта сборка пакета не компилируется с ошибкойCS0246. - Почему: один файл несёт все определённые в таблицах enum, поскольку enum — это результат уровня проекта, а не отдельной вкладки, — поэтому нет единственной вкладки, чей дом он мог бы разделить.
- Обходной путь: поместите обе сгенерированные папки в одну сборку, либо зарегистрируйте этот enum из кода плагина вместо этого. Ошибка — это видимая ошибка компиляции с указанным отсутствующим типом, никогда не молчаливое повреждение.
Типизированные ссылки на ассеты разрешаются по загруженным типам — короткое имя, только если оно уникально, без типов из предопределённых сборок
- Что:
AssetRef@Group<Type>принимает любой унаследованный отUnityEngine.Objectтип ассета, который может загрузить проект, — будь то тип движка или ваш собственный, — без списка разрешённых типов. Три вещи отклоняются, а не угадываются: короткое имя, которое разделяют несколько загруженных типов (AmbiguousAssetType— таким может бытьTextAsset, в зависимости от установленных пакетов) должно быть записано как полное имя (UnityEngine.TextAsset); неизвестное имя даётUnknownAssetTypeс предложением ближайшего совпадения; а тип, живущий в предопределённой сборке (Assembly-CSharpи её аналоги — любая папка со скриптами без assembly definition), даётAssetTypeNotReferenceable. - Почему: сгенерированная сопутствующая сборка — это assembly definition, а assembly definition не может ссылаться на предопределённые сборки —
AssetReferenceT<T>для такогоTне скомпилировался бы. Разрешение неоднозначного имени произвольным выбором молча привязало бы столбец не к тому типу. - Обходной путь: перенесите тип в assembly definition, либо снимите ограничение
<…>и оставьте неограниченныйAssetRef@Group. Компоненты и типы, существующие только в редакторе, никогда не являются кандидатами. - Также: браузер не разрешает имена типов (у него нет проекта, относительно которого их разрешать): веб-приложение разбирает
<Type>и показывает его в подсказке столбца, но не выдаёт ни одной из трёх диагностик по имени типа и не предлагает ни пикер, ни перетаскивание. Кодогенерация никогда не выводит неразрешённое имя дословно — имя, которое не удалось разрешить, откатывается кAssetReferenceс предупреждениемAssetTypeUnresolvedFallback.
Пикер ассетов, перетаскивание и подготовленные регистрации — что автоматично, а что нет
- Что: перетаскивание или выбор ассета сразу записывает адрес в ячейку и подготавливает изменение Addressables (добавить · переместить · создать группу) для отражения; изменение выполняется только после того, как запись в таблицу прошла успешно — либо, когда подготовлены только регистрации и больше ничего, само по себе, с последующим автоматическим повторным импортом; отражение, которое не смогло записать, потому что его вкладки происходят из книги, оставляет их подготовленными. Регистрация, на которую больше ничто не ссылается, сбрасывается при повторном вводе ячейки, а любая, дожившая до отражения, пропускается как не имеющая ссылок; ассет, уже присутствующий в группе, сохраняет свой существующий адрес; ассет из другой группы перемещается только после подтверждения, называющего остальные ячейки, которые на него ссылаются. Автоматический адрес — это имя файла без расширения, а адрес, уже занятый другим ассетом в этой группе, отклоняется, а не переименовывается. Новые группы получают схемы по умолчанию
BundledAssetGroupSchemaиContentUpdateGroupSchema. Применённые и пропущенные пункты записываются в лог консоли вместе с причинами, а для источника «локальная папка» диалог завершения отражения повторяет итог (Addressables: N registered, M skipped). - Почему: таблица канонична — проект никогда не должен меняться из-за отражения, которое не дошло до таблицы, а запись, на которую не указывает ни одна ячейка, была бы сиротой, которую таблица не объясняет.
- Обходной путь: если регистрация была пропущена, следующий повторный импорт сообщит о ячейке как о
UnknownAssetKey; устраните причину и отразите снова. Регистрация всегда требует пакет Addressables.
Вложенные ассеты адресуются как parent[sub], а текстура в режиме Sprite проходит <Sprite>
- Что: запись вложенного объекта (спрайт внутри текстуры, материал внутри шрифта) адресуется так, как её называет сам Addressables, —
parent[sub], — и этот ключ проверяется по собственному типу вложенного объекта. Адрес родителя удовлетворяет как своему собственному типу, так и типу любого вложенного ассета, который он содержит, — именно это позволяет текстуре, импортированной в режиме Sprite, проходить проверку столбца<Sprite>. Перетаскивание вложенного ассета подготавливает к регистрации именно родителя и записывает в ячейкуparent[sub]. - Граница: чтобы
parent[sub]прошёл проверку, запись вложенного объекта должна существовать в каталоге Addressables; пикер перечисляет известные ему вложенные ключи после их родителя.
У цветов нет HDR, касательные кривой следуют своему режиму, градиенты квантуются — так и задумано
- Что:
Color— это четыре байта: канал выше 1 (HDR) обрезается до 0…1 при экспорте. КасательнаяAnimationCurve, чья сторона —Auto,Linear,ConstantилиClampedAuto, пересчитывается из режима при импорте, так что число, введённое вручную и противоречащее своему режиму, заменяется (тот же пересчёт, что выполняет Unity при применении режима);Onceчитается какClampForeverи никогда не записывается обратно; кривая без ключей не имеет текстовой формы и существует только как пустая ячейка необязательного столбца. Времена ключейGradientквантуются до 16 бит при импорте (в точности как их хранит Unity), градиент с одним ключом возвращается из Unity как два идентичных ключа, а цветовое пространство записывается, только если оно было задано. - Почему: значение, которое показывает таблица, должно быть тем же значением, которое хранит движок, поэтому нормализация, которую Unity выполнила бы позже, выполняется один раз, на входе, — и каждая поверхность (таблица, поле редактора, веб-превью, запечённый ассет) показывает одну и ту же кривую и один и тот же градиент.
- Обходной путь: используйте
Free/Free, когда хотите, чтобы числа касательных воспринимались буквально; храните интенсивности HDR в отдельном столбцеfloat.
Редакторы-плашки и нативные поля существуют только для трёх визуальных типов
- Что: Data Studio показывает скаляры
Color,AnimationCurveиGradientкак собственные поля Unity, а ихList<>— как редакторы-плашки; веб-приложение показывает превью через собственные редакторы и списки плашек. Любой другой столбец-список —List<int>,List<Enum<…>>, списки wrapper'ов — остаётся каноническим текстом в обоих хостах, и wrapper, содержащий один из этих трёх типов (Pair<Color>), тоже остаётся текстом. - Почему: у этих трёх типов на каждый элемент есть картинка; для остальных единственная каноническая строка уже является самым точным представлением, а внешняя нотация wrapper'а принадлежит его плагину.
- Пространство для развития: тип плагина, который хранит одно из этих трёх значений, может получить те же редакторы, объявив соответствующий архетип
StudioCellEditorHint(см. Создание плагинов, §4.16).
Раскраска по владельцу — это только «таблица против кода»
- Что: боковая панель/легенда различает ровно два происхождения — настоящие вкладки таблиц и виртуальные вкладки реестра кода. Нет третьей классификации «сгенерировано» и нет поколоночной раскраски по владельцу.
- Почему: происхождение выводится из самой вкладки, которую окно уже знает наверняка; поколоночная классификация потребовала бы ещё одной точки расширения, чтобы быть достоверной, а ни один потребитель об этом не просил.
- Не то же самое, что цвет таблицы:
@styleпозволяет таблице назвать свой собственный цвет, и этот цвет — метаданные отображения, выбранные автором, — он ничего не говорит о том, откуда таблица берётся. Обе раскраски читаются из разных мест и никогда не смешиваются.
Выпадающие списки ссылок отвечают на членство, а не на порядок
- Что: у ячейки
RecordId@Tabтеперь есть выпадающий список с поиском (и чек-лист дляList<>), но выпадающий список ячейки списка только добавляет и удаляет элементы — он не может переместить элемент. Изменение порядка списка выполняется на холсте, где у каждого элемента есть своя строка. - Почему: выпадающий список отвечает на вопрос «что здесь есть»; «какая позиция» нуждается в поверхности, которая выстраивает элементы по порядку, а холст уже является такой поверхностью. Дублирование этого в двух местах означало бы поддерживать два ответа.
- Также: выпадающий список подключается только к обычному столбцу-ссылке, не wrapper'у. Текст ячейки wrapper'а несёт собственную нотацию wrapper'а, поэтому вставка голого ключа в неё уничтожила бы значение — доступ внутрь wrapper'а — это задача зарегистрированного виджета ячейки (см. Создание плагинов).
Поиск All читает запечённые данные в редакторе и живую сессию в браузере
- Что: пункт All Data Studio ищет по запечённым базам данных, поэтому сначала нужен один успешный импорт, и он обновляется сам, когда импорт завершается или меняются активные настройки. Пункт All веб-приложения ищет по значениям, которые сессия показывает прямо сейчас, включая подготовленные правки. Сопоставление, порядок результатов и страница по 50 строк — это один и тот же код в обоих случаях, и в обоих случаях результат открывается двойным щелчком (или клавишей Enter на выделенной строке), открывая эту таблицу с выделенной найденной ячейкой.
- Почему: у браузера нет запечённых ScriptableObject; у него есть живая сессия — и ответ значением на экране это именно то, для чего нужна браузерная сессия. Редактор продолжает читать запечённую истину, которая у него уже есть.
- Граница: в редакторе правка, которая подготовлена, но ещё не отражена, не находится через All, пока не выполнен импорт, а результат из листа, который сессия не загрузила, не переходит по клику — уведомление под списком объясняет почему. В браузере подготовленная правка находится сразу же, и к любому результату можно перейти.
5. Производительность
- Обычный путь линеен и быстр: 50,000 строк × 20 столбцов ≈ 628 ms (живой редактор, Mono; 144 ms в headless-режиме); 50 вкладок × 2,000 строк со 180k ячеек-ссылок ≈ 294 ms. Типичные размеры проектов не представляют проблемы.
- Память растёт линейно, но с большими затратами на boxing: ≈ 59 байт/ячейку в удержании (≈ 138 пиковых во время импорта). При потолке ячеек Google Sheets (~10M ячеек) это экстраполируется в ~6.3 s импорта, ~590 MB в удержании, ~1.4 GB пиковых — учитывайте маломощные/32-битные окружения при экстремальных размерах. (Ориентированный на столбцы IR — это признанный пункт бэклога.)
- Таблица авторинга виртуализирована по строкам свыше 200 строк и в редакторе, и в браузере, так что открытие крупной таблицы больше не строит виджет на каждую строку. Что не виртуализировано — так это построчная логика, которая давала бы другой ответ, если бы смотрела только на экран, — замер ширины, поиск, сортировка, координаты, оверлей подготовки. Подробности и два нюанса — в §4.
- Путь ошибок тоже линеен: даже когда ссылки ломаются массово, вычисление ближайших предложений остаётся ограниченным. Бюджет предложений на каждое поле и отфильтрованный по длине, с досрочным завершением расчёт расстояния редактирования удерживают его примерно линейным относительно числа сломанных ссылок (≈ 45 ms при 4,000 сломанных ссылках, headless; корректные данные того же масштаба ≈ 2.7 ms). Переименование вкладки, на которую есть ссылки, само по себе не ломает ссылки массово: переименование переписывает ссылающиеся ячейки
@type.
6. Демонстрационная сцена
- Примеры — это выборочные импорты — оба демо-примера и их сцены не присутствуют по умолчанию. Они поставляются как пакеты Unity (
Assets/SheetForge/Examples/SheetForgePluginDemo.unitypackageиSheetForgeCoreDemo.unitypackage). Импортируйте один из них — двойной щелчок, либо кнопка Импортировать Plugin/Core Demo в окне «Начало работы» — чтобы восстановитьAssets/SheetForge.PluginDemo/…илиAssets/SheetForge.CoreDemo/…. До этого примеров вообще нет в вашем проекте — они поставляются только в виде этих пакетов — поэтому они никогда не могут конфликтовать с вашим проектом. Основной продукт полностью самодостаточен без них. - Сначала требует один импорт (на каждой машине) — адреса Addressables, которые он загружает, представляют собой некоммитящийся кеш. До этого он показывает сообщение с рекомендациями.
- Настройка пространства имён для демо не нужна — закоммиченные типы примеров используют пространство имён по умолчанию
SheetForge.Generatedс префиксом классаExample*, поэтому повторный импорт демо перегенерирует их на месте без какой-либо настройкиgeneratedNamespace.
7. Расширение плагинами — реализованные стыки (с границами) и то, что всё ещё зарезервировано
Поставляются шестнадцать контрактов расширения, каждый из них подключается без единой правки Core — полный набор описан в Создании плагинов. Все шестнадцать обнаруживаются через обнаружение (TypeCache Unity в редакторе; сканирование загруженной сборки в браузере), без ссылки на сборку и без манифеста для правки. Одиннадцати Core-контрактам передаётся реестр, в который они регистрируют то, что добавляют, тогда как пяти контрактам Editor — графовому виджету, действию инспектора, провайдеру редактора ячейки, провайдеру панели и провайдеру источника — обнаруживаются и используются как есть.
У пяти из реализованных стыков есть границы, которые стоит обозначить здесь, а не оставлять вам их обнаруживать:
Ссылающиеся пользовательские типы ячеек — полный паритет RecordId@Tab для вашей собственной нотации
-
Что: зарегистрированный парсер ячейки, который также реализует
IReferencingCellType(а его значение реализуетIRefBearingValue), говорит Core, как читать и переписывать ключ, зарытый в его собственной нотации. Такой столбец затем получает всё, что получает встроенная ссылка:- проверку целостности с предложениями ближайшего совпадения;
- распространение переименования ключа с сохранением полезной нагрузки (
attack:add:10→power:add:10); - рёбра и порты графа, пикер
▾, счётчики обратных ссылок; - обнаружение сирот и правила экспортируемых выпадающих списков.
Обнаружение — это приведение типа уже зарегистрированного парсера — отдельного канала регистрации нет, а пользовательский тип, который его не реализует, не меняется ни на бит. См. Создание плагинов, §4.4a.
-
Границы: полезная нагрузка не может содержать
;— Core разбивает ячейку списка на элементы прежде, чем ваш парсер вообще увидит текст, поэтому точка с запятой внутри значения была бы раздроблена на два элемента (то же ограничение несут wrapper-типы). А@targetдолжен называть настоящую вкладку таблицы: виртуальная вкладка реестра кода отклоняется сUnknownTargetTab, точно так же, как и дляRecordId@Tab. Именно это ограничение позволяет собственным механизмам Core — отчётности о неразрешённых ссылках и распространению переименования — применяться без изменений.
Wrapper-типы <> (MyWrapper<T>) — с правилами отклонения
-
Что: плагин регистрирует обобщённую форму значения (например,
Pair<int>=1~2) черезICellWrapperType; Core рекурсивно разрешает внутренний тип (см. Синтаксис таблиц). -
Правила отклонения:
Pair<List<T>>отклоняется — список не может находиться внутри wrapper'а, аListостаётся плоским и всегда самым внешним.Pair<int>@Tabотклоняется — ставьте@на внутреннем листе:Pair<RecordId@Tab>.Pair<int?>/Pair<int=1>отклоняются — необязательность/значения по умолчанию — это уровень поля, а не часть внутреннего типа.
List<Pair<T>>разрешён, но собственный разделитель wrapper'а должен отличаться от;(разделителя списка) — это ответственность автора плагина, которую Core обеспечить не может.
Пользовательские структурные маркеры (@yourMarker) — только для поколоночных метаданных
- Что: плагин регистрирует строку
@markerчерезIStructuralMarkerDefinition/ISheetForgeMarkerPlugin, обобщая поколоночную проверку@overlap. Значение хранится как метаданныеFieldSchema.MarkerValues. - Границы: маркер отвечает только за проверку значения по столбцу — он не берёт на себя разбор формы данных целой строки (нормализация остаётся способом выразить формы данных). А генерация кода не запекает значения маркеров: как и
@overlap, это лишь метаданные для проверки/отображения, невидимые для отпечатка схемы — поэтому ничего, связанного с маркерами, не попадает ни в сгенерированный код, ни в запечённый SO.
Цветовые пресеты — поверхности, которые рисуем мы, а не виджеты Unity
- Что: плагин регистрирует цветовой пресет через
ISheetForgeThemePlugin/ThemeRegistry. Он появляется вPreferences ▸ SheetForge ▸ Themeрядом со встроенными пресетами Default и High contrast и применяется, только если пользователь его выберет (регистрация никогда не захватывает экран самовольно). Пресет переопределяет только те слоты, которые называет, — каждый остальной слот сохраняет значение продукта по умолчанию, поэтому пресеты остаются валидными по мере добавления новых слотов. - Граница — смешанное оформление ожидаемо: тема покрывает то, что SheetForge рисует сам (фоны окон, заголовки, текст, акценты, цвета сетки и подготовки). Нативные виджеты Unity, отрисованные внутри этих окон — оформление кнопок, рамки полей, стрелки всплывающих меню — продолжают следовать скину редактора, который Unity не позволяет пакету перестилизовать. Так что выбор Always light, пока редактор работает в тёмном скине, даёт светлую поверхность SheetForge с тёмными нативными виджетами на ней. Настройте скин редактора под неё, если хотите единообразный вид.
- Граница — только цвет: пресеты несут цвета (
0xRRGGBBна слот). Отступы, размеры шрифта и раскладка не темизируются, а полупрозрачные заливки (фоны значков, затемнение модальных окон) выводятся из цвета слота плюс фиксированная альфа, а не настраиваются отдельно.
Декларативные поверхности авторинга — намеренно ограниченный словарь
- Что:
ISheetForgeStudioPluginпозволяет пакету описывать команды, панели, значки столбцов и формы редактора ячейки как данные, так что одна регистрация рисуется и редактором, и браузером одинаково. Словарь фиксирован и растёт только через добавление: пять мест размещения действий, тринадцать видов узлов, семь архетипов редактора ячейки (два новейших,CurveEditorиGradientEditor, — те, что используют встроенные типы кривой и градиента). - Граница — это не UI-фреймворк. У произвольной отрисовки, составного ввода и многошаговых сценариев здесь нет слов, а добавить их означало бы вечно поддерживать миниатюрный набор UI-инструментов. Для этого и существует
IStudioPanelProvider: зарегистрируйте его под тем же id, что и описанная панель, и редактор нарисует богатую версию, пока браузер рисует описанную. Лазейки только для веба нет — браузер не может загрузить тип UIToolkit, а притворяться иначе означало бы, что расширение плагина работает только на одном экране. - Граница — у действия ровно четыре возможности: подготовить одну ячейку, подготовить несколько ячеек как один шаг Undo, сфокусировать запись, запросить перерисовку. Поэтому команда плагина — это обычная подготовленная правка, которая проходит через тот же шлюз, предварительную проверку и отправку, что и правка, введённая вручную. Сама сессия авторинга намеренно не раскрывается.
- Никогда не срабатывает вхолостую и никогда не срабатывает ошибочно — для наблюдателей:
IPipelineObserverсрабатывает в конце явного цикла импорта. Два пути вообще никогда не достигают этой точки — прогон, остановившийся до начала конвейера (нет активных настроек; Addressables не установлен), и этап генерация кода→компиляция, прерванный ошибкой компиляции. Если вам нужно «была предпринята попытка импорта», сочетайте это с шинойImportEventsна стороне редактора.
Спроектировано, но ещё не реализовано (пока нет потребителя)
- Маркеры формы данных для целой строки (например, один маркер, читающий 2D-матрицу как единое поле) — намеренно не реализованы: маркер отвечает за поколоночную проверку, а не за разбор строки, а нормализация (ссылки + столбец
type+List<T>) выразительно полна. Сам стык регистрацииMarkerRegistryреализован; зарезервирована только эта трактовка — разбор формы. - Запекание значений пользовательских маркеров в сгенерированный код — не входит в текущий объём, пока какому-либо потребителю не понадобятся метаданные маркеров в виде констант/атрибутов генерируемого кода.
- Контейнеры SO для отдельных записей / с отложенной загрузкой — проектирование завершено, но не реализовано; текущая генерация кода создаёт только Database SO с полной загрузкой на вкладку.
8. Эволюция схемы
- Экспорт/отправка требуют свежего запекания после изменения схемы — устаревшее запекание завершается ошибкой
ExportSchemaMismatch(несовпадение отпечатка). Теперь отказ предлагает выполнить этот импорт за вас: один подтверждающий клик запускает его, и после этого ничего не экспортируется и не отправляется автоматически — вы снова нажимаете исходное действие, как только импорт завершится. - Первый импорт после изменения схемы внутренне состоит из двух этапов (генерация кода → компиляция → запекание) — автоматически, одно действие пользователя; останавливает его только неудача компиляции (безопасное прерывание, предложение конкретных действий, лимит попыток — 3).
9. Границы локализации
Фрагменты деталей отчёта (некорректные значения, предложения), низкоуровневые исключения и логи для разработчиков — это встроенный английский текст внутри локализованного каркаса отчёта — подстановки во время выполнения не могут быть ключами языковой таблицы (стандартная для индустрии граница). Всё, что отрисовывается редактором, плюс каркас отчёта и предложения «почему»/«как», полностью локализовано на всех 10 языках.
Все десять языков переведены полностью. Каждый ключ в каждой языковой таблице несёт настоящий перевод — включая Data Studio, настройки темы, диалоги и строки логов. Паритет ключей между десятью файлами обеспечивается тестами, поэтому ничего не откатывается к сырому ключу и не ломает плейсхолдер.
Горстка записей в каждом языке действительно читается точно так же, как английская версия, и это переводческое решение, а не ожидающий перевод: это символы и строки-заполнители (—, +), имена собственные и названия форматов (Google Sheets, SHA-256), а также слова, которые в этом языке действительно пишутся так же, как в английском (OK, Alpha).
Собственные метки плагина вообще не попадают в таблицу Core: зарегистрируйте их через ISheetForgeStringsPlugin, чтобы они следовали языку пользователя, либо оставьте незарегистрированными — тогда они отображаются дословно.
10. Взаимодействия с редактором
- Границы Ctrl+Z: сфокусированное текстовое поле первым перехватывает Ctrl+Z (стандарт ОС); после успешного отражения история подготовленных изменений очищается — undo никогда не дотягивается до того, что уже записано в таблицу (таблица канонична).
- Смена языка заблокирована во время импорта/экспорта/отправки (она вызывает перекомпиляцию из-за перегенерации меню). Изменения темы не блокируются — они никогда не вызывают перекомпиляцию, поэтому яркость и пресет можно переключать в любой момент, в том числе во время выполнения операции.
- Тема и язык — это настройки для каждого пользователя (EditorPrefs), а не для проекта — у каждого участника команды свои собственные, и ни одна из них не попадает в систему контроля версий. Выбор нестандартного цветового пресета записывает один сгенерированный файл стилей в
Assets/SheetForge/Editor/Generated/(исключён из Git, самовосстанавливается); пресет по умолчанию ничего не записывает и удаляет его. - Сгенерированный код + запечённые SO + группа Addressables — это исключённые из Git кеши, локальные для каждой машины — каждая машина один раз выполняет импорт; код игры загружает данные по адресу, никогда через прямую ссылку из сцены.
11. Лицензирование
В репозитории есть уведомление LICENSE: регулирующим соглашением является Unity Asset Store EULA, с уведомлением о просмотре репозитория (исходный код виден для справки и для лицензированных покупателей; никакого распространения/перепродажи за пределами EULA без письменного разрешения). Сторонний код: отсутствует — включая написанный вручную читатель/писатель OOXML xlsx.
12. Проверенное состояние (на момент релиза)
- Двойной тестовый набор: 2,150 headless-тестов .NET (2,150 проходят) + 3,021 тест EditMode (3,021 проходит, 0 провалов, 4 пропущено). Эти два числа — единственное место, где указаны счётчики; все остальные страницы ссылаются сюда.
- Четыре пропуска — это живой round-trip с Google, который запускается, только когда в окружении присутствуют учётные данные сервисного аккаунта, и в этом прогоне был пропущен. С учётными данными он неоднократно проверялся на реальной таблице — получение → отправка, включая обнаружение конфликта перемещённой строки, независимую от локали обработку float, смешанный межвкладочный пакет и полный запуск диспетчера, утверждающий один объединённый отчёт и ровно один автоматический повторный импорт на отправку. Результат: 4/4 успешно.
- У веб-приложения свои собственные проверки, все зелёные: проверка типов, линт, 414 юнит-тестов, публикация в WebAssembly + прогон дымового теста (который загружает настоящую DLL плагина), продакшн-сборка, 56 сквозных браузерных тестов и проверка констант между языками (шесть пар Unity↔веб: версия хоста, формат плагина, версия схемы реестра, область OAuth, имена сборок хоста, пороги виртуализации строк).
- Все скомпилированные asmdef продукта: 0 ошибок, 0 предупреждений. (Asmdef примеров — по два в каждом демо — появляются только когда вы импортируете демо-пакет, и не компилируются до этого момента.)
- Защитные тесты зелёные: ноль корейских литералов в исходном коде продукта, ноль домен-специфичных терминов в стыках ядра (оба пропускают
/Samples~/— пример является доменным содержимым), паритет ключей на 10 языках. - Сборка-симулятор потребителя без IVT компилируется исключительно против публичного API (утверждение о ядре, обеспечиваемое компилятором). Находящаяся там мини-плагин-проба без IVT реализует пятнадцать из шестнадцати контрактов расширения (
ISheetForgePlugin/ валидатор / ребро / маркер / шаблон / граф / реестр кода / тема / studio UI / строки / конвейер /ISheetSourceProvider/ виджет Studio / действие инспектора Studio / редактор ячейки Studio), используя только публичную поверхность, так что публичность контрактов остаётся доказанной, даже когда пример Plugin Demo не компилируется. Шестнадцатый,IStudioPanelProvider, возвращаетVisualElementи вместо этого задействован тестом на стороне редактора. Та же проба также реализует опциональные интерфейсы возможностей, включаяIReferencingCellType/IRefBearingValue, и задействует их через публичную поверхность (обнаружение через приведение типа, все пять хуков, сохранение остатка).
Пункты, которые можно проверить только во время публикации в Asset Store (не в репозитории): поведение диалога установки .unitypackage, объявление зависимости в Portal, исключение тестовых сборок из распространяемого пакета, и повторная проверка на 0 предупреждений в чистом проекте.
13. Таблицы локализации (игровой текст)
Границы Таблиц локализации и моста Unity Localization, честно изложенные. (Сам пакет необязателен — см. §1.)
Мост однонаправленный, а о внешних правках таблицы спрашивают — но никогда не сливают их автоматически
- Что: синхронизация идёт только в направлении таблица → StringTable. Мост помечает таблицы, которыми владеет, и снимает отпечаток при каждой синхронизации; если с тех пор StringTable отредактировали чем-то ещё — окном Localization Tables, собственным расширением Google Sheets от Unity, импортом XLIFF — следующая синхронизация останавливается и спрашивает: перезаписать из таблицы или прервать с отчётом о различиях.
- Почему: два интерфейса записи поверх одних и тех же данных заканчиваются тихими перезаписями. Таблица канонична, поэтому другой интерфейс должен действовать явно, а не молча.
- Обходной путь: проводите переводы через таблицу — книга переводов (экспорт в xlsx + частичный повторный импорт только языковых столбцов) существует именно для этого.
Переименование ключа вне Studio — это удаление плюс добавление
- Что: переименование ключа в Data Studio переименовывает запись таблицы на месте, сохраняя внутренний id, к которому привязываются ссылки
LocalizedString, — ссылки из сцен выживают. Переименование ключа прямо в источнике таблицы (Google Таблицы, Excel) неотличимо от удаления одного ключа и добавления другого: мост создаёт новую запись, а старая становится сиротой, и ссылки из сцен продолжают указывать на сироту. - Почему: сравнение на уровне текста не может отличить переименование от удаления-плюс-добавления без угадывания, а неверная догадка молча перепривязала бы ссылки.
- Обходной путь: переименовывайте ключи в Data Studio (в любом из хостов); отчёт о сиротах фиксирует последствия внешнего переименования.
Ключи-сироты в таблице по умолчанию сохраняются
- Что: ключ, присутствующий в StringTable, но которого больше нет в листе, сохраняется, отмечается как сирота и удаляется только через явное действие очистки — либо автоматически, если включить настройку удалять при синхронизации. Ничего не удаляется как побочный эффект.
- Почему: отсутствующая строка в таблице может быть ошибкой посреди редактирования; уничтожать из-за этого переводы было бы невосстановимо.
Только StringTable — AssetTable не охвачены
- Что: мост заполняет коллекции
StringTable. ОсьAssetTableUnity Localization (локализованные спрайты, аудио, префабы) не синхронизируется из таблиц. Признанный пункт бэклога. - Обходной путь: управляйте AssetTable собственными инструментами пакета; мост их не трогает.
XLIFF и псевдоязыки не реализуются заново
- Что: синхронизируемые таблицы — это обычные таблицы Unity Localization, поэтому собственные экспорт/импорт XLIFF пакета и псевдолокализация работают на них без изменений. SheetForge не добавляет вторую реализацию.
- Граница: результат, который эти инструменты записывают в таблицы, считается внешней правкой (первый пункт выше). Держите таблицу источником истины и переносите переводы через книгу переводов.
Языкоспецифичные помощники Smart Format не поставляются
- Что: столбец
smartпомечает запись как Smart String, но SheetForge не предоставляет собственных грамматических форматтеров — например, выбор корейских частиц намеренно не включён. - Обходной путь: точки расширения Smart Format самого пакета остаются полностью доступны для форматтеров, которые вы напишете сами.
Константы ключей очищаются до ASCII
- Что: сгенерированные константы
{Tab}Keysпревращают каждый символ вне ASCII-букв, цифр и_в_(коллизии получают числовой суффикс), поэтому не-ASCII ключ даёт нечитаемое имя константы — сам ключ при этом продолжает работать везде. - Обходной путь: держите ключи в ASCII (
ui.ok,dialog.intro), если вы используете константы. Как и файл enum, определённый таблицей, файл констант всегда генерируется в папку настроек — применимо то же замечание о сборке.
Веб-приложение авторит таблицы локализации; синхронизация — задача редактора
- Что: авторинг, проверка, покрытие, чеканка ключей, языковая линза и книга переводов — всё это работает в браузере. Запись StringTable — нет: у браузера нет проекта Unity, в который можно было бы писать.
- Почему: честные границы возможностей, а не отсутствующая функция: таблицы живут в проекте.
Похожие страницы
- Частые вопросы и устранение неполадок — взгляд на многие из этих пунктов через призму симптомов
- Источники, экспорт и отправка — границы Google/xlsx в контексте
- Data Studio — границы авторинга в контексте