CustomVariablesDashboard
Сборки: ru.vortex.nani.variables.dashboard (рантайм, контракт), ru.vortex.nani.variables.dashboard.editor (редактор, окно)
Namespace: Vortex.NaniExtensions.CustomVariablesDashboard
Активация
Обе сборки — defineConstraints: ["USING_NANINOVELL"] (тоггл naninovellExt в SdkSettings). Источник квестов Vortex подключается только при USING_VORTEX_QUESTS; без него панель работает по сценариям .nani.
Назначение
Панель переменных Naninovel: Tools → Vortex → Debug → Custom Variables Dashboard.
- Значения — переменные движка в Play Mode с текущими значениями и правкой.
- Связи — для каждой переменной: где её пишут, читают, сравнивают и ждут — в сценариях
.naniи в квестах Vortex отдельно; клик по месту открывает сценарий на строке во внешнем редакторе или выделяет пресет квеста, кнопка Find у каждой строки выделяет сценарий или пресет и показывает его в инспекторе. Если ассет перемещён или удалён после сборки — предупреждение в консоль. Работает и без запуска игры. - Потеряшки — объявлены, но не используются; используются, но не объявлены; не пишутся; не читаются; двойники по регистру; исключения без переменной.
- Проблемы — то, что не удалось прочитать при сборке.
Вне ответственности:
- Употребления переменных вне сценариев и квестов (префабы с
NaniVariableSwitcher, каталог персонажей, код сSetVariableValue/GetVariableValue) — не сканируются; ложные потери от них закрываются исключениями. - Окно «Nani Variables» плагина
NarrativeImportTool— отдельный инструмент, не затрагивается.
Зависимости
- Naninovel —
Engine,ICustomVariableManager,CustomVariablesConfiguration - Редактор Unity —
AssetDatabase,SerializedObject,ScriptableSingleton - Квесты Vortex — только по имени типа пресета (
t:QuestPreset) и именам его сериализованных полей; ссылки на сборку квестов нет
Использование
Сборка карты связей
Кнопка «Собрать» на любой вкладке, кроме «Значения». Сборка читает конфиг переменных, все сценарии .nani в Assets и все пресеты квестов проекта; индикатор прогресса с отменой. Карта живёт только в памяти: сбрасывается при перекомпиляции и при каждом входе в Play Mode (в проекте включена перезагрузка домена). Изменения сценариев и квестов после сборки не отслеживаются — ориентир актуальности время сборки в окне.
Подключение нового условия или логики квеста
Объект квеста, работающий с переменной Naninovel, реализует INaniVariableReference — иначе панель его не видит, а переменная может оказаться в «потеряшках»:
[Serializable]
public class NaniVariableRangeCondition : QuestConditionLogic, INaniVariableReference
{
[SerializeField] private string variableName;
[SerializeField] private string range;
string INaniVariableReference.VariableName => variableName;
NaniVariableOperation INaniVariableReference.Operation => NaniVariableOperation.Compare;
string INaniVariableReference.Operand => range;
// ...
}
Контракт поведение в игре не меняет. Сейчас его реализуют NaniVariableCondition (сравнение), NaniVariableValueComparation (сравнение), NaniVariableValueWait (ожидание).
Объект, работающий с формулой из нескольких переменных, реализует INaniExpressionReference — одно свойство Expression. Панель разбирает формулу тем же разбором условий, что if: и @if в сценариях: каждая переменная — своя связь со сравнением или чтением, место у всех — этот объект квеста. Так реализован NaniExpressionComparation.
string INaniExpressionReference.Expression => expression; // "Day>=3 || (Flag && Money>100)"
Объект может реализовать оба контракта — обрабатываются независимо.
Исключения
Настройки проекта: ProjectSettings/CustomVariablesDashboard.asset (в контроле версий, общий для команды). Запись — имя переменной (с учётом регистра), виды потерь, причина. Исключение убирает переменную только из секций указанных видов; в списке «Связи» и карточке она видна всегда. Добавляются кнопкой «Исключить» в секции потери (дополняет вид существующей записи на это имя) или в секции «Исключения». Двойники по регистру не исключаются.
Архитектура
CustomVariablesDashboard/
├── INaniVariableReference.cs ← контракт + NaniVariableOperation (рантайм)
└── Editor/
├── CustomVariablesDashboardWindow*.cs ← окно: Values / Links / Losses / Problems
├── Model/
│ ├── VariableModels.cs ← объявление, связь, места (.nani / квест), проблема, LossKinds
│ ├── VariableIndex.cs ← VariableEntry, VariableIndex (Current — static, память)
│ ├── LossAnalysis.cs ← виды потерь, двойники, исключения без переменной
│ └── DashboardSettings.cs ← исключения (ScriptableSingleton в ProjectSettings)
└── Sources/
├── VariableIndexBuilder.cs ← сборка и Rebuild: конфиг → .nani → квесты → переменные
├── NaniScriptSource.cs ← разбор .nani
└── QuestSource.cs ← обход пресетов квестов (USING_VORTEX_QUESTS)
Зависимости однонаправленные: окно → сборщик и источники → модель. Модель не знает ни сборщика, ни источников. Место связи (LinkLocation) полиморфно: Describe, Open (переход), Find (выделить ассет в инспекторе).
Словарь операций
| Вид | Что значит | Пример |
|---|---|---|
| запись | присваивание | @set Day=1, @set Day++, @set Day+=2 |
| сравнение | условие с оператором и операндом | @if Day>=3, if:Flag (== true), if:!Flag (== false), условие квеста >=3 |
| ожидание | логика квеста ждёт истинности сравнения | NaniVariableValueWait |
| чтение | участие в выражении без сравнения | b в @set a=b+1, аргумент функции, арифметика в условии |
| прочее | не распознано, показывается исходный текст | — |
Для потерь «не пишется» / «не читается»: сравнение, ожидание и чтение — чтение; составное присваивание — запись; прочее не считается. Начальное значение из конфига записью не считается.
Разбор .nani (эвристика, не компилятор Naninovel)
- Разбираются
@set, параметрif:любой команды,@if,@elseif; команды в строках текста ([set …]) — так же. Комментарии (;) и метки (#) пропускаются. - Переменная в выражении — идентификатор вне строкового литерала, за которым не идёт
(, и неtrue/false. В@set CardsUnlocked++ if:isUnlocked("VArtCop00")переменная одна —CardsUnlocked. - Сравнение
3 < Dayпоказывается какDay > 3. Одиночный=в условии — равенство (if:Day=1). - Выражение в фигурных скобках (
if:{a=5 && b>=70}) — один параметр, пробелы внутри не разделяют. - Выражение с пробелами без кавычек разбирается по первому слову — так же, как его видит Naninovel.
- Подстановки в тексте (
{Var}в строках диалога) не разбираются: переменная, которую только подставляют в текст, попадёт в «не читается».
Обход квестов
Все ассеты QuestPreset проекта, через SerializedObject: поля startConditions (старт), interruptConditions (прерывание), logic (логики) с вложенными гейтами на любую глубину; награды не обходятся. Место — пресет и путь: «Старт › группа 2 › условие 1 › вложенное 3», «Логики › 4». Связи дают объекты, реализующие INaniVariableReference (одна связь) и INaniExpressionReference (связь на каждую переменную формулы). Битая ссылка на тип, пустой элемент, пустое имя переменной, пустая формула — в «Проблемы».
Контракт
Вход
CustomVariablesConfiguration.PredefinedVariables, ассеты.naniвAssets, ассетыQuestPreset- Объекты квеста, реализующие
INaniVariableReferenceилиINaniExpressionReference
Выход
- Окно; запись — только исключения в
ProjectSettingsи правка значения переменной в Play Mode
Гарантии
- Сборка заменяет карту целиком или не меняет её (отмена, исключение в сборке — прежняя карта)
- Каждое объявление даёт переменную в карте, даже без связей; каждая связь — ровно в одной переменной с тем же именем
- Имена сравниваются с учётом регистра, как в менеджере переменных Naninovel
- Виды потерь вычисляются при показе из текущей карты и исключений, нигде не хранятся
- Ошибка одного ассета не обрывает сборку
Ограничения
- Разбор выражений — эвристика: экзотические выражения могут попасть в «прочее»
- Карта не отслеживает изменения ассетов; пересборка — вручную
- Текущее значение в карточке «Связи» обновляется живьём, только пока окно подписано на менеджер переменных — после открытия вкладки «Значения»; иначе — при перерисовке окна
- Подстановки
{Var}в строках текста не разбираются (см. «Разбор.nani») - Имена полей пресета квеста (
startConditions,interruptConditions,logic) и имя типа (t:QuestPreset) зашиты в источник квестов. Поломка шва громкая: поля нет в пресете или пресетов не найдено — проблема сборки, связи не теряются молча
Граничные случаи
| Ситуация | Поведение |
|---|---|
| Карта не собрана | Подсказка и кнопка «Собрать» |
| Вход в Play Mode, перекомпиляция | Карта сброшена, собрать заново |
| Пустое / повторное имя в конфиге | Проблема сборки; повтор — учитывается первое |
| Пустой элемент в группе условий или логик | Проблема сборки |
| Исключение при обходе одного пресета | Проблема сборки, остальные пресеты обходятся |
В пресете нет поля раздела (startConditions / interruptConditions / logic) |
Проблема сборки: структура пресета изменилась |
| Пресеты квестов не найдены при включённых квестах | Проблема сборки: переименован тип пресета |
Незакрытая кавычка в команде .nani |
Проблема сборки со строкой; остаток строки разобран одним параметром |
| Клик или Find по ассету, перемещённому после сборки | Предупреждение в консоль |
| Битая ссылка на тип в пресете | Проблема сборки с именем типа |
| Ссылка на переменную с пустым именем | Проблема, а не связь |
| Несколько исключений на одно имя | Виды объединяются |
| Исключение на имя, которого нет в карте | Секция «Исключения без переменной» |
| Исключение с пустым именем | Не действует, предупреждение |
USING_VORTEX_QUESTS выключен |
Только сценарии; в карточке «Квесты Vortex отключены» |
| Переменной нет в рантайме (Play Mode) | В карточке «переменной нет в рантайме» |