# Ввод: действия, клавиатура, мышь/тач, геймпад

## Действия

InputManager работает с **действиями** (семантическими именами), а не с кодами клавиш.
Игра один раз задаёт маппинг — дальше везде только `isActionActive` / `isActionJustPressed`:

```ts
engine.input.bindActions({
    advance: ['Space', 'Enter'],
    menu: ['Escape'],
    up: ['KeyW', 'ArrowUp'],
    down: ['KeyS', 'ArrowDown'],
    left: ['KeyA', 'ArrowLeft'],
    right: ['KeyD', 'ArrowRight']
});

engine.input.isActionJustPressed('advance'); // нажато в этом тике
engine.input.isActionActive('up');           // зажато
engine.input.isActionJustReleased('attack'); // отпущено в этом тике (клавиши+геймпад)
```

Одна клавиша может маппиться на **несколько действий** (например Space = `advance` в
диалоге и `attack` вне его, геймпадная A = `advance` и `interact`) — повторные
`bindActions` не перезаписывают, а дополняют. Конфликты разводятся уровнем сцены:
в диалоге действия боя/действия не опрашиваются.

Отпускание и удержание нужны для заряжаемых действий (замах, резонанс). Для точных
кодов есть `wasKeyPressed(code)` / `wasKeyReleased(code)`; у указателя —
`pointer.downTicks` (сколько тиков удержана кнопка, 0 — только нажата).

## Геймпад

Стандартная карта кнопок (0=A, 1=B, 8=Select, 9=Start, 12–15 — крестовина).
Опрашивается автоматически в начале каждого тика (`input.update()` в Engine.tick):

```ts
engine.input.bindGamepad({
    advance: [0],   // A
    menu: [9],      // Start
    up: [12], down: [13], left: [14], right: [15]
});

engine.input.getLeftStick(); // { x, y } в -1..1 (мёртвая зона 0.15) или null
```

Один и тот же код работает и с клавиатурой, и с геймпадом — действие единое.

## Указатель (мышь/тач)

Координаты указателя пересчитываются в **виртуальные** пиксели:

```ts
const p = engine.input.getPointer();
p.x, p.y;          // виртуальные пиксели
p.justPressed;     // нажато в этом тике
p.justReleased;    // отпущено в этом тике
p.down;            // зажато
p.isTouch;         // это тач (для мобильного управления)
```

## Виртуальный джойстик (тач)

Для мобильных: джойстик появляется там, где игрок коснулся экрана. Добавляется
в uiRoot сцены; опрашивается вектор:

```ts
import { VirtualJoystick } from '@rpg/engine';

const joystick = new VirtualJoystick({
    screen: { width: 480, height: 270 },
    radius: 28
});
engine.renderer.uiRoot.addChild(joystick);

// в update сцены:
const v = joystick.getVector(); // { x, y } в -1..1; {0,0} если не активен
player.moveBy(v.x * speed * dt, v.y * speed * dt);
```

Джойстик сам обрабатывает pointer-события (`eventMode: 'static'`, hitArea на весь экран).
В демо движение — только кнопками/стиком (мышь в мир не routed): экранный вектор
сцена переводит в мировое направление через `screenToWorld` и ведёт героя
`moveFree` (скольжение по стенам, расталкивание тел, поворот по экранной оси).

## Жизненный цикл тика

`justPressed`-флаги очищаются в `input.endTick()` — Engine вызывает его в конце
каждого тика. Не вызывайте `endTick` вручную из сцены, иначе пропустите нажатия.

## Раскладки

Используйте `e.code`-имена (`KeyW`, а не `KeyЫ`) — раскладка не важна. Для
перенастраиваемых клавиш храните маппинг в Settings (`extra`) и вызывайте
`bindActions` заново при изменении.