ContentTagsSystem

Namespace: Vortex.Sdk.ContentTagsSystem.* Assembly: ru.vortex.sdk.contenttags Условная компиляция: defineConstraints: ["USING_VORTEX_CONTENT_TAGS"], тоггл в SdkSettings


Область применения

Пакет закрывает один класс задач: одна и та же игра выходит в нескольких изданиях, и отличаются они не содержанием, а составом показанного. Решение об этом принимается один раз — при сборке, — и дальше не меняется ни игроком, ни ходом игры.

Типовой пример — главное меню. В сборке для Steam на нём есть кнопка «Оценить игру» и блок ссылок на соцсети; в сборке для паблишера их быть не должно — ни кнопки магазина, ни внешних ссылок. Раздел «Дополнения» нужен там, где DLC продаются, и бессмыслен в демо-версии. Содержимое меню при этом общее: те же префабы, те же сцены, тот же код.

Без пакета это делается правкой префабов под каждую сборку: собрал для Steam — вернул кнопку, собрал для паблишера — убрал. С пакетом кнопка один раз помечается тегом store_links, тег включается в бандле steam и не включается в бандле publisher_x, а перед сборкой меняется одно поле — активное издание.

Те же очертания у соседних задач:

Задача Тег В каких изданиях включён
Кнопка «Оценить в Steam» store_links steam
Блок ссылок на соцсети social_links steam, gog
Раздел дополнений в меню dlc_section везде, кроме demo
Призыв купить полную версию demo_upsell только demo
Отладочные пункты меню dev_tools ни в одном релизном

Когда пакет не подходит:

  • контент обязан физически отсутствовать в сборке — цензура, возрастные ограничения, региональные запреты: это AssetSwapSystem, подмена ассетов перед сборкой. Здесь выключенное остаётся в билде;
  • показ зависит от того, что сделал игрок — открыл, посмотрел, купил: это RecordMarksSystem и прочие системы прогресса;
  • показ зависит от права на платный контент: это проверка владения на стороне площадки, а не конфигурация сборки;
  • переключение нужно игроку в настройках — тогда это обычная настройка игры, а не издание.

Назначение

Конфигурация состава интерфейсов и систем под издание сборки. Одна игра выходит в нескольких изданиях — витрина Steam, витрина GOG, сборка под паблишера, демо — и отличается набором показанного: кнопка магазина, блок соцсетей, раздел дополнений, пункт меню.

Механика состоит из трёх вещей:

  • тег — строковый ключ, объявленный в ассете настроек;
  • бандл — издание: белый список включённых в нём тегов;
  • активный бандл — одно поле, определяющее конфигурацию текущей сборки.

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

Вне ответственности:

  • Физическое отсутствие контента. Выключенное остаётся в билде. Для изданий, где контент обязан отсутствовать (цензура, возрастные ограничения), существует AssetSwapSystem — подмена ассетов перед сборкой. Этот пакет — мягкая конфигурация представлений.
  • Чистка данных игрока. Прогресс по контенту, выключенному в этом издании, остаётся в сохранении и просто не показывается. Квесты, инвентарь, галерея не фильтруются.
  • Выбор издания в пайплайне. Значение в ассете проставляет сборщик — вручную или скриптом перед билдом.

Зависимости

