InputBusSystem
Namespace: Vortex.Unity.InputBusSystem
Сборка: ru.vortex.unity.inputbussystem
Назначение
Управление вводом на основе Unity Input System с поддержкой карт ввода (Input Action Maps), подписки на экшены по принципу LIFO и прямой привязки клавиш.
Возможности:
- Централизованная маршрутизация сигналов ввода через статический контроллер
- Управление картами ввода с автоматической активацией/деактивацией по наличию подписчиков
- LIFO-маршрутизация — сигнал получает только последний зарегистрированный подписчик
- Гарантия парности
performed/canceled— отмена доставляется только получателю нажатия - Привязка произвольных клавиш и комбинаций к UI-событиям без прямого взаимодействия с Input System API
Вне ответственности:
- Ребиндинг клавиш в рантайме
- Визуальный редактор карт ввода (используется стандартный Unity Input Actions Asset)
- Аналоговый ввод в маршрутизации — шина роутит событиями только дискретные действия (Button); аналоговые значения читаются polling'ом через
GetAction(см. Использование)
Зависимости
| Зависимость | Назначение |
|---|---|
| Unity Input System | InputSystem.actions, InputAction, InputActionMap |
Vortex.Core.AppSystem |
App.GetState(), App.OnStateChanged, App.OnExit |
Vortex.Core.SettingsSystem |
Settings.Data().DebugMode — диагностическое логирование |
Vortex.Core.Extensions |
AddNew, AddOnce — хелперы коллекций |
Vortex.Unity.EditorTools |
[ValueSelector], [ClassLabel] |
Vortex.Unity.UI.Misc |
AdvancedButton — опциональная интеграция |
Использование
Подписка на экшен из кода
InputController.AddActionUser("Player/Jump", this, OnJumpPerformed, OnJumpCanceled);
// Отписка
InputController.RemoveActionUser("Player/Jump", this);
this используется как ключ идентификации. Повторный AddActionUser с тем же объектом не создаст дубликат.
Polling-чтение значения (GetAction)
Для аналогового ввода (стики, триггеры, курсорная дельта), где нужна не callback-подписка, а само значение по требованию, GetAction("Карта/Экшен") отдаёт InputAction напрямую — дальше ReadValue<T>() / IsPressed() в своём Update:
var move = InputController.GetAction("Player/Move");
var delta = move?.ReadValue<Vector2>() ?? Vector2.zero;
null — ключ не найден. Это escape-hatch мимо LIFO-маршрутизации: значение читает сам вызывающий, шина в polling не участвует (в эдиторе метод при необходимости сам поднимает Init).
Подписка через Inspector (InputActionHandler)
Компонент InputActionHandler на GameObject:
inputAction— экшен из dropdown (составной idКарта/Экшен, сгруппирован по картам в папки)button— опционально, ссылка наAdvancedButtononPressed/onReleased— Unity Events
Позиция указателя (InputPointerHandler)
Компонент InputPointerHandler подписывается на экшен по правилам шины (LIFO) и на каждое его событие отдаёт наружу текущую позицию указателя одним UnityEvent<Vector2> (onPoint, screen-координаты через Pointer.current). В отличие от InputActionHandler (два события press/release) — один метод со значением вектора; что делать с точкой и когда начинать/заканчивать ловлю решает подписчик (например, включением/выключением объекта по press-экшену).
inputAction— экшен, событие которого = момент отдать позицию (напр.CursorPosition)onPoint—UnityEvent<Vector2>, вызывается на каждое событие экшена с текущей позицией
Подписка отложена до AppStates.Running (как в InputActionHandler), автоотписка в OnDisable/OnDestroy.
Активация карты (InputMapHandler)
Компонент InputMapHandler на GameObject:
inputMap— карта из dropdown
Карта активна, пока хотя бы один InputMapHandler для неё включён в сцене.
Прямая привязка клавиш (KeyboardHandler)
Компонент KeyboardHandler на GameObject:
buttonCode— массив отдельных клавиш (любая вызовет срабатывание)buttonsCombinations— массив комбинаций (последняя клавиша — активирующая, остальные — модификаторы)button,onPressed,onReleased— аналогичноInputActionHandler
Архитектура
InputBusSystem/
├── InputController.cs # Статический контроллер: индексация, маршрутизация
├── InputSubscriber.cs # Модель подписчика с equality по Owner
├── Handlers/
│ ├── InputActionHandler.cs # Подписка на экшен через InputController
│ ├── InputPointerHandler.cs # На каждое событие экшена отдаёт позицию указателя (screen)
│ ├── InputMapHandler.cs # Активация карты ввода
│ └── KeyboardHandler.cs # Прямая привязка клавиш/комбинаций
└── Debug/
├── Model/SettingsModelExtInput.cs # partial SettingsModel + InputDebugMode
└── Presets/DebugSettingsExtInput.cs # partial DebugSettings + toggle
Поток данных
Unity Input System
↓ performed / canceled
InputController (маршрутизация по LIFO)
↓ callback
InputActionHandler / подписчик
↓ UnityEvent
UI / игровая логика
KeyboardHandler работает автономно — создаёт собственный InputAction в обход InputController.
Ключевые концепции
Составной id экшена (Карта/Экшен)
Экшен индексируется по составному id Карта/Экшен (через /), а не по голому имени. Это:
- снимает конфликт одноимённых экшенов в разных картах (id различаются,
Initне падает на дубликате); - раскладывает дропдаун по папкам-картам —
ValueSelectorчерезSearchablePopupгруппирует значения по/.
Философия уникальности имён сохраняется: уникальна теперь связка «карта/экшен», а не голое имя экшена.
LIFO-маршрутизация
При нескольких подписчиках на один экшен performed доставляется только последнему (subscribers[^1]). Позволяет перехватывать ввод в модальных окнах и оверлеях без отписки предыдущих слоёв.
Парность performed/canceled
Объект, получивший performed, запоминается в CatchPerformed. При canceled сигнал доставляется только ему. Если верхний подписчик сменился — canceled отбрасывается.
Карты с подсчётом пользователей
Карта активируется при первом AddMapUser и деактивируется при уходе последнего пользователя (RemoveMapUser). Предотвращает конфликт между картами без ручного управления.
Комбинации клавиш
KeyboardHandler поддерживает до 3 модификаторов через композитные биндинги Unity (OneModifier, TwoModifiers, ThreeModifiers). Последняя клавиша в массиве — активирующая, остальные — модификаторы.
Отложенная подписка
Хэндлеры проверяют App.GetState() < AppStates.Running и откладывают регистрацию до готовности приложения через App.OnStateChanged.
Контракт
Вход
- Unity Input Actions Asset с определёнными картами и экшенами
AppStates.Running— состояние, при котором хэндлеры регистрируются
Выход
UnityEvent onPressed/onReleased— вInputActionHandlerиKeyboardHandlerAdvancedButton.Press()/Release()— опциональная интеграция- Активация/деактивация
InputActionMap— вInputMapHandler
Гарантии
- Один
performed→ максимум одинcanceledдля того же подписчика - Карта деактивируется только при нулевом количестве пользователей
- Подписчик не дублируется в списке (
AddOnceс equality поOwner) - Хэндлеры отписываются в
OnDisable/OnDestroy
Ограничения
- Маршрутизация — только дискретный ввод (Button); аналоговые значения событиями не передаются, но доступны polling'ом через
GetAction InputSubscriber.Equalsсравнивает поOwner— один объект не может иметь разные колбэки на один экшенKeyboardHandlerподдерживает максимум 3 модификатора; 4+ игнорируются с предупреждением- Нет механизма приоритетов — только порядок добавления (LIFO)
API
InputController (static)
// Карты ввода
static string[] GetMaps()
static void AddMapUser(string mapId, object inputMapUser)
static void RemoveMapUser(string mapId, object inputMapUser)
// Экшены ввода
static string[] GetActions()
static void AddActionUser(string actionInputId, object actionInputUser,
Action onPerformedCallback, Action onCanceledCallback)
static void RemoveActionUser(string actionInputId, object actionInputUser)
// Прямой доступ к InputAction для polling-чтения значения (ReadValue<T>()/IsPressed())
static InputAction GetAction(string actionInputId) // null — ключ не найден
InputSubscriber
// Equality определяется по Owner
public readonly object Owner;
public Action OnPerformed { get; }
public Action OnCanceled { get; }
Критические требования
- Input Actions Asset должен быть назначен —
InputController.Init()читаетInputSystem.actions. При отсутствии словари пусты, хэндлеры выбросятKeyNotFoundException. - Уникальны имя карты и связка
Карта/Экшен. Экшены индексируются составным id, поэтому одноимённые экшены в РАЗНЫХ картах допустимы (id различаются). Дубликат имени карты или полностью совпадающегоКарта/ЭкшенвызоветArgumentExceptionпри инициализации (в норме Unity такого не создаёт). KeyboardHandlerне проходит черезInputController— его экшены не участвуют в LIFO-маршрутизации.KeyboardHandlerтребует физической клавиатуры — приKeyboard.current == nullлогирует предупреждение.
Debug
Диагностическое логирование InputController управляется через Settings.Data().InputDebugMode; KeyboardHandler логирует под общим Settings.Data().DebugMode. Настраивается в ассете DebugSettings (toggle InputDebugMode).
При включении логируются: регистрация/отписка подписчиков, доставка performed/canceled, активация/деактивация карт.
Граничные случаи
| Ситуация | Поведение |
|---|---|
| Несуществующее имя экшена/карты | KeyNotFoundException (кроме RemoveActionUser) |
RemoveActionUser для незарегистрированного объекта или несуществующего экшена |
Тихий пропуск — в т.ч. отписка на App.OnExit после очистки шины |
Подписчик performed удалён до canceled |
canceled отбрасывается |
Нет подписчиков при performed |
Тихий пропуск |
Keyboard.current == null |
Предупреждение; нажатия игнорируются |
| Комбинация с 4+ модификаторами | Предупреждение; комбинация пропущена |
OnEnable до AppStates.Running |
Подписка отложена до смены состояния |
| Input Actions Asset не назначен | Словари пусты; KeyNotFoundException при регистрации |