Перейти к содержанию

Сборка экрана из готовых компонентов игры

Экран описывается в YAML — компоненты и данные, без координат. Вёрстку раскладывает flexbox, а хром (панели, кнопки, рамки, бары, ячейки навигации) берётся из библиотеки спрайтов игры по таблице ролей role-map.yaml. Генерируется только то, чего в игре нет: задник и иллюстрации.

Сборка — два прохода

Растяжимые панели надо запечь в плоский PNG под тот размер, который они в итоге займут, а размер известен только после раскладки: карточки — flex:1 1 0, их высота это остаток после шапки и навигации. Поэтому сначала рендер, потом замер, потом сборка заново.

BUILD="uv run --project art-generator python -m art_generator.screen_build \
    --spec game_design_features/guild/screen-s3a-real.yaml \
    --kit  game_design_features/_ui-kit \
    --kit  game_design_features/guild/art-kitmode \
    --role-map    game_design_features/_ui-kit/role-map.yaml \
    --asset-index game_design_features/_ui-kit/asset-index.json \
    --out    game_design_features/guild/screen-s3a-real.html \
    --report game_design_features/guild/coverage-s3a.md"

$BUILD                                              # 1. первый рендер
uv run --project art-generator python art-generator/scripts/measure_boxes.py \
    game_design_features/guild/screen-s3a-real.html --out /tmp/boxes.json
$BUILD --boxes /tmp/boxes.json                      # 2. пересборка с запечёнными плитами

Второй проход обязателен, если файл пойдёт в Figma. Без --boxes плиты остаются на CSS border-image. В браузере это верно, а html.to.design border-image не умеет: правило вырождается в border-style:solid дефолтного цвета, и панель приезжает чёрным прямоугольником в толщину своего борта. Так уже случилось — пять карточек и шапка. Проверка:

grep -c border-image game_design_features/guild/screen-s3a-real.html   # должно быть 0

В конце печатается строка вида 28 parts: 20 from the game's library, 8 generated. Отчёт --report расписывает это по деталям: какая роль, какой спрайт ответил и как он был выбран — решением таблицы, точным именем из спека или поиском. Поиск — единственный из трёх, который догадка.

Без --role-map и --asset-index сборка работает по-старому: весь хром берётся из сгенерированного кита. Так можно проверить, что изменилось от привязки к библиотеке.

Дальше в Unity — обязательно с картой спрайтов

$BUILD --boxes /tmp/boxes.json \
       --sprite-map-out game_design_features/guild/sprite-map-s3a.json

# после импорта в Figma:
overlord/tools/figma_unity_pipeline/import_figma.sh \
    --screen GuildS3a --file-key <KEY> --root-id <ID> \
    --library-map game_design_features/guild/sprite-map-s3a.json

Без --library-map экспорт выгрузит копию каждой взятой из игры детали в Assets/Sprites/UI/<Screen>/ — двадцать дубликатов на экран, каждый с новым GUID. Это ровно то дублирование, ради устранения которого весь сборщик и писался, только на шаг позже.

Сверка по хешу здесь не спасает: она сравнивает внутри одной папки, а библиотека лежит в Assets/InternalAssets/Sprites/UI. Расширить сверку тоже не выход — Figma рендерит узел в его размере по вёрстке, и кнопка 290x109, выгруженная как 178x67, даёт другой хеш, чем исходный спрайт. Знание о том, какой файл ответил, есть только у сборщика — он его и записывает.

Проверки

# пропорции: метки одной роли обязаны рендериться одного размера
uv run --project art-generator python art-generator/scripts/audit_proportions.py \
    game_design_features/guild/screen-s3a-real.html \
    --kit game_design_features/_ui-kit --kit game_design_features/guild/art-kitmode \
    --asset-index game_design_features/_ui-kit/asset-index.json

# анти-паттерны вёрстки (59 правил)
node .claude/skills/impeccable/scripts/detect.mjs \
    game_design_features/guild/screen-s3a-real.html

Аудит возвращает код 1, если внутри одной роли разброс ≥8%. Детектор молчит, когда всё чисто; если сомневаетесь, что он вообще работает, прогоните его на файле с заведомой ошибкой — на 9px тексте и цветном glow он даёт четыре находки.

Переиндексировать библиотеку

Нужно после того, как художник добавил или переименовал спрайты:

uv run --project art-generator python art-generator/scripts/index_ui_assets.py \
    --root overlord/frontend/unity/Assets/InternalAssets/Sprites/UI \
    --out  game_design_features/_ui-kit/asset-index.json

Индекс тянет из Unity-.meta границы 9-slice, поэтому растяжимые панели растягиваются так, как их задумал художник, а не background-size: 100% 100%.

Что править, если деталь выглядит не так

Почти всегда — role-map.yaml, а не код. Роль → спрайт решается там, один раз на всю игру. Ищите кандидатов по метаданным индекса, а не по именам папок: заявка «в игре нет тянущейся по всем сторонам панели» была написана после поиска только в Shared/ — панель нашлась в LoadingScreen/. И не верьте именам: NavBar/icon_selected — это красный крестик закрытия.