2D Game Engine

Wejście (InputManager)

Akcje zamiast kodów klawiszy, stan klawiatury, pointer i remapowanie. Jak czytać wejście w komponentach i scenach.

Akcje, nie klawisze#

Logika gry czyta akcje (move-left, jump, interact), nigdy surowe kody klawiszy. Dzięki temu remapowanie sterowania nie dotyka kodu rozgrywki.

ts
update(dt: number) {
  const input = this.getInput()

  if (input.isActionDown('move-left')) this.player.translate(-240 * dt, 0)
  if (input.isActionPressed('jump')) this.jump()
  if (input.isActionReleased('interact')) this.closeDialog()
}
MetodaZnaczenie
isActionDown(action)Akcja trzymana w tej klatce
isActionPressed(action)Akcja wciśnięta w tej klatce (edge)
isActionReleased(action)Akcja puszczona w tej klatce (edge)

To samo dla surowych kodów: isKeyDown, isKeyPressed, isKeyReleased.

API#

ts
attach(port); detach(); isAttached(); update()
isKeyDown(code); isKeyPressed(code); isKeyReleased(code)
isActionDown(action); isActionPressed(action); isActionReleased(action)
getPointer(): PointerState
getPointerPosition(); getPointerWorldPosition()
setActionMap(map); getActionMap(); remapAction(action, codes)

Domyślna mapa akcji#

defaultInputMap: move-up, move-down, move-left, move-right, interact, jump, restart, menu, pause, time-slow, time-fast, time-normal, mute.

Remapowanie#

ts
const input = this.getInput()

input.remapAction('jump', ['Space', 'KeyW', 'ArrowUp'])

input.setActionMap({
  'move-left': ['KeyA'],
  'move-right': ['KeyD'],
  jump: ['Space'],
  interact: ['KeyE'],
  pause: ['Escape']
})

Przykład: ekran ustawień sterowania#

ts
class ControlsScene extends Scene {
  private listeningFor: string | null = null

  startRebind(action: string) {
    this.listeningFor = action
  }

  update() {
    if (!this.listeningFor) return
    const input = this.getInput()
    for (const code of ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'Space', 'KeyE']) {
      if (input.isKeyPressed(code)) {
        input.remapAction(this.listeningFor, [code])
        this.listeningFor = null
        break
      }
    }
  }
}

Pointer#

ts
const pointer = this.getInput().getPointer()

const screen = this.getInput().getPointerPosition()
const world = this.getInput().getPointerWorldPosition()

if (pointer.isDown) {
  this.spawnAt(world.x, world.y)
}

getPointerWorldPosition() uwzględnia pozycję i zoom kamery — kliknięcie trafia w świat gry, nie w ekran.

Informacja

Adapter blokuje domyślne zachowanie przeglądarki (scrollowanie spacją, strzałkami) tylko w obszarze gry. Poza canvasem strona działa normalnie.

Przykład: komponent sterowania#

ts
export class PlayerController extends Component {
  phase = 'input' as const
  private speed = 260

  onUpdate(dt: number) {
    const input = this.owner.getInput()
    const vx = (input.isActionDown('move-right') ? 1 : 0) - (input.isActionDown('move-left') ? 1 : 0)
    const vy = (input.isActionDown('move-down') ? 1 : 0) - (input.isActionDown('move-up') ? 1 : 0)
    const len = Math.hypot(vx, vy) || 1
    this.owner.translate((vx / len) * this.speed * dt, (vy / len) * this.speed * dt)
  }
}

Normalizacja wektora sprawia, że ruch po skosie nie jest szybszy niż w poziomie.