Build a HTML5 game like “Color Jump” using Phaser 3 and Matter physics – adding coins and commenting the source code

Talking about Color Jump game, Game development, HTML5, Javascript and Phaser.

As promised, here we go with the second step of Color Jump prototype.

In the first step we saw the basic prototype of the game, and now it’s time to add collectible coins, just like we did in the fourth part of “Don’t Touch the Spikes” prototype.

Look at the game:

Tap to jump. Hit the walls matching the color of the ball, or it’s game over. If you fall down, it’s game over. If you fly off the top, it’s game over. Try to collect the coins.

Exactly like in Don’t Touch the Spikes, coins introduce a new kind of Matter body called sensor. Sensors trigger collision events, but do not react with colliding body physically, so the player won’t bounce on the coin, and it’s exactly what we need.

And now you have the full commented source code, line by line:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// the game itself
let game;
 
// global game options
let gameOptions = {
 
    // world gravity
    gravity: 4,
 
    // ball horizontal speed
    ballSpeed: 4,
 
    // jump force
    jumpForce: 30,
 
    // amount of bars each wall is divided in
    bars: 4,
 
    // array with the colors to pick from
    barColors: [0x1abc9c, 0x2980b9, 0x9b59b6, 0xf1c40f, 0xc0392b, 0xecf0f1]
}
 
// constants used to pass "LEFT" and "RIGHT" as arguments rather than "0" and "1"
const LEFT = 0;
const RIGHT = 1;
 
// function to be executed when the windows has loaded
window.onload = function() {
 
    // object containing configuration options
    let gameConfig = {
 
        // render type: let the game decide if CANVAS of WEBGL
        type: Phaser.AUTO,
 
        // width of the game, in pixels
        width: 750,
 
        // height of the game, in pixels
        height: 1334,
 
        // background color (black)
        backgroundColor: 0x000000,
 
        // scene to play
        scene: playGame,
 
        // physics settings
        physics: {
 
            // we are using Matter JS
            default: "matter",
            matter: {
 
                // gravity settings
                gravity: {
                    x: 0,
                    y: gameOptions.gravity
                }
            }
        }
    }
 
    // game creation
    game = new Phaser.Game(gameConfig);
 
    // giving focus to the frame (if any) where the game is running in
    window.focus();
 
    // pure javascript to scale the canvas
    resize();
    window.addEventListener("resize", resize, false);
}
 
