Phaser 3 Tilemap Pack Update
Yesterday I updated my Tilemap and Filepack Template to be compatible with Phaser 3.17, webpack 4, babel, and implemented pools for the player and enemy projectiles. I created the template a year ago and other than a minor compatibility update last fall haven’t touched it since.
Phaser 3 Tilemap & File Pack Project Template
Demo
The original idea of the template was to illustrate a concept I was trying to explain to another user over at HTML5GameDevs. The idea was to have a single level scene class which dynamically loaded the proper tilemap each time. That way you would not have to have a different scene for each level of the game. It then grew as I became interested in trying out other ideas and Phaser functions until it was essentially a finished small game. I knew a lot less about Phaser 3 a year ago and the documentation, although extensive, was not in the state that it currently is in. As such I am trying to make a concerted effort to go through, clean up, and replace as much code with actual Phaser API methods as I can.
The major point of this release, besides testing against 3.17, was to implement pools for the projectiles so that the game wasn’t needlessly creating and destroying dozens of sprites while the scene was running. I’m not sure if there was a real performance gain but it is definitely the correct way to do it.
Implementing Pools
1. Modify Level.js
The first thing I had to do was move the imports of the fireball.js and darkFireball.js to Level.js. Previously that had been in the player.js and demon.js files respectively. Then I had to modify the configuration of the two groups in question:
In Level.js
this.enemyAttack = this.add.group();
this.enemyAttack.runChildUpdate = true;
became
this.enemyAttack = this.add.group({
classType: DarkFireball,
maxSize: 50,
runChildUpdate: true
});
and
this.playerAttack = this.add.group();
this.playerAttack.runChildUpdate = true;
became
this.playerAttack = this.add.group({
classType: Fireball,
maxSize: 100,
runChildUpdate: true
});
2. Modify Fireball.js and DarkFireball.js
Next I had to add a method to the to fireball classes to be called when the object is pulled from the pool:
fire (x, y)
{
this.setPosition(x, y);
this.scene.physics.moveTo(this, this.scene.crosshair.x, this.scene.crosshair.y);
this.emitter.flow(0)
this.setActive(true);
this.setVisible(true);
}
Notice that the physics moveTo method is moved from the constructor into the fire method since we want it to happen each time the object is fired. We also have to tell the particle emitter to begin flowing again in case it has exploded in a previous life. The method is nearly identical in both classes other than the fact that the darkFireball is moving towards the player instead of the crosshair.
All instances of this.destroy()
were then changed to this.setActive(false);
and this.setVisible(false);
I also had to modify the constructor of the classes slightly since we are now using Phaser’s object factory which only passes a single paramater, the scene.
3. Modify Player.js and Demon.js
Next was a pretty simple change. I went to the spot in the player and demon classes where the fireball was created and changed it to call the pool instead:
let fireball = this.scene.playerAttack.get();
if (fireball)
{
fireball.fire(this.x, this.y);
}
The group’s get method is called and an object in the group is returned(if it is available). Then the object’s fire method that we created up above is called with the coordinates passed to it.
4. A Bug!
At this point everything was working pretty well except that on occasion the fireballs would glitch out which I suspected was due to the collision methods in the Level class being called over and over again since the fireballs were no longer being destroyed.
I added a simple check in each collision method to see if the fireballs were active…
fireballWall(fireball, wall)
{
if (fireball.active) {
fireball.wallCollide();
}
}
…and the problem was resolved!
Final Thoughts
I looked briefly at replacing my custom “create from tiled objects” method with Phaser’s method but decided it would end up being just as complex if not more since I am checking against the registry to see if the object had previously been picked up or destroyed. I still want to revisit this idea with fresh eyes as I think there is probably a better way of doing it.