Pobtastic / Goldfish Game / Extras

Created Sun, 28 Apr 2024 22:43:11 +0100 Modified Thu, 12 Dec 2024 14:35:47 +0000

Introduction

“Buoyancy”: Upwards Force On The Player

Let’s just keep this ultra simple. We’ll change the following line:

// Reset velocity every frame to stop movement when no key is pressed.
this.player.setVelocity(0);

To instead only apply to the horizontal plane, and always apply a little “upward force” vertically on the player:

  update() {
    // Manage moving the player.
    const moveSpeed = 8 * 8 * this.gameScale;

    // Reset X velocity every frame to stop movement when no key is pressed.
    this.player.setVelocityX(0);

    // Provide a little buoyancy for when no keys are being pressed.
    this.player.setVelocityY(-moveSpeed / 4);

    // Handle left and right.
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-moveSpeed);
    }
    else if (this.cursors.right.isDown) {
      this.player.setVelocityX(moveSpeed);
    }

    // Handle up and down.
    if (this.cursors.up.isDown) {
      this.player.setVelocityY(-moveSpeed);
    }
    else if (this.cursors.down.isDown) {
      this.player.setVelocityY(moveSpeed);
    }

  }
Note that the rest of the control code stays exactly the same, as we’re not incrementing the existing value, rather we’re setting what it needs to be (hence the player doesn’t rise faster than they “fall”).

Stopping The Bubbles (Out Of Air)

Running out of air isn’t fatal, maybe it was supposed to be but as this subgame wasn’t finished, perhaps it was just never implemented? What it does do though is that it causes the player to lose all their fish, and prevents them from collecting any more until they refill at the surface!

  decreaseOxygenLevel() {

    // Ensure the oxygen level is always zero or higher.
    let newLevel = Math.max(this.data.get('oxygenLevel') - 1, 0);

    // Update the stored value.
    this.data.set('oxygenLevel', newLevel);

    // Is the player out of oxygen?
    if (newLevel === 0) {
      // Lose all their fish...
      this.data.set('fishCaught', 0);

      // Update the fish count display.
      this.textObjects['fish-count'].setText("0");
      this.textObjects['fish-count'].setPosition(6 * 8 * this.gameScale, 22 * 8 * this.gameScale);

      // Stop the air bubbles from being produced.
      this.bubbleEmitter.stop();

      // And prevent the player from being able to collect goldfish.
      this.goldfishCollision.active = false;
    }

    // Draw/ update the air bar.
    this.updateOxygenDisplay(newLevel);
  }

The only extra part for this is to ensure the bubbles “restart” again, so let’s go straight into that next…

Refilling Air At The Surface

This has a slight complication in that the boats “block” the player from being able to refill.

  // Is the player at the surface?
  if (this.player.y === (8 * 8 * this.gameScale)) {

    // This is a rough calculation, it doesn't need to be exact really.
    // Here we're checking if the player is "colliding" with any of the boats.
    const boatBlockingSurface = this.boats.some(boat => {
      return this.player.x >= boat.x + 18 * this.gameScale && this.player.x <= boat.x + this.boatWidth - 12 * this.gameScale;
    });

    // If the player is NOT colliding with any boats then:
    if (!boatBlockingSurface) {

      // Limit the amount of times the audio occurs.
      const currentTime = Date.now();
      if (currentTime - this.lastHitTime >= 1000) {
        // Make the "refill air" sound.
        this.sound.play('refill-air');
        // Update the last "hit" time.
        this.lastHitTime = currentTime;
      }

      //  Refill the oxygen bar.
      this.data.set('oxygenLevel', 19);

      // Restart the bubbles (regardless of if they're already being emitted).
      this.bubbleEmitter.start();

      // Same as above with the goldfish collision.
      this.goldfishCollision.active = true;
    }

  }

Merge Both “On-WorldBounds” Routines

This is a minor detail, in the examples we defined these separately (as the examples are independent of each other), but in the game code they need to be merged together. The reason for this is that body.blocked.right applies to them both - so when one is destroyed, the second check will throw an error due to body.gameObject.anims.currentAnim.key no longer existing.

  this.physics.world.on('worldbounds', (body) => {
    if (body.blocked.right &&
      (
        this.enemyTypes.some(type => type.key === body.gameObject.anims.currentAnim.key) ||
        body.gameObject.anims.currentAnim.key === 'goldfish'
      )
    ) {
      body.gameObject.destroy();
    }
  });

Don’t Overlap Goldfish And Sea Creatures

Same as we did for enemies with other enemies, let’s just destroy the sprite which is still off-screen.

  this.physics.add.overlap(this.enemyGroup, this.goldfishGroup, function (enemy, goldfish) {
    if (enemy.inCamera) {
      enemy.destroy();
    }
    else {
      goldfish.destroy();
    }
  });

Collision With Sea Creatures

The overlap event listener fires continuously when the player and a sea creature overlap. To make it more like how it is in the real subgame, we’ll introduce a delay on how often the sound plays.

  // Declare the time storage variable.
  this.lastHitTime = 0;

  // Listener for player collision with sea creatures.
  this.physics.add.overlap(this.player, this.enemyGroup, (player, enemy) => {

    // Get the current time.
    const currentTime = Date.now();

    // Limit the amount of times the audio/ collision detection occurs.
    if (currentTime - this.lastHitTime >= 1000) {
      // Make the "got hit by a creature" sound.
      this.sound.play('hit-creature');
      // Update the last "hit" time.
      this.lastHitTime = currentTime;
    }

    // Reset the fish caught counter.
    this.data.set('fishCaught', 0);
    this.textObjects['fish-count'].setText(this.data.get('fishCaught'));
    this.textObjects['fish-count'].setPosition(6 * 8 * this.gameScale, 22 * 8 * this.gameScale);
  });

Finishing The Game

The real game … which … isn’t a full game, no-one really knows all that much about it- once the player has collected 20 goldfish, it just instantly starts the real game without so much as even a little “congratulations” message! This I guess isn’t really a surprise given this is a remnant of a subgame which wasn’t actually implemented - but for us, let’s change scene and show a little message.

  // Add an event handler for the player collecting the goldfish.
  this.goldfishCollision = this.physics.add.overlap(this.player, this.goldfishGroup, (player, goldfish) => {

    // Increment the fish caught counter by one.
    this.data.set('fishCaught', this.data.get('fishCaught') + 1);

    // Updates the fish caught counter on the screen. Note; when the count is
    // above 10, we move the horizontal position one digit left to keep the
    // unit aligned.
    this.textObjects['fish-count'].setText(this.data.get('fishCaught'));
    this.textObjects['fish-count'].setPosition((this.data.get('fishCaught') >= 10 ? 5 : 6) * 8 * this.gameScale, 22 * 8 * this.gameScale);

    // Remove the collected goldfish.
    goldfish.destroy();

    // Has the player collected all 20 goldfish?
    // We leave this till last as it replicates the actual game, where you
    // briefly see "20" and then the real game begins.
    if (this.data.get('fishCaught') === 20) {
      // Instead, show our congratulations screen.
      this.scene.start('CongratsScreen');
    }
  });