Saturday, May 4, 2024
HomeGame Developmentjavascript - Controlling Input Handling in Turn-Based Game

javascript – Controlling Input Handling in Turn-Based Game


I’ve been building a turn-based style RPG in Typescript based upon this ROT-JS Tutorial. For the most part it’s fairly clear and I have a handle on everything, except I’ve begun building out a graphics engine to render the game in 2D sprites rather than terminal style graphics. Inspiration is the game presentation of modern tile-based RPGs like Moonring or Caves of Qud’s graphical release.

The roadblock I’m currently having, though, involves input handling and precise control over it. The tutorial’s method of input handling is to have the engine standup some listeners on initialization and simply update the game any time an event is detected

window.addEventListener('keydown', (event) => {
  this.update(event);
});

Eventually this settles on a structure where the game is broken down into screens (Main Menu, GameScreen, GameOver, etc) that has an input handler that gets invoked by the window listener, and returns an action from that input handler

window.addEventListener('keydown', (event) => {
  engine.screen.update(event);
});

With each game screen being defined like this

class BaseGameScreen {
inputHandler: BaseInputHandler

constructor()

update(event) {
  const action = this.inputHandler.handleKeyPress(event)
}

This works for the ASCII style presentation of most roguelikes, but felt sluggish when holding down a key, since it reverted to the polling rate after the initial press. To address this, I moved the udpate call into the requestAnimationFrame loop that the renderer uses

class Input {
  keyMap: new Map()

  constructor() {
     window.addEventListener("keydown", this.markPressed)
     window.addEventListenred("keyup", this.markReleased)
  }

  markPressed(event) {
    this.keyMap.set(event.key, "pressed")
  }

  markReleased(event) {
    this.keyMap.set(event.key, "released")
  }
}

const input = new Input()

function renderScreen() {
  requestAnimationFrame(renderScreen)

  // could probably consolidate these into one call but eh
  window.engine.screen.update(input)
  window.engine.screen.render()
}

renderScreen()

This greatly improved the input feel of the game, but way in the opposite way. Now even a casual tap of the keyboard causes the player to fly across the tiles, taking several turns at once. I even followed this tutorial for using the command pattern in an input handler to be able to define a list of what keys are pressed, and what actions correspond to those keys

And it works, but the game design in the tutorial is intended for a real-time multiplayer game, and includes reference a gameState object that I just don’t have or need, really. And each screen will have a different set of actions that need to be defined, and this is where I’m getting confused

My goal is to have a system where the input handlers know nothing about what KEY is pressed, but instead respond to which actions are requested, without repeating an action so fast the player can’t reasonably respond

Something like

  ... // inside inputHandler
  handleEvent() {
  if (event.wantsMovement) {
    doMovementAction()
  } else if (event.wantsInventory) {
    doInventoryAction()
  } 
}

But, how do I effectively add this? Should I be doing something like const event = InputManger(this.actionMap) where each screen defines its own actions, or should I just do it before the update call

const input = new InputManager(actionMap)

renderScreen() {
   const event = input.getInputs()
   window.engine.screen.update(event)
}

Anyway, I know that was a lot of questions but I hope I was clear on what I’m looking to do

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments