Search code examples
jquerykeypress

move element with keypress (multiple)


Diagonal move not working and issue on left-longPress/right simultaneous

But on double keypress the ship goes crazy!

$(document).bind('keydown', function(e) {
    var box = $("#plane"),
        left = 37,
        up = 38,
        right = 39,
        down = 40

    if (e.keyCode == left) {
        box.animate({left: "-=5000"},3000);
    }
    if (e.keyCode == up) {
        box.animate({top: "-=5000"},3000);
    }
    if (e.keyCode == right) {
        box.animate({left:"+=5000"},3000);
    }
    if (e.keyCode == down) {
        box.animate({top: "+=5000"},3000);
    }
});
$(document).bind('keyup', function() {
    $('#plane').stop();
});

Solution

  • In game development it's best to create a wrapper around the keyboard.
    Such wrapper should implement the following:

    • when a key is pressed use Event.preventDefault() to prevent the browser doing default stuff (like i.e. page scrolling on arrow keys)
    • prevent the automatic key-repeat (after a ~1s long-press) check if the keydown event is not an Event.repeat
    • use an array or Set to store all the active (pressed) key's Event.key
    • use an object or Map to define the function actions that we want to trigger for a specific key-name (Event.key)
    • inside the game loop iterate that Set of pressed keys and call the related action function

    To fix the diagonal player movement, you can do it in wo ways, using an angle of rotation, or move the player diagonally by 1 / Math.sqrt(2) (approximately ~ 0.7071) distance. In the following example I'll use angles (in radians):

    const player = {
      el: document.querySelector("#player"),
      x: 100, y: 50,
      dirX: 0, dirY: 0,
      velX: 0, velY: 0,
      power: 2, angle: 0,
      move() {
        if (this.dirX || this.dirY) {
          this.angle = Math.atan2(this.dirY, this.dirX);
          this.velX = Math.cos(this.angle) * this.power;
          this.velY = Math.sin(this.angle) * this.power;
          this.x += this.velX;
          this.y += this.velY;
          this.dirX = this.dirY = 0; // Reset directions
        }
        this.el.style.translate = `${this.x}px ${this.y}px`;
        this.el.style.rotate = `${this.angle}rad`;
      }
    };
    
    const keyboard = {
      actions: new Map(Object.entries({
        ArrowLeft  () { player.dirX = -1; },
        ArrowRight () { player.dirX =  1; },
        ArrowUp    () { player.dirY = -1; },
        ArrowDown  () { player.dirY =  1; },
        // add more key actions here
      })),
      keys: new Set(),
      handle(evt) {
        if (evt.repeat || !this.actions.has(evt.key)) return;
        evt.preventDefault();
        this.keys[evt.type === "keydown" ? "add" : "delete"](evt.key);
      },
      callActions() {
        for (let key of this.keys) this.actions.get(key)();
      }
    };
    
    addEventListener("keydown", (evt) => keyboard.handle(evt));
    addEventListener("keyup", (evt) => keyboard.handle(evt));
    
    const engine = () => {
      keyboard.callActions();
      player.move();
      requestAnimationFrame(engine);
    };
    
    engine();
    #player{
      position: absolute;
      left: 0;  top: 0;
      width: 20px;  height: 20px;
      background: #0bf;  border-radius: 50%;
    }
    Click here to focus and use arrow keys to move:
    <div id="player">&rarr;</div>