Слой пользовательского ввода (Input System) и боевая камера (Cinemachine): единая точка ввода за интерфейсом, контексты по фазе игры, три режима камеры с data-клампом, dev-камера в билде за флагом доступа.

Связано: 10. Арена и расстановка (дизайн), 02. Архитектура реализации — Фаза 1 (Боевое ядро), 05. Презентация и интерполяция, 01. Dependency Injection и события, 11. Управление (раскладка клавиш).

Статус: дизайн согласован и реализован в коде (2026-07-10, ветка feat/input-system-and-camera, EditMode 187/187). Слой ввода, пауза (Space), dev-хоткеи (R/F5) — готовы и тестируемы. Скрипты камеры и DI-проводка готовы; сборка рига камеры в сцене (Cinemachine Brain + 3 виртуальные камеры + focus-target) и визуальная настройка (демпинг/зум/зона) — за Максом (см. §6, §7). Отклонения от исходного плана помечены ниже (§3).


1. Зачем этот документ

До этого среза централизованного ввода в проекте не было: единственным «нашим» вводом была dev-консоль (GuildmasterCommands, хоткей F5), остальное — сторонние ассеты (Feel/MMTools, QFSW). Камера была статичной. Управление начало бы расползаться по компонентам.

Документ описывает три вещи:

  1. Слой ввода — единая точка, за которой скрыт Input System; контексты по фазе игры.
  2. Камеру — три режима (Action / Overview / Dev), переключение, кламп из данных арены.
  3. Разграничение игрового ввода и dev-инструментов.

2. Слой ввода: единая точка за интерфейсом

Весь геймплейный ввод сходится в IInputService (Guildmaster.Core.Input). Потребители (камера, боевой слой) зависят только от интерфейса и про Input System не знают.

interface IInputService
{
    InputContext Context { get; }
    void SetContext(InputContext context);      // None / Menu / Deployment / Combat
 
    Vector2 CameraPan { get; }                   // непрерывно: WASD + стрелки, [-1..1]
    float   CameraZoomDelta { get; }             // непрерывно: колесо мыши
 
    event Action CycleViewRequested;             // Tab
    event Action PauseToggleRequested;           // Space
}

Соглашение о раздаче: непрерывный ввод (пан/зум) читается значением каждый кадр; дискретный (нажатия) приходит C#-событиями. Через MessagePipe ввод не гоняем — камера это презентация, шина тут избыточна.

Контексты (InputContext) задают, какие карты действий активны:

КонтекстАктивные карты
Noneничего (загрузка/переходы)
MenuUI (заглушка под будущее меню)
DeploymentCamera (расстановка — будущая фаза)
CombatCamera + Combat

Пауза — это состояние симуляции, а не отдельный контекст: действие PauseToggle работает и в идущем, и в приостановленном бою.

Жизненный цикл. InputService (Guildmaster.Game.Input) регистрируется в RootLifetimeScope как singleton — ввод глобален и переживает перезагрузку боевой сцены. Карты действий строятся в конструкторе, включаются/гасятся по SetContext. BattleInputController (боевой скоуп, IStartable/IDisposable) на старте боя ставит контекст Combat и подписывает Space→пауза, на уничтожении — отписывается и гасит контекст в None.


3. Отклонение от плана: действия в коде, не в .inputactions

Решено: карты действий строятся в коде внутри InputService, а не хэндовым ассетом .inputactions с генерацией C#-обёртки.

Почему: хрупкость ручного .meta импортёра и нестабильный мост Unity в момент реализации делали код-дефайн надёжнее — он компилируется детерминированно и тестируется в EditMode. Контракт IInputService от этого не зависит.

Цена / когда пересмотреть: нет GUI-редактирования биндов и «из коробки» UI-ребиндинга. И то, и другое — чистая будущая миграция поверх той же таблицы биндов: заводим .inputactions-ассет, генерим обёртку, InputService начинает оборачивать её вместо ручных карт; потребители не замечают.


4. Клавиши (игрок)

Полная раскладка для игрока — в геймдиз-доке 11. Управление (раскладка клавиш). Коротко:

  • Пан: WASD + стрелки.
  • Зум: колесо мыши.
  • Переключение вида (cycle): Tab — Action → Overview → [Dev, если выдан доступ].
  • Пауза/продолжить бой: Space.