// playGame scene
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
 
    // preloading assets
    preload(){
        this.load.image("wall", "wall.png");
        this.load.image("ball", "ball.png");
        this.load.image("coin", "coin.png");
    }
 
    // method to be executed once, when the scene has been created
    create(){
 
        // arrays where to store left and right walls
        this.leftWalls = [];
        this.rightWalls = [];
 
        // each wall is made by "gameOptions.bars" pieces, so we actually have "gameOptions.bars" walls each side
        for(let i = 0; i < gameOptions.bars; i++){
 
            // adding left and right walls
            this.leftWalls[i] = this.addWall(i, LEFT);
            this.rightWalls[i] = this.addWall(i, RIGHT);
        }
 
        // adding the ball
        this.ball = this.matter.add.image(game.config.width / 4, game.config.height / 2, "ball");
 
        // setting ball body as circular
        this.ball.setCircle()
 
        // adding the coin, no matter where, we'll set its position later
        this.coin = this.matter.add.image(0, 0, "coin");
 
        // setting coin body as circular
        this.coin.setCircle();
 
        // setting coin body as static (not affected by gravity or collisions)
        this.coin.setStatic(true);
 
        // setting coin body as sensor. Will fire collision events without actually collide
        this.coin.body.isSensor = true;
 
        // giving the coin a label called "coin"
        this.coin.body.label = "coin"
 
        // this method will randomly place the coin
        this.placeCoin();
 
        // setting ball velocity (horizontal, vertical)
        this.ball.setVelocity(gameOptions.ballSpeed, 0);
 
        // waiting for pointer down input to call "jump" method
        this.input.on("pointerdown", this.jump, this);
 
        // waiting for a "collisionstart" event. "e" is the event, "b1" and "b2" the bodies
        this.matter.world.on("collisionstart", function (e, b1, b2) {
 
            // checking b1 and b2 labels to be "leftwall"
            if(b1.label == "leftwall" || b2.label == "leftwall"){
 
                // handling collisions on the LEFT side
                this.handleWallCollision(LEFT, b1, b2);
            }
 
            // checking b1 and b2 labels to be "rightwall"
            if(b1.label == "rightwall" || b2.label == "rightwall"){
 
                // handling collisions on the RIGHT side
                this.handleWallCollision(RIGHT, b1, b2);
            }
 
            // checking b1 and b2 labels to be "coin"
            if(b1.label == "coin" || b2.label == "coin"){
 
                // calling the method to move the coin elsewhere
                this.placeCoin();
            }
        }, this);
    }
 
    // method to add a wall, given its number (0 = top) and it side
    addWall(wallNumber, side){
 
        // getting "wall" preloaded image
        let wallTexture = this.textures.get("wall");
 
        // determining wall height according to game height and the number of bars
        let wallHeight = game.config.height / gameOptions.bars;
 
        // determining wall x position
        let wallX = side * game.config.width + wallTexture.source[0].width / 2 - wallTexture.source[0].width * side;
 
        // determining wall y position
        let wallY = wallHeight * wallNumber + wallHeight / 2;
 
        // adding the wall
        let wall = this.matter.add.image(wallX, wallY, "wall");
 
        // the wall is static
        wall.setStatic(true);
 
        // giving the wall the proper label
        wall.body.label = (side == RIGHT) ? "rightwall" : "leftwall"
 
        // setting wall height
        wall.displayHeight = wallHeight;
 
        // returning the wall object
        return wall
    }
 
    // method to place the coin
    placeCoin(){
 
        // just placing the coin in a random position between 20% and 80% of the game size
        this.coin.x = Phaser.Math.Between(game.config.width * 0.2, game.config.width * 0.8);
        this.coin.y = Phaser.Math.Between(game.config.height * 0.2, game.config.height * 0.8);
    }
 
    // method to handle ball Vs wall collision
    handleWallCollision(side, bodyA, bodyB){
 
        // if the ball and the wall have different colors...
        if(bodyA.color != bodyB.color){
 
            // restart the game
            this.scene.start("PlayGame");
        }
 
        // calling a method to paint the walls
        this.paintWalls((side == LEFT) ? this.rightWalls : this.leftWalls);
 
        // updating ball velocity
        this.ball.setVelocity(gameOptions.ballSpeed, this.ball.body.velocity.y);
    }
 
    // method to paint the walls, in the argument the array of walls
    paintWalls(walls){
 
        // looping through all walls
        walls.forEach(function(wall){
 
            // picking a random color
            let color = Phaser.Math.RND.pick(gameOptions.barColors);
 
            // tinting the wall
            wall.setTint(color);
 
            // also assigning the wall body a custom "color" property
            wall.body.color = color;
        });
 
        // picking a random wall
        let randomWall = Phaser.Math.RND.pick(walls);
 
        // painting the ball with the same color used by the random wall
        this.ball.setTint(randomWall.body.color);
 
        // also assigning the ball body a custom "color" property
        this.ball.body.color = randomWall.body.color;
    }
 
    // method to jump
    jump(){
 
        // setting new ball velocity
        this.ball.setVelocity((this.ball.body.velocity.x > 0) ? gameOptions.ballSpeed : -gameOptions.ballSpeed, -gameOptions.jumpForce);
    }
 
    // method to be called at each frame
    update(){
 
        // updating ball velocity
        this.ball.setVelocity((this.ball.body.velocity.x > 0) ? gameOptions.ballSpeed : -gameOptions.ballSpeed, this.ball.body.velocity.y);
 
        // if the ball flies off the screen...
        if(this.ball.y < 0 || this.ball.y > game.config.height){
 
            // restart the game
            this.scene.start("PlayGame");
        }
    }
};
 
// pure javascript to resize the canvas and scale the game
function resize(){
    let canvas = document.querySelector("canvas");
    let windowWidth = window.innerWidth;
    let windowHeight = window.innerHeight;
    let windowRatio = windowWidth / windowHeight;
    let gameRatio = game.config.width / game.config.height;
    if(windowRatio < gameRatio){
        canvas.style.width = windowWidth + "px";
        canvas.style.height = (windowWidth / gameRatio) + "px";
    }
    else{
        canvas.style.width = (windowHeight * gameRatio) + "px";
        canvas.style.height = windowHeight + "px";
    }
}

What about some particle effects next time? I will show you how to add eye-candy effects. Download the source code.