diff --git a/assets/js/constants.js b/assets/js/constants.js index fdc2d64..9dfcc56 100644 --- a/assets/js/constants.js +++ b/assets/js/constants.js @@ -1 +1,2 @@ const KEY_UP = 32; +const KEY_R = 82; diff --git a/assets/js/index.js b/assets/js/index.js index 8c72a15..d7557a8 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -1,6 +1,15 @@ +const game = new Game('canvas-game') + window.addEventListener('load', () => { // iteration - 1: create & start the game - + game.start() // iteration - 2: add key listeners to the game + document.addEventListener('keydown', (event) => { + game.onKeyEvent(event) + }) + + document.addEventListener('keyup', (event) => { + game.onKeyEvent(event) + }) }); diff --git a/assets/js/models/brackgorund.js b/assets/js/models/brackgorund.js index f325711..cbec29f 100644 --- a/assets/js/models/brackgorund.js +++ b/assets/js/models/brackgorund.js @@ -2,27 +2,77 @@ class Background { constructor(ctx) { this.ctx = ctx; - // positions + + this.vx = -1 + + this.bgX = 0 + this.bgY = 0 + this.bgWidth = this.ctx.canvas.width + this.bgHeight = this.ctx.canvas.height -18 + + this.footerX = 0 + this.footerY = this.ctx.canvas.height - 79 + this.footerWidth = this.ctx.canvas.width + this.footerHeight = 79 this.bgImg = new Image(); this.bgImg.src = 'assets/img/game-bg.png'; - // load and set ready this.footerImg = new Image(); this.footerImg.src = 'assets/img/game-bg-footer.png'; - // load and set ready - + + this.bgImg.isReady = false + this.footerImg.isReady = false + + this.bgImg.onload = () => { + this.bgImg.isReady = true + } + + this.footerImg.onload = () => { + this.footerImg.isReady = true + } } draw() { if (this.bgImg.isReady && this.footerImg.isReady) { // draw both images + this.ctx.drawImage( + this.bgImg, + this.bgX, + this.bgY, + this.bgWidth, + this.bgHeight + ) + this.ctx.drawImage( + this.footerImg, + this.footerX, + this.footerY, + this.footerWidth, + this.footerHeight + ) + this.ctx.drawImage( + this.footerImg, + this.footerX + this.ctx.canvas.width -1, + this.footerY, + this.footerWidth, + this.footerHeight + ) + + if (this.footerX + this.footerWidth === 0) { + this.footerX = 0 + } } } move() { // move the ground - + this.footerX += this.vx // check bounds } + + onKeyEvent(event) { + if (event.type === 'keydown' && event.keyCode === KEY_R) { + game.restart() + } + } } diff --git a/assets/js/models/flappybird.js b/assets/js/models/flappybird.js index 7558d30..2922ccd 100644 --- a/assets/js/models/flappybird.js +++ b/assets/js/models/flappybird.js @@ -7,26 +7,63 @@ class FlappyBird { this.jumpImpulse = 70; this.vy = 3; + this.width = 0 + this.height = 0 + this.sprite = new Image(); this.sprite.src = 'assets/img/bird.png'; - // sprite setup + this.sprite.isReady = false + this.isImpulsing = false + + // sprite setup + this.sprite.horizontalFrames = 3 + this.sprite.verticalFrames = 1 + this.sprite.initHorizontalFrame = 0 + this.sprite.initVerticalFrame = 0 this.drawCount = 0; this.movements = { up: false } + this.sprite.onload = () => { + this.sprite.isReady = true + this.sprite.frameWidth = Math.floor(this.sprite.width / this.sprite.horizontalFrames) + this.sprite.frameHeight = Math.floor(this.sprite.height / this.sprite.verticalFrames) + this.width = this.sprite.frameWidth + this.height = this.sprite.frameHeight + } } onKeyEvent(event) { // iteration 2: configure frame animation + const status = (event.type === 'keydown') + if (event.keyCode === KEY_UP) { + this.movements.up = status + this.isImpulsing = true + } } draw() { if (this.sprite.isReady) { // draw sprite + this.ctx.drawImage( + this.sprite, + this.sprite.initHorizontalFrame * this.sprite.frameWidth, + this.sprite.initVerticalFrame * this.sprite.frameHeight, + this.sprite.frameWidth, + this.sprite.frameHeight, + this.x, + this.y, + this.width, + this.height + ) this.drawCount++; // animate sprite } + this.animate() + if (this.y <= 0 || this.y + this.height + 79 >= this.ctx.canvas.height) { + game.stop() + } } animate() { @@ -36,14 +73,33 @@ class FlappyBird { animateFrame(initVerticalFrame, initHorizontalFrame, segments, frequency) { // iteration 2: animate the sprite + if (this.drawCount < 10) { + } else if (this.drawCount < 20) { + this.sprite.initHorizontalFrame = 1 + } else if (this.drawCount < 30) { + this.sprite.initHorizontalFrame = 2 + } else { + this.sprite.initHorizontalFrame = 0 + this.drawCount = 0 + } } move() { // iteration 2: move the y + if (this.movements.up && this.isImpulsing) { + this.y += -this.jumpImpulse + this.isImpulsing = false + } else { + this.y += this.vy + } } collides(element) { // iteration 3: check collisions (true|false) + return this.x < element.x + element.width && + this.x + this.width > element.x && + this.y < element.y + element.height && + this.y + this.height > element.y } } diff --git a/assets/js/models/game.js b/assets/js/models/game.js index 5c66a1d..cca4636 100644 --- a/assets/js/models/game.js +++ b/assets/js/models/game.js @@ -10,9 +10,9 @@ class Game { this.fps = 1000 / 60; // iteration 1: setup the backgound - + this.backgound = new Background(this.ctx) // iteration 2: setup the flappy - + this.bird = new FlappyBird(this.ctx, this.canvas.width / 4, this.canvas.height / 2) // iteration 2: setup the flappy this.pipes = []; @@ -20,41 +20,138 @@ class Game { this.pipesFrequency = 100; // bonus: setup the score + this.score = 0 + this.bestScore = 0; + this.isCounting = false + this.scoreIntervalId = undefined + this.isStopped = false } - onKeyEvent(event) { // iteration 2: link flappy key events + this.bird.onKeyEvent(event) + if (this.isStopped) { + this.backgound.onKeyEvent(event) + } } start() { // Iteration 1: each 60f clear - move - draw - [next iterations: addPipes - checkCollisions - checkScore] + if (!window.localStorage.getItem('bestScore')) { + this.bestScore = 0 + } else { + this.bestScore = parseInt(window.localStorage.getItem('bestScore')) + } + + this.drawIntervalId = setInterval(() => { + this.isStopped = false + this.clear() + this.draw() + this.checkCollisions() + this.checkScore() + this.move() + this.drawPipesCount++ + + if (this.drawPipesCount % this.pipesFrequency === 0) { + this.addPipes() + this.drawPipesCount = 0 + } + + }, this.fps) } stop() { // Iteration 1: stop the game + this.isStopped = true + this.isCounting = false + clearInterval(this.drawIntervalId) + clearInterval(this.scoreIntervalId) + this.end() } restart() { // Bonus: restart on demand + this.pipes = [] + this.score = 0 + this.bird.y = this.canvas.height / 2 + this.start() } end() { // Iteration 4: stop the game and setup score + this.saveScore() + this.ctx.save() + this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)' + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) + this.ctx.font = '26px FlappyFont' + this.ctx.fillStyle = 'white' + this.ctx.textAlign = 'center' + this.ctx.fillText( + 'Game over!', + this.canvas.width / 2, + this.canvas.height / 3, + ) + this.ctx.fillText( + `Score: ${this.score}`, + this.canvas.width / 2, + this.canvas.height / 2, + ) + this.ctx.fillText( + `Press 'R' to restart`, + this.canvas.width / 2, + this.canvas.height * 2 / 3, + ) + this.ctx.restore() } clear() { // Iteration 1: clean the screen + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) + + this.pipes = this.pipes.filter(pipe => pipe.x + pipe.width >= 0) } move() { // Iteration 1: move the background + this.backgound.move() // Iteration 2: move the flappy + this.bird.move() // Iteration 3: move the pipes + this.pipes.forEach(pipe => pipe.move()) } addPipes() { // Iteration 3: each draw pipes frequency cycles concat a pair of pipes to the pipes array and reset the draw cycle + const minSpace = 2 * this.bird.height + this.bird.jumpImpulse + const maxHeight = 0.75 * (this.canvas.height - 79) + const minHeight = 30 + const bottomHeight = Math.floor(Math.random() * (maxHeight - minHeight) + minHeight) + let topHeight = (this.canvas.height - 79) - bottomHeight - minSpace + + if (topHeight > 200) { + topHeight = 200 + } + + this.pipes.push( + new Pipe( + this.ctx, + 0, + 200 - topHeight, + topHeight, + this.canvas.width, + 0, + topHeight, + 'top'), + new Pipe( + this.ctx, + 0, + 0, + bottomHeight, + this.canvas.width, + this.canvas.height - 79 - bottomHeight, + bottomHeight, + 'bottom') + ) } randPairOfPipes() { @@ -63,16 +160,60 @@ class Game { checkCollisions() { // Iteration 4: check pipes collisions among flappy + if (this.pipes.some(pipe => this.bird.collides(pipe))) { + this.stop() + } } checkScore() { // Bonus + if (this.isCounting === false) { + if (this.pipes.some(pipe => pipe.x < this.canvas.width / 4 - 20)) + { + this.isCounting = true + this.score = 1 + this.addScore() + } + } + } + + addScore() { + this.scoreIntervalId = setInterval(() => { + this.score++ + }, this.fps * 100) + } + + saveScore() { + if (this.bestScore > this.score) { + } + else { + window.localStorage.setItem('bestScore',this.score) + } } draw() { // Iteration 1: draw the background + this.backgound.draw() // Iteration 2: draw the flappy + this.bird.draw() // Iteration 2: draw the pipes + this.pipes.forEach(pipe => pipe.draw()) // Bonus: draw the score + this.ctx.font = '26px FlappyFont' + this.ctx.fillStyle = 'white' + this.ctx.textAlign = 'center' + this.ctx.fillText( + `${this.score}`, + 30, + 40, + ) + this.ctx.font = '26px FlappyFont' + this.ctx.fillStyle = 'white' + this.ctx.textAlign = 'center' + this.ctx.fillText( + `Best: ${this.bestScore}`, + 70, + 480, + ) } } diff --git a/assets/js/models/pipe.js b/assets/js/models/pipe.js index 0e69354..80cc5dd 100644 --- a/assets/js/models/pipe.js +++ b/assets/js/models/pipe.js @@ -1,7 +1,10 @@ class Pipe { - constructor(ctx, x, y, height, mode) { + constructor(ctx, sX, sY, sHeight, x, y, height, mode) { this.ctx = ctx; + this.sX = sX; + this.sY = sY; + this.sHeight = sHeight; this.x = x; this.vx = 3; this.y = y; @@ -10,15 +13,39 @@ class Pipe { this.img = new Image(); // iteration 3: load the source checking the mode and setup this.with (must be the image with) + + if (this.mode === 'top') { + this.img.src = './assets/img/pipe-top.png' + } else { + this.img.src = './assets/img/pipe-bottom.png' + } + this.width = this.img.width + + this.img.isReady = false + this.img.onload = () => { + this.img.isReady = true + } } draw() { if (this.img.isReady) { // iteration 3: draw the pipe don't worry if looks unscaled + this.ctx.drawImage( + this.img, + this.sX, + this.sY, + this.img.width, + this.sHeight, + this.x, + this.y, + this.img.width, + this.height, + ) } } move () { // iteration 3: move the pipe + this.x -= this.vx } }