Зависимость Назначение
ru.vortex.extensions SwitcherState
ru.vortex.unity.ui.misc UIStateSwitcher, [StateSwitcher]
ru.vortex.unity.editortools [ClassLabel], [ToggleButton] в тоггле SDK
Sirenix Odin Inspector [ValueDropdown] для выбора тегов
UnityEditor (под #if UNITY_EDITOR) выпадашки и окно ревизии

Использование

Настройка проекта

  1. Включите тоггл пакета в Tools → Vortex → Configs → SDK Settings.
  2. Create → Vortex → Settings → Content Tags, положите ассет в Resources. Быстрый доступ к нему потом — Tools → Vortex → Content Tags → Settings.
  3. Объявите теги: ключ плюс описание, зачем тег заведён.
  4. Заведите бандлы изданий и отметьте в каждом включённые теги.
  5. Выберите activeBundle.

Разметка контента

// Любая система может спросить напрямую — не только UI.
if (ContentBus.IsActive("store_links"))
    BuildStoreSection();

На сцене: ContentTagGateHandler там, где состояние настраивается свитчером; ContentTagObjectHandler там, где раздела просто не должно быть.

Перед сборкой издания

  1. Поставьте нужный activeBundle в ассете.
  2. Откройте Tools → Vortex → Content Tags → Index, нажмите Обновить.
  3. Проверьте состав издания и разделы «объявлены, но не используются» и «используются, но не объявлены» — второй означает опечатку в компоненте или удалённое объявление.
  4. Find в строке носителя: открытая сцена — выделение объекта, префаб — открытие в режиме редактирования и выделение, закрытая сцена — подсветка ассета.

Архитектура

ContentBus (static)
  ├── IsActive(tag): bool                  ← включён ли тег в текущем издании
  ├── Matches(tags, mode): bool            ← сопоставление списка по режиму
  ├── ActiveBundle: string
  ├── ActiveTags: IReadOnlyCollection<string>
  └── Build()                              ← ленивое чтение ассета при первом обращении

ContentSettings (SO, Resources)
  ├── tags: List<ContentTag>               ← объявленные теги
  ├── bundles: List<ContentBundle>         ← издания
  ├── activeBundle: string                 ← издание текущей сборки
  └── Validate()                           ← пустые и дублирующиеся ключи, необъявленные теги

Handlers
  ├── ContentTagGateHandler                ← UIStateSwitcher: On / Off
  └── ContentTagObjectHandler              ← SetActive на списке целей

Editor
  ├── ContentTagsIndex                     ← индекс «тег → носители» одним проходом
  └── ContentTagsWindow                    ← Tools/Vortex/Content Tags/Index, только чтение

Порядок работы

  1. При первом обращении к шине читается ассет из Resources.
  2. Теги активного бандла становятся активным набором; набор живёт до конца сеанса.
  3. Компонент в OnEnable сопоставляет свой список тегов с активным набором и переключает показ.

Отдельного шага загрузки нет и быть не должно: чтение синхронное, ждать нечего, подписываться не на что. В отличие от систем с внешней проверкой, здесь не существует промежуточного состояния «ещё не знаю».

Режимы сопоставления

TagMatchMode Условие показа
All активны все перечисленные теги
Any активен хотя бы один
None не активен ни один
Single активен ровно один из перечисленных

Пустой список тегов на компоненте — ошибка конфигурации: Error и выключение. Трактовать незаполненную настройку как выполненное условие нельзя — забытая разметка показывала бы контент во всех изданиях.

Что значит «белый список»

Бандл перечисляет включённое, но контент без тегов виден всегда. Пакет управляет только тем, что промаркировано: большая часть игры общая для всех изданий, и обязательная разметка стоила бы дороже цены ошибки. Следствие: забытый тег означает, что объект уедет во все издания. Ревизия перед сборкой — задача окна Content Tags.


Критические требования

  1. Ассет ContentSettings лежит в Resources. Без него активный набор пуст: всё размеченное выключено. Несколько ассетов — берётся первый, остальные игнорируются с Error.
  2. Сломанная конфигурация гасит размеченное, а не открывает его. Пустое или неизвестное активное издание даёт пустой набор. Это сделано намеренно: поломка видна сразу, лишнее не уезжает в сборку издания.
  3. Два управляющих компонента на одном UIStateSwitcher недопустимы. Победит отработавший последним. Проверкой это не закрывается — следите при вёрстке.
  4. Собственный объект не кладётся в цели ContentTagObjectHandler. Выключив себя, компонент не получит OnEnable и обратно не включится. Такая цель пропускается с Error.
  5. Пакет включается define-символом. Без тоггла USING_VORTEX_CONTENT_TAGS в SdkSettings сборка не компилируется.

Контракт

Вход

  • ContentSettings в Resources: объявленные теги, бандлы, ключ активного издания.

Выход

  • ContentBus.IsActive(tag) — включён ли тег;
  • ContentBus.Matches(tags, mode) — результат сопоставления списка;
  • ContentBus.ActiveBundle, ContentBus.ActiveTags — состав текущей конфигурации.

Гарантии

  • API синхронное: первый вызов отвечает немедленно, промежуточных состояний нет.
  • Активный набор строится один раз и за сеанс не меняется.
  • Необъявленный тег считается выключенным.
  • Сломанная конфигурация даёт пустой набор, а не произвольный.

Ограничения

Ограничение Причина
Выключенный контент остаётся в билде Пакет управляет показом; физическое удаление — AssetSwapSystem
Издание не переключается в рантайме Конфигурация сборки, а не настройка игрока
Переопределения издания в редакторе нет Что записано в ассете, то и соберётся; проверка — правкой поля
Немаркированный контент виден всегда Осознанный default-allow
Закрытые сцены сканируются текстом Открывать каждую сцену ради ревизии слишком дорого; носитель в них не определяется
Сканирование сцен требует Force Text При Force Binary окно сообщает о неполном результате

API

ContentBus (static)

Член Описание
IsActive(string tag) Тег включён в текущем издании. Необъявленный — false + Error один раз на ключ
Matches(IReadOnlyList<string> tags, TagMatchMode mode) Сопоставление набора; пустой список — false при любом режиме
ActiveBundle Ключ активного издания; пусто при сломанной конфигурации
ActiveTags Активный набор тегов

ContentSettings (SO, Create → Vortex → Settings → Content Tags)

Поле Описание
tags Объявленные теги: ключ и справочное описание
bundles Издания: ключ и белый список тегов
activeBundle Издание текущей сборки, выпадашка по бандлам

ContentTagGateHandler

Поле Описание
tags Ключи тегов, выпадашка по ассету
mode All / Any / None / Single
switcher UIStateSwitcher с состояниями SwitcherState.Off / On

ContentTagObjectHandler

Поле Описание
tags, mode То же
targets Объекты, которыми управляет компонент; себя класть нельзя
invert Показать цели при невыполненном условии

Граничные случаи

Ситуация Поведение
ContentSettings не найден в Resources Error, активный набор пуст, всё размеченное выключено
Несколько ContentSettings в Resources Error, берётся первый
activeBundle пуст Error, набор пуст
activeBundle указывает на удалённый бандл Error, набор пуст
Тег в бандле не объявлен Error при валидации, в набор не попадает
Дубликат ключа тега или бандла Error с индексом
Запрос по необъявленному тегу false + Error один раз на ключ
Пустой список тегов на компоненте Error, показ выключен
Пустой список целей у ContentTagObjectHandler Error, ничего не переключается
Собственный объект в целях Error, цель пропускается
Повторный вход в Play Mode без Domain Reload Состояние шины сбрасывается и конфигурация читается заново
Asset Serialization не Force Text Окно предупреждает: закрытые сцены не просканированы
Отмена сканирования Прежний индекс сохраняется

Файловая структура

ContentTagsSystem/
├── Bus/             # ContentBus — публичная точка входа
├── Model/           # ContentTag, ContentBundle, TagMatchMode, ContentTagsCatalog
├── Settings/        # ContentSettings
├── Handlers/        # ContentTagGateHandler, ContentTagObjectHandler
├── DefineSettings/  # тоггл пакета в SdkSettings
└── Editor/          # ContentTagsIndex, ContentTagsWindow