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.