Refactoring components into their own classes in our Phaser game
Our simple browser game has come a long way, and as it gets bigger and more complicated, it’s only logical to move some components into its own class. A game scene should only be concerned with putting assets into the screen, and not with defining characters or create their logic.
In today’s blog, we will create a separate Player and Npc classes, and move their logic from a scene into their own classes. Player will be our main character that we can control with arrow keys, and Npc will be other character sprites in our screen (we will treat Npc and enemies the same for now).
The first step is to create new a new player.js and npc.js files in our main directory. And import them in our index.html file.
The logic for a new Player or Npc is very simple. When we create an instance, it will take the following arguments: the scene it belongs it, its initial x, initial y coordinate, and a name we can refer it to.
class Player extends Phaser.GameObjects.Sprite {
constructor(config) {
super(config.scene, config.x, config.y, config.key)
config.scene.add.existing(this)
this.setScale(1.5)
} }
Our custom class will inherit from the Phaser.GameObjects.Sprite class, after we create it, we want to display it onto the screen with config.scene.add.existing(this) and set its scale to 1.5. We will do the same for Npc class. These are the same codes as we put in our scene1.js file when we created the character.
Then, in our scene1.js’s preload method, we will still need to load in our images.
preload() {
this.load.image(‘bulbasaur’, ‘assets/bulbasaur.png’)
this.load.image(‘charmander’, ‘assets/charmander.png’)
this.load.image(‘squirtle’, ‘assets/squirtle.png’)
this.load.atlas(‘pikachu’, ‘assets/pikachuspritesheet.png’, ‘assets/pikachusprites.json’) }
In the create method, instead of adding each sprite, we will create some new Npc instances and a new Player instead.
When we create the new Player and Npcs, we give them an object as an argument which contains the current scene, (because in our custom classes, we add the sprite to the current scene with config.scene.add(this), the x and y coordinates to add the sprite to. Notice the key in the config argument needs to match what we name our images in the preload method for them to show up.
If you save and refresh the page, we will see the exact same scene1 as before.
Next we will also move the movement logic to our Player class. We will take 2 approaches. Back in our Player class, let’s define some custom functions to handle movement:
Notice the codes are exactly the same as they were in the scene1.js file. (This in this case refers to the instance that’s calling the methods). Now that we have the movement logic for our Player, we can delete the redundant codes in our scene1.js’s update method:
To further refactor the codes, let’s move the cursor inputs into the Player class, so in other scenes, we don’t have to define the cursors and then listen for user input, and move our character.
We define our own update() method and move our character sprite accordingly. Instead of moving its x and y coordinates, I’ve set a body velocity. This will enable our character to have collisions with other sprites on the screen. Since we’re still creating the cursor object in our scene, we will pass an argument of cursors to our update().
Back in our scene1.js file’s update method, we can just call this.pikachu.update(this.cursors) and delete the old movement logics like below. Now for each scene that we need to move our character, we only need to add 2 lines! (1 to create the cursors in create(), and 1 to call our character’s update())
In our scene1.js file, we have the following codes to prevent our character from walking off the edges of the top, left, bottom scene into oblivion, (remember if he/she walks to the right edge, our scene transitions to scene2).
// Set world boundaries
// if (this.pikachu.x < 30)
// this.pikachu.x = 30
// else if (this.pikachu.y < 30)
// this.pikachu.y = 30
// else if (this.pikachu.y > 570)
// this.pikachu.y = 570
Now that our character has his/her own class, we can add a one-liner to our Player Constructor to check for collisions with world boundaries:
this.body.setCollideWorldBounds(true)
By doing this, we can remove our redundant codes in our scene1.js file.
The last feature we will implement for today’s blog is grouping all 3 of our NPCs into a group. This has the added benefit of when our character want to interact with anyone of them, we can just add some game logic between our character and the group.
In our scene1.js’s create() method, let’s add the following highlighted code:
Now we can detect collision between our character and NPCs with just:
this.physics.add.collision(this.pikachu, this.groupOfNpcs, some_function_to_be_called). We will stop here for today’s blog and continue next time, try out everything yourself!