Andi Macht
Andi Macht

Reputation: 13

Restart game implementation in a small JavaScript game with phaser.js

I'm kinda new to coding and I started building a Website with HTML & CSS and came pretty quickly up with the idea to create a game on my page. Because I'm still new to any programming language and only have some starter knowledge I worked a lot with ChatGPT and an example of phaser to edit the code and learn more JavaScript/phaser basics. Currently I'm trying to implement a whole new function instead of just editing code of the example. This function is supposed to give me a restart/respawn button, but I ran into the same issue with every solution I found.

https://html-test.jonaschmidt56.repl.co/gamepage.html

If you want to see the Game itself at its current stage.

This is my current code (all of it), last change I made was putting the restartGame function in the create function, before it was a lone function between update and hitBomb function.

The console always gives me the same error in the player = this.physics.add.sprite(100, 450, 'dude'); line of the restartGame function:

[Error] TypeError: undefined is not an object (evaluating 'this.physics.add')
    restartGame (script2.js:165)
    (anonymous function) (script2.js:147)
    emit (phaser.js:201)
    processDownEvents (phaser.js:103739)
    update (phaser.js:103443)
    updateInputPlugins (phaser.js:102156)
    onMouseDown (phaser.js:102357)
    (anonymous function) (phaser.js:115867)
var config = {
  type: Phaser.AUTO,
  width: 1280,
  height: 720,
  parent: 'game-container',
  physics: {
    default: 'arcade',
    arcade: {
      gravity: {
        y: 300
      },
      debug: false
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  }
};

var player;
var coins;
var bombs;
var platforms;
var cursors;
var score = 0;
var gameOver = false;
var scoreText;
var keyW;
var keyA;
var keyS;
var keyD;
var restartButton;

var game = new Phaser.Game(config);

function preload() {
  this.load.image('background', 'assets/background.png');
  this.load.image('ground', 'assets/platform.png');
  this.load.spritesheet('coin', 'assets/coin.png', {
    frameWidth: 20,
    frameHeight: 24
  });
  this.load.image('bomb', 'assets/bomb.png');
  this.load.spritesheet('dude', 'assets/dude.png', {
    frameWidth: 32,
    frameHeight: 48
  });
}

function create() {
  // cursor keys für WASD
  cursors = this.input.keyboard.createCursorKeys();

  keyW = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W);
  keyA = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);
  keyS = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S);
  keyD = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);

  //  A simple background for our game
  const background = this.add.tileSprite(0, 0, 1280, 720, 'background');
  background.tileScaleX = 1;
  background.tileScaleY = 1;

  background.setOrigin(0, 0);

  //  The platforms group contains the ground and the 2 ledges we can jump on
  platforms = this.physics.add.staticGroup();

  //  Here we create the ground.
  //  Scale it to fit the width of the game (the original sprite is 400x32 in size)
  platforms.create(400, 700, 'ground').setScale(2).refreshBody();
  platforms.create(900, 700, 'ground').setScale(2).refreshBody();

  //  Now let's create some ledges
  platforms.create(600, 600, 'ground');
  platforms.create(600, 300, 'ground');
  platforms.create(50, 250, 'ground');
  platforms.create(900, 420, 'ground');
  platforms.create(1100, 420, 'ground');

  // The player and its settings  
  player = this.physics.add.sprite(100, 450, 'dude');

  //  Player physics properties. Give the little guy a slight bounce.  
  player.setBounce(0.1);
  player.setCollideWorldBounds(true);

  //  Our player animations, turning, walking left and walking right.
  this.anims.create({
    key: 'left',
    frames: this.anims.generateFrameNumbers('dude', {
      start: 0,
      end: 3
    }),
    frameRate: 10,
    repeat: -1
  });

  this.anims.create({
    key: 'turn',
    frames: [{
      key: 'dude',
      frame: 4
    }],
    frameRate: 20
  });

  this.anims.create({
    key: 'right',
    frames: this.anims.generateFrameNumbers('dude', {
      start: 5,
      end: 8
    }),
    frameRate: 10,
    repeat: -1
  });

  //  Input Events
  cursors = this.input.keyboard.createCursorKeys();

  //  Some coins to collect, 12 in total, evenly spaced 70 pixels apart along the x axis  
  coins = this.physics.add.group({
    key: 'coin',
    repeat: 11,
    setXY: {
      x: 0,
      y: 0,
      stepX: 70
    }
  });

  this.anims.create({
    key: 'spin',
    frames: this.anims.generateFrameNumbers('coin', {
      start: 0,
      end: 3
    }),
    frameRate: 10,
    repeat: -1,
  });

  coins.children.iterate(function(child) {
    child.play('spin');
    child.setBounceY(Phaser.Math.FloatBetween(0.1, 0.3));
    child.x += Phaser.Math.Between(0, 400);
    child.y += Phaser.Math.Between(0, 200);
  });

  bombs = this.physics.add.group();

  //  The score

  scoreText = this.add.text(32, 16, 'Score: 0', {
    fontSize: '32px',
    fill: '#fff',
    fontFamily: 'Oswald, sans-serif'
  });

  //  Collide the player and the coins with the platforms
  this.physics.add.collider(player, platforms);
  this.physics.add.collider(coins, platforms);
  this.physics.add.collider(bombs, platforms);

  this.physics.resume();
  restartButton = this.add.text(600, 350, 'Respawn', {
    fontSize: '32px',
    fill: '#fff',
    fontFamily: 'Oswald, sans-serif'
  });
  restartButton.setInteractive();
  restartButton.on('pointerdown', function() {
    restartGame.call(this); // Call restartGame with the correct context
  });

  function restartGame() {
    score = 0;
    gameOver = false;

    coins.clear(true, true);
    bombs.clear(true, true);

    player.setX(100);
    player.setY(450);
    player.clearTint();

    //player = this.physics.add.sprite(100, 450, 'dude');
    //player.setBounce(0.1);
    //player.setCollideWorldBounds(true);

    player = this.physics.add.sprite(100, 450, 'dude');
    player.setBounce(0.1);
    player.setCollideWorldBounds(true);

    coins.children.iterate(function(child) {
      child.enableBody(true, Phaser.Math.Between(0, 400), Phaser.Math.Between(0, 200), true, true);
      child.play('spin');
      child.setBounceY(Phaser.Math.FloatBetween(0.1, 0.3));
    });

    restartButton.setVisible(false);

    this.physics.add.collider(player, platforms);
    this.physics.add.collider(coins, platforms);
    this.physics.add.collider(bombs, platforms);

    console.log(restartGame);
  }
}

