javascriptaddeventlistenergame-loop

How to use addEventListener in a game loop


I'm relatively new to Javascript, so please bear with me if this is a rather trivial problem.

I'm trying to make a game where I can control a player with the arrow keys. A simplified version of the code (which doesn't work) looks like the following:

class Player {
  constructor(X) {
    this.X = X;
  }
}

class Game {
  constructor() {
    this.gamePlayer = new Player(0);
    window.requestAnimationFrame(() => this.gameLoop());
  }

  playerMovement(event) {
    switch (event.code) {
      case "ArrowLeft":
        console.log(this.gamePlayer.X);
        this.gamePlayer.X -= 1;
        break;
      case "ArrowRight":
        console.log(this.gamePlayer.X);
        this.gamePlayer.X += 1;
        break;
    }
  }

  gameLoop() {
    window.addEventListener("keydown", this.playerMovement);

    setTimeout(() => {
      window.requestAnimationFrame(() => this.gameLoop());
    }, 50);
  }
}

window.onload = () => {
  let game = new Game();
};

So, basically, I'd like to control gamePlayer.X with the left and right arrow keys. However, with the above code, whenever I press an arrow key, I get the following error message:

Uncaught TypeError: Cannot read property 'X' of undefined at playerMovement

So my question is, why can't it read this.gamePlayer.X inside the function playerMovement? How should I change the code to make it work?


Solution

  • You only need to register the event listener once, can't hurt to do so in the constructor. You seem to know this already as you're doing it elsewhere, but the reason you're getting an error is the scope changed when you used window.addEventListener("keydown", this.playerMovement); instead of window.addEventListener("keydown", (e) => this.playerMovement(e));. when you pass the function directly the scope becomes that of the caller - in this case window. The lambda maintains the scope.

    Also I'm not sure what that setTimeout is doing in gameloop, I suspect it will cause you problems later.

    class Player {
      constructor(X) {
        this.X = X;
      }
    }
    
    class Game {
      constructor() {
        this.gamePlayer = new Player(0);
        window.requestAnimationFrame(() => this.gameLoop());
        window.addEventListener("keydown", (e) => this.playerMovement(e));
      }
    
      playerMovement(event) {
        switch (event.code) {
          case "ArrowLeft":
            console.log(this.gamePlayer.X);
            this.gamePlayer.X -= 1;
            break;
          case "ArrowRight":
            console.log(this.gamePlayer.X);
            this.gamePlayer.X += 1;
            break;
        }
      }
    
      gameLoop() {
        
    
        window.requestAnimationFrame(() => this.gameLoop());
      }
    }
    
    window.onload = () => {
      let game = new Game();
    };