Step 9 - Scoring Points!
Any good game needs points, so let's add some!
First we'll add a score label and best score to our Level
to keep track of the current score for us. Additionally we'll add an incrementScore()
to up the value. We can use the browser's localStorage
feature to keep track of our best score.
typescript
// level.tsexport class Level extends ex.Scene {...score: number = 0;best: number = 0;scoreLabel = new ex.Label({text: 'Score: 0',x: 0,y: 0,z: 1,font: new ex.Font({size: 20,color: ex.Color.White})});bestLabel = new ex.Label({text: 'Best: 0',x: 400,y: 0,z: 1,font: new ex.Font({size: 20,color: ex.Color.White,textAlign: ex.TextAlign.End})});onInitialize(engine: ex.Engine): void {...this.add(this.scoreLabel);this.add(this.bestLabel);const bestScore = localStorage.getItem('bestScore');if (bestScore) {this.best = +bestScore;this.setBestScore(this.best);} else {this.setBestScore(0);}}incrementScore() {this.scoreLabel.text = `Score: ${++this.score}`;this.setBestScore(this.score);}setBestScore(score: number) {if (score > this.best) {localStorage.setItem('bestScore', this.score.toString());this.best = score;}this.bestLabel.text = `Best: ${this.best}`;}}
typescript
// level.tsexport class Level extends ex.Scene {...score: number = 0;best: number = 0;scoreLabel = new ex.Label({text: 'Score: 0',x: 0,y: 0,z: 1,font: new ex.Font({size: 20,color: ex.Color.White})});bestLabel = new ex.Label({text: 'Best: 0',x: 400,y: 0,z: 1,font: new ex.Font({size: 20,color: ex.Color.White,textAlign: ex.TextAlign.End})});onInitialize(engine: ex.Engine): void {...this.add(this.scoreLabel);this.add(this.bestLabel);const bestScore = localStorage.getItem('bestScore');if (bestScore) {this.best = +bestScore;this.setBestScore(this.best);} else {this.setBestScore(0);}}incrementScore() {this.scoreLabel.text = `Score: ${++this.score}`;this.setBestScore(this.score);}setBestScore(score: number) {if (score > this.best) {localStorage.setItem('bestScore', this.score.toString());this.best = score;}this.bestLabel.text = `Best: ${this.best}`;}}
When our Bird
flies between two pipes we want to increment the score. To do this will create a new file score-trigger.ts
to detect this.
typescript
// score-trigger.tsexport class ScoreTrigger extends ex.Actor {constructor(pos: ex.Vector, private level: Level) {super({pos,width: 32,height: Config.PipeGap,anchor: ex.vec(0, 0),vel: ex.vec(-Config.PipeSpeed, 0)})this.on('exitviewport', () => {this.kill();});}override onCollisionStart(): void {this.level.incrementScore();}}
typescript
// score-trigger.tsexport class ScoreTrigger extends ex.Actor {constructor(pos: ex.Vector, private level: Level) {super({pos,width: 32,height: Config.PipeGap,anchor: ex.vec(0, 0),vel: ex.vec(-Config.PipeSpeed, 0)})this.on('exitviewport', () => {this.kill();});}override onCollisionStart(): void {this.level.incrementScore();}}
Now in our pipe factory we put these triggers in between our pipes, and add ScoreTrigger
to our reset()
and stop()
routines
typescript
// pipe-factory.tsexport class PipeFactory {...spawnPipes() {...const scoreTrigger = new ScoreTrigger(ex.vec(this.level.engine.screen.drawWidth,randomPipePosition),this.level);this.level.add(scoreTrigger);}...reset() {for (const actor of this.level.actors) {if (actor instanceof Pipe ||actor instanceof ScoreTrigger) {actor.kill();}}}stop() {this.timer.stop();for (const actor of this.level.actors) {if (actor instanceof Pipe ||actor instanceof ScoreTrigger) {actor.vel = ex.vec(0, 0);}}}}
typescript
// pipe-factory.tsexport class PipeFactory {...spawnPipes() {...const scoreTrigger = new ScoreTrigger(ex.vec(this.level.engine.screen.drawWidth,randomPipePosition),this.level);this.level.add(scoreTrigger);}...reset() {for (const actor of this.level.actors) {if (actor instanceof Pipe ||actor instanceof ScoreTrigger) {actor.kill();}}}stop() {this.timer.stop();for (const actor of this.level.actors) {if (actor instanceof Pipe ||actor instanceof ScoreTrigger) {actor.vel = ex.vec(0, 0);}}}}
You might notice that sometimes the score double counts sometimes! Well this is because the bird is using a box collider and is rotating into the score trigger! This is an easy fix, we'll switch to a circle collider in the bird.
typescript
// bird.tsexport class Bird extends ex.Actor {...constructor(private level: Level) {super({pos: Config.BirdStartPos,// width: 16,// height: 16,radius: 8,color: ex.Color.Yellow});}...}
typescript
// bird.tsexport class Bird extends ex.Actor {...constructor(private level: Level) {super({pos: Config.BirdStartPos,// width: 16,// height: 16,radius: 8,color: ex.Color.Yellow});}...}