Слой пользовательского ввода (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). Камера была статичной. Управление начало бы расползаться по компонентам.
Документ описывает три вещи:
- Слой ввода — единая точка, за которой скрыт Input System; контексты по фазе игры.
- Камеру — три режима (Action / Overview / Dev), переключение, кламп из данных арены.
- Разграничение игрового ввода и 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 | ничего (загрузка/переходы) |
Menu | UI (заглушка под будущее меню) |
Deployment | Camera (расстановка — будущая фаза) |
Combat | Camera + 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|false→CameraModeController.SetDevAccess.
Разграничение доступа к dev-инструментам вообще — отдельная сквозная тема; здесь заложен только шов в слое камеры.
Рестарт — это dev, а не игрок:
- F5 — релоад сцены (пустая арена). Был раньше, оставлен.
- R — перезапуск последнего боя: релоад сцены + повтор последнего
gm_spawn_*сетапа (запомнен вstatic-поле, переживает релоад). Также командаgm_restart_battle.
И F5, и R читают Keyboard.current напрямую в GuildmasterCommands — это dev-инструменты, они сознательно НЕ в игровом контракте IInputService.
7. Как собрать риг камеры в сцене (чек-лист)
Скрипты и DI готовы; DI поднимает камеру автоматически, как только CameraModeController есть в сцене (иначе бой не падает — регистрация условная). Сборка рига (BattleScene):
- На Main Camera добавить
CinemachineBrain. Камера — ортографическая (2D). - Создать три объекта с
CinemachineCamera:CM Action,CM Overview,CM Dev. Всем — ортографический lens. CM Action: добавить тело слежения (CinemachineFollowили Position Composer), поле Follow → объектCombatFocusTarget(см. п.4).- Создать объект
CombatFocusTargetс одноимённым компонентом (стартовая позиция — центр арены). - Создать объект
CameraControllerсCameraModeController; в инспекторе прокинуть ссылки на триCinemachineCamera. Параметры (зона/зум/демпинг/пан) — крутить на вкус. - Проверить: 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 даёт «тот же расклад, новый сид».