function update() {
  if (gameOver) {
    return;
  }

  if ((cursors.left.isDown || keyA.isDown) && !(cursors.right.isDown || keyD.isDown)) {
    player.setVelocityX(-160);

    player.anims.play('left', true);
  } else if ((cursors.right.isDown || keyD.isDown) && !(cursors.left.isDown || keyA.isDown)) {
    player.setVelocityX(160);

    player.anims.play('right', true);
  } else {
    player.setVelocityX(0);

    player.anims.play('turn');
  }

  if ((cursors.up.isDown || keyW.isDown) && player.body.touching.down) {
    player.setVelocityY(-330);
  }

  if (cursors.down.isDown || keyS.isDown) {
    player.setVelocityY(330);
  }
  //  Checks to see if the player overlaps with any of the coins, if he does call the collectCoin functionm
  this.physics.add.overlap(player, coins, collectCoin, null, this);
  this.physics.add.collider(player, bombs, hitBomb, null, this);
}

function collectCoin(player, coin) {
  coin.disableBody(true, true);

  //  Add and update the score
  score += 1;
  scoreText.setText('Score: ' + score);

  if (coins.countActive(true) === 0) {
    //  A new batch of coins to collect
    coins.children.iterate(function(child) {

      child.enableBody(true, child.x, 0, true, true);

    });

    var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);

    var bomb = bombs.create(x, 16, 'bomb');
    bomb.setBounce(1);
    bomb.setCollideWorldBounds(true);
    bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
    bomb.allowGravity = false;

  }
}

function restartGame(scene, physics) {
  score = 0;
  gameOver = false;

  coins.clear(true, true);
  bombs.clear(true, true);

  player.setX(100);
  player.setY(450);
  player.clearTint();

  //player = this.physics.add.sprite(100, 450, 'dude');
  //player.setBounce(0.1);
  //player.setCollideWorldBounds(true);

  player = physics.add.sprite(100, 450, 'dude'); // Use 'scene' to access physics
  player.setBounce(0.1);
  player.setCollideWorldBounds(true);

  coins.children.iterate(function(child) {
    child.enableBody(true, Phaser.Math.Between(0, 400), Phaser.Math.Between(0, 200), true, true);
    child.play('spin');
    child.setBounceY(Phaser.Math.FloatBetween(0.1, 0.3));
  });

  restartButton.setVisible(false);

  this.physics.add.collider(player, platforms);
  this.physics.add.collider(coins, platforms);
  this.physics.add.collider(bombs, platforms);

  console.log(restartGame);
}

function hitBomb(player, bomb) {
  this.physics.pause();

  player.setTint(0xff0000);

  player.anims.play('turn');

  player.anims.stop();

  gameOver = true;

  restartButton.setVisible(true);
}

Upvotes: 1

Views: 157

Answers (1)

winner_joiner
winner_joiner

Reputation: 14720

The solution is to pass the scene / this to the eventlistener (link to the documentation), and pass the function as a function like seens in the example:

restartButton.on('pointerdown', restartGame, this);

If you want don't want to pass the callback function directly, as seen, you could do this.

restartButton.on('pointerdown', function() {
   restartGame.call(this); // Call restartGame with the correct context
}, this); // <-- still passing `this` as the context

Or you could use an arrow function, since the arrow function, doesn't have its own this context ():

restartButton.on('pointerdown', () => {
   restartGame.call(this); 
}); // <-- no need for `this`

Info: This article / documentation explains the javascript nuances, of the last two code snipplets. Basically: "...In arrow functions, this retains the value of the enclosing lexical context's this...."

P.s.: For future reference I would suggest reading this StackoverFlow howto, on how to improve code examples for questions on Stackoverflow. Apart from helping us answer them faster, it can help you, while debugging, to find the problem/issue.

Update:

Well the issue is, that on hit (in the function hitBomb), you are setting many things, and some of them you are not setting back on restart:

So in the function restartGame, there are somethings to do:

  • add the line player.clearTint();
  • add the line this.physics.resume();.
    for this to work you would have to add this on the eventlistener restartButton.on('pointerdown', restartGame, this); in the create function
  • And finally just: remove the line coins.clear(true, true); from the restartGame function, since you still need them.

Upvotes: 0

Related Questions