Question

I'm working on a 2D game in Node where the character needs to move diagonally. This is a top-down, text-based game purely in a Node environment (no browser, so I don't have nice keydown/keyup events at my disposal).

I'm using the keypress library to read user input but I don't know how to capture two keys at once to cause diagonal movement (e.g. down arrow and right arrow). Here's the code I currently have for horizontal and vertical movement:

game = new Game()
game.print()

keypress(process.stdin)
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.on('keypress', (ch, key) ->
  return unless key
  return process.stdin.pause() if key.ctrl and key.name is 'c'

  player = game.player
  switch key.name
    when 'up' 
      if key.shift then player.turnUp() else player.moveUp()
    when 'right' 
      if key.shift then player.turnRight() else player.moveRight()
    when 'down' 
      if key.shift then player.turnDown() else player.moveDown()
    when 'left' 
      if key.shift then player.turnLeft() else player.moveLeft()
    when 'enter' 
      player.repeatMove()
    when 'b' 
      player.placeBlock()

  game.update()
  game.print()
)

This is a turn-based game and currently the run loop of the game is advanced on user input. Instead, I think what I need to do is have an interval-based game update and keep track of the two most recent keypress events and the time between them.

Is there a more robust way?

Was it helpful?

Solution 2

I ended up building a keyboard module that reports when simultaneous keys are pressed. It's built on top of the keypress library.

Every time a key is pressed the keyboard module checks what keys have been pressed in the past 100ms and reports those as a single key event. It is throttled to emit events at most once every 40ms to give it time to collect key combinations. The downside is that single-key inputs now have a 40ms delay. This is a consequence of how input works in the terminal.

EventEmitter = require('events').EventEmitter
keypress = require('keypress')
_ = require('underscore')

# This module allows for reading simultaneous key events in the terminal.
module.exports = class Keyboard extends EventEmitter
    _keyTimes: {}

    constructor: ->
        keypress(process.stdin)
        process.stdin.setRawMode(true)
        process.stdin.resume()

        @_emitKeyStatus = _.throttle(@_emitKeyStatus, 40, leading: false)
        process.stdin.on('keypress', (ch, key) => @_processKey(key))

    # Batch-emits any keys recently pressed.
    _emitKeyStatus: ->
        currentTime = @_tuple2time(process.hrtime())
        millisecond = 1000000
        keys = {}

        for own keyName, time of @_keyTimes
            if currentTime - time < 100 * millisecond
                keys[keyName] = true

        @emit('keypress', keys)

    _tuple2time: (tuple) ->
        (tuple[0] * 1e9) + tuple[1]

    _processKey: (key) ->
        return unless key
        return @emit('quit') if key.ctrl and key.name is 'c'

        time = @_tuple2time(process.hrtime())

        # Treat ctrl, shift and meta as distinct keys.
        @_keyTimes.shift = time if key.shift 
        @_keyTimes.ctrl = time if key.ctrl
        @_keyTimes.meta = time if key.meta

        @_keyTimes[key.name] = time
        @_emitKeyStatus()

OTHER TIPS

Here is my personnal implementation of keyboard manager :

It's quite easy to understand. I have an object which descripts my keyboard status. On any change I send an event.

tools.KeyboardController.keyPressed = function(e) {
    var evtobj = window.event? event : e;
    var key = evtobj.keyCode;

    //don't need to change anything if it's already pressed
    if(tools.KeyboardController.keyStatus[key] === true) return;
    //we store the key in an object which describe the keyboard status
    tools.KeyboardController.keyStatus[key] = true;
    //send an event to signal the touch is pressed 
    EventManager.fire("tools.KeyboardController.keyPressed."+key);
}

tools.KeyboardController.keyReleased = function(e) {
    var evtobj = window.event? event : e;
    var key = evtobj.keyCode;
    //if key is not already realese, noting to do
    if(tools.KeyboardController.keyStatus[key] === false) return;
    //set the key as not pushed
    tools.KeyboardController.keyStatus[key] = false;
    //send an event to signal the touch is released
    EventManager.fire("tools.KeyboardController.keyReleased."+key);
}

tools.KeyboardController.keyStatus = {};

document.onkeydown  = tools.KeyboardController.keyPressed;
document.onkeyup    = tools.KeyboardController.keyReleased;
checkInput: function(){
    if (this.isPlayerOne == false) {
    if (myInput.isKeyDown(Input.KEY_UP)) {
    if (this.y > 0) {
    this.y = this.y - 10;
    }
    }
    else
    if (myInput.isKeyDown(Input.KEY_DOWN)) {
    // x,y are taken from the left corner
    if (this.y < game_height - this.height)
    this.y = this.y + 10;
    }
    }
    else {
    if (myInput.isKeyDown(65)) { // 'A'
    if (this.y > 0) {
    this.y = this.y - 10;
    }
    }
    else
    if (myInput.isKeyDown(90)) { // 'Z'
    // x,y are taken from the left corner
    if (this.y < game_height - this.height)
    this.y = this.y + 10;
    }
    }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top