Pobtastic / Goldfish Game / Goldfish

Created Tue, 23 Apr 2024 23:00:07 +0100 Modified Thu, 12 Dec 2024 14:35:47 +0000

Introduction

As with all the images, preload first:

  preload() {
    // Preload the goldfish assets.
    for (let i = 1; i <= 8; i++) {
      this.load.image(`goldfish-${i}`, `/images/booty/goldfish-${i}.png`);
    }
  }

First off, collecting goldfish is the whole point of this subgame so let’s initialise a count for how many the player has caught so far:

  create() {
    // Initial value for number of goldfish caught.
    this.data.set('fishCaught', 0);

    // Let's also set the width of the goldfish sprite, as we're going to use
    // it quite a bit here.
    this.goldfishWidth = this.textures.get('goldfish-1').getSourceImage().width;
  }

Then, create an animation using the four frames the goldfish uses:

  // Predefine the animation frames for goldfish.
  const goldfishFrames = [];
  for (let i = 1; i <= 4; i++) {
    goldfishFrames.push({ key: `goldfish-${i}` });
  }

  // Create the goldfish animation.
  this.anims.create({
    key: 'goldfish',
    frames: goldfishFrames,
    frameRate: 10,
    repeat: -1
  });

This is the magic! All the goldfish will be contained within a group which will make them much easier to manage. This allows us to set default properties, and gives them a lot of autonomy:

  // To refer to all our goldfish, let's store them in a group.
  this.goldfishGroup = this.physics.add.group({
    key: 'goldfish-1',
    repeat: 0,

    // This is important; initially - they are inactive/ "dead".
    active: false,

    // When a new goldfish is spawned, they inherit all these properties:
    createCallback: (goldfish) => {
      goldfish.setOrigin(0, 0);

      // Start off-screen by one goldfish width.
      goldfish.setX(-this.goldfishWidth);

      // We can't repurpose the world boundaries, as the player uses them.
      // Instead, we use custom boundaries to do the same thing:
      goldfish.setCollideWorldBounds(true);
      goldfish.body.onWorldBounds = true;
      goldfish.body.setBoundsRectangle(

        // Note; the left starting point is one goldfish off-screen, and the
        // right hand side is also one goldfish off-screen. This means the
        // goldfish isn't destroyed until it's completely off-screen.
        new Phaser.Geom.Rectangle(
          -this.goldfishWidth,
          8 * 8 * this.gameScale,
          config.width + this.goldfishWidth * 2,
          8 * 12 * this.gameScale
        )
      );
    }
  });

  // Set up an event listener for the 'worldbounds' event.
  // This event is triggered when any physics body hits the boundaries of the physics world.
  this.physics.world.on('worldbounds', (body) => {

    // Check if the goldfish is moving out of the right-hand boundary.
    // The 'blocked.right' property indicates the body has hit the right edge of the world bounds.
    if (body.blocked.right && body.gameObject.anims.currentAnim.key === 'goldfish') {

      // Destroy the goldfish if it hits the right boundary to prevent it from moving right forever.
      // This will free it up, so it can be recreated.
      body.gameObject.destroy();
    }
  });

  // Set up the next spawn event.
  this.scheduleNextSpawn();

Usually time events loop, as you need to keep on doing an action. However, I wanted the goldfish to spawn at different times every time, so it seemed like the best solution was to not set “loop: true” but instead, have the event set up the next spawning event itself:

  // Spawns the goldfish, and sets up the next spawning event.
  scheduleNextSpawn() {
    this.time.addEvent({
      delay: Phaser.Math.Between(1, 10) * 500,
      callback: () => {
        this.spawnGoldfish(),
        this.scheduleNextSpawn()
      },
      callbackScope: this
    });
  }

  // Spawns a goldfish.
  spawnGoldfish() {

    // Ensure there's only ever a maximum of 5 goldfish on-screen at any point.
    if (this.goldfishGroup.getTotalUsed() < 5) {

      // Find the first inactive goldfish, and set it to appear off-screen at a
      // random character block height on the left.
      let goldfish = this.goldfishGroup.getFirstDead(
        true,
        -this.goldfishWidth,
        Phaser.Math.Between(8, 18) * 8 * this.gameScale,
        'goldfish-1'
      );

      // If creation was successful, then make it active, set the speed and
      // play the animation.
      if (goldfish) {
        goldfish.setActive(true);
        goldfish.setVelocityX(100);
        goldfish.play('goldfish');
      }
    }
  }

Make sure to output the number of goldfish which have been caught.

  // Add the status text.
  this.printAt("FISH ", 0, 22, "32px", "#000000");
  this.printAt("AIR", 8, 22, "32px", "#000000");

  // Note; we use a label on this text as it needs to be updated.
  this.textObjects['fish-count'] = this.printAt("0", 6, 22, "32px", "#000000");

Lastly, handle the player actually collecting the goldfish.

  // 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();
  });

Again, we’re not trying for a 100% accurate emulation - just a playable approximation to keep this fun. The goldfish are generated a lot faster than in the actual game, so maybe we can tweak the parameters later.

Booty Disassembly