Действие «центрировать камеру» из плана выкинуто — Action-камера и так держит фокус.


5. Камера: три режима на Cinemachine 3

CinemachineBrain на Main Camera + три CinemachineCamera (Cinemachine 3.1.x, CinemachineCamera API). Переключение — сменой приоритета; Brain плавно блендит. Всем рулит CameraModeController (Guildmaster.Presentation), потребляя IInputService.

РежимЧто делаетПан/зумКламп
ActionДинамично держит драку: следует за CombatFocusTarget (сглаженный центроид живых юнитов), орто-зум подгоняется под разброс боя.автода
OverviewОбзор всей арены; при включении встаёт в центр на весь размер зоны.ручнойда
DevСвободный полёт, широкий зум.ручнойнет

CombatFocusTarget — MonoBehaviour в сцене; каждый кадр ставит свой transform в сглаженный центроид живых юнитов и считает Spread (макс. расстояние юнита от центра) для динамического зума. Только читает симуляцию. Action-камера следует за ним (поле Follow).

Кламп из данных, не из коллайдера. По принципу арены (геометрия = данные, 10. Арена и расстановка (дизайн)) кламп НЕ через CinemachineConfiner2D. Вместо этого — из ArenaLayoutData.Bounds: ограничивается не центр камеры, а видимая область — её край не может зайти за зону (зона = Bounds + авторимый _boundsPadding). Отсюда авто-выводится макс-зум (вьюпорт не больше зоны). Мин-зум — авторимый параметр. Всё это презентация, симуляции не касается. Dev-камера клампу не подчиняется.


6. Dev-камера в билде и разграничение доступа

Dev-режимы живут в билде всегда (не за #if UNITY_EDITOR) — чтобы щупать в реальной сборке для QA. Доступ — за флагом DevAccess в CameraModeController:

  • Обычный игрок циклит только Action ↔ Overview.
  • Dev входит в цикл, только когда доступ выдан.
  • Выдать/забрать: команда QFSW gm_cam_dev true|falseCameraModeController.SetDevAccess.

Разграничение доступа к dev-инструментам вообще — отдельная сквозная тема; здесь заложен только шов в слое камеры.

Рестарт — это dev, а не игрок:

  • F5 — релоад сцены (пустая арена). Был раньше, оставлен.
  • R — перезапуск последнего боя: релоад сцены + повтор последнего gm_spawn_* сетапа (запомнен в static-поле, переживает релоад). Также команда gm_restart_battle.

И F5, и R читают Keyboard.current напрямую в GuildmasterCommands — это dev-инструменты, они сознательно НЕ в игровом контракте IInputService.


7. Как собрать риг камеры в сцене (чек-лист)

Скрипты и DI готовы; DI поднимает камеру автоматически, как только CameraModeController есть в сцене (иначе бой не падает — регистрация условная). Сборка рига (BattleScene):

  1. На Main Camera добавить CinemachineBrain. Камера — ортографическая (2D).
  2. Создать три объекта с CinemachineCamera: CM Action, CM Overview, CM Dev. Всем — ортографический lens.
  3. CM Action: добавить тело слежения (CinemachineFollow или Position Composer), поле Follow → объект CombatFocusTarget (см. п.4).
  4. Создать объект CombatFocusTarget с одноимённым компонентом (стартовая позиция — центр арены).
  5. Создать объект CameraController с CameraModeController; в инспекторе прокинуть ссылки на три CinemachineCamera. Параметры (зона/зум/демпинг/пан) — крутить на вкус.
  6. Проверить: Tab циклит Action↔Overview; gm_cam_dev true добавляет Dev; в Action камера следует за дракой; в Overview видно всю арену и пан/зум упирается в зону.

Визуальная приёмка (ощущение слежения, демпинг центроида, границы зоны, скорость зума) — за Максом.


8. Открытые хвосты

  • UI-ребиндинг / .inputactions-ассет — будущая миграция (§3).
  • Единый dev-access — сквозной механизм выдачи доступа к dev-инструментам (сейчас только gm_cam_dev для камеры).
  • F5/R и single-mode релоадLoadScene(active.name) перезагружает всё; в мультисценовой компоновке (CoreScene + аддитивная BattleScene) поведение F5/R пересмотреть.
  • Точный повтор боя с тем же сидом (Shift+R и т.п.) — сейчас R даёт «тот же расклад, новый сид».