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) |
выпадашки и окно ревизии |
Использование
Настройка проекта
- Включите тоггл пакета в
Tools → Vortex → Configs → SDK Settings. Create → Vortex → Settings → Content Tags, положите ассет вResources. Быстрый доступ к нему потом —Tools → Vortex → Content Tags → Settings.- Объявите теги: ключ плюс описание, зачем тег заведён.
- Заведите бандлы изданий и отметьте в каждом включённые теги.
- Выберите
activeBundle.
Разметка контента
// Любая система может спросить напрямую — не только UI.
if (ContentBus.IsActive("store_links"))
BuildStoreSection();
На сцене: ContentTagGateHandler там, где состояние настраивается свитчером; ContentTagObjectHandler там, где раздела просто не должно быть.
Перед сборкой издания
- Поставьте нужный
activeBundleв ассете. - Откройте
Tools → Vortex → Content Tags → Index, нажмите Обновить. - Проверьте состав издания и разделы «объявлены, но не используются» и «используются, но не объявлены» — второй означает опечатку в компоненте или удалённое объявление.
- 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, только чтение
Порядок работы
- При первом обращении к шине читается ассет из
Resources. - Теги активного бандла становятся активным набором; набор живёт до конца сеанса.
- Компонент в
OnEnableсопоставляет свой список тегов с активным набором и переключает показ.
Отдельного шага загрузки нет и быть не должно: чтение синхронное, ждать нечего, подписываться не на что. В отличие от систем с внешней проверкой, здесь не существует промежуточного состояния «ещё не знаю».
Режимы сопоставления
TagMatchMode |
Условие показа |
|---|---|
All |
активны все перечисленные теги |
Any |
активен хотя бы один |
None |
не активен ни один |
Single |
активен ровно один из перечисленных |
Пустой список тегов на компоненте — ошибка конфигурации: Error и выключение. Трактовать незаполненную настройку как выполненное условие нельзя — забытая разметка показывала бы контент во всех изданиях.
Что значит «белый список»
Бандл перечисляет включённое, но контент без тегов виден всегда. Пакет управляет только тем, что промаркировано: большая часть игры общая для всех изданий, и обязательная разметка стоила бы дороже цены ошибки. Следствие: забытый тег означает, что объект уедет во все издания. Ревизия перед сборкой — задача окна Content Tags.
Критические требования
- Ассет
ContentSettingsлежит вResources. Без него активный набор пуст: всё размеченное выключено. Несколько ассетов — берётся первый, остальные игнорируются с Error. - Сломанная конфигурация гасит размеченное, а не открывает его. Пустое или неизвестное активное издание даёт пустой набор. Это сделано намеренно: поломка видна сразу, лишнее не уезжает в сборку издания.
- Два управляющих компонента на одном
UIStateSwitcherнедопустимы. Победит отработавший последним. Проверкой это не закрывается — следите при вёрстке. - Собственный объект не кладётся в цели
ContentTagObjectHandler. Выключив себя, компонент не получитOnEnableи обратно не включится. Такая цель пропускается с Error. - Пакет включается 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