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) В карточке «переменной нет в рантайме»