diff --git a/assets/css/main.css b/assets/css/main.css index 035659a..9de32d2 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -27,3 +27,7 @@ body * { flex-direction: column; height: 100%; } + +canvas { + border: black 2px solid; +} diff --git a/assets/js/index.js b/assets/js/index.js index 660a2c6..d79b759 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -1,4 +1,10 @@ window.addEventListener('load', () => { // iteration - 1: create & start the game + const game = new Game("canvas-game") + + game.start() + // iteration - 2: add key listeners to the game + document.addEventListener('keydown', (e) => game.onKeyEvent(e)); + document.addEventListener('keyup', (e) => game.onKeyEvent(e)); }); diff --git a/assets/js/models/background.js b/assets/js/models/background.js new file mode 100644 index 0000000..44696ac --- /dev/null +++ b/assets/js/models/background.js @@ -0,0 +1,62 @@ +class Background { + + constructor(ctx) { + this.ctx = ctx; + // positions + this.x = 0; + this.y = 434; + this.vx = 3; + this.width = this.ctx.canvas.width; + this.height = this.ctx.canvas.height; + + this.bgImg = new Image(); + this.bgImg.src = 'assets/img/game-bg.png'; + // set image dimensions + this.bgImg.width = this.width; + this.bgImg.height = this.height; + + + this.footerImg = new Image(); + this.footerImg.src = 'assets/img/game-bg-footer.png'; + // set image dimensions + this.footerImg.width = this.width; + this.footerImg.height = 64; + + } + + draw() { + // iteration 1: draw the static backgorund img + this.ctx.drawImage(this.bgImg, + 0, + 0, + this.ctx.canvas.width, + this.ctx.canvas.height, + ) + + // iteration 1: draw footer img twice + this.ctx.drawImage(this.footerImg, + this.x, + this.y, + this.footerImg.width, + this.footerImg.height + ); + + this.ctx.drawImage(this.footerImg, + this.x + this.footerImg.width, + this.y, + this.footerImg.width, + this.footerImg.height + ); + } + + move() { + // iteration 1: move the ground + this.x -= this.vx; + + // iteration 1: check bounds and reset position + if(this.x + this.footerImg.width < 0) { + this.x = 0; + } + + } +} diff --git a/assets/js/models/flappybird.js b/assets/js/models/flappybird.js index e4460af..c0bfa32 100644 --- a/assets/js/models/flappybird.js +++ b/assets/js/models/flappybird.js @@ -4,7 +4,7 @@ class FlappyBird { this.ctx = ctx; this.x = x; this.y = y; - this.jumpImpulse = 70; + this.jumpImpulse = 10; this.vy = 3; this.sprite = new Image(); @@ -22,32 +22,66 @@ class FlappyBird { } this.drawCount = 0; + this.wasJumping = false; } onKeyEvent(event) { const isJumping = event.type === 'keydown'; + switch (event.keyCode) { case KEY_UP: - // iteration 2: jump! if necessary =D + if(isJumping && !this.wasJumping) { + this.vy = -this.jumpImpulse; + this.wasJumping = true + setTimeout(() => { + this.vy = 3; + }, 100); + } else if (!isJumping) this.wasJumping = false; } } draw() { // draw sprite - + this.ctx.drawImage(this.sprite, + this.sprite.horizontalFrameIndex * this.width, + this.sprite.verticalFrameIndex * this.height, + this.width, + this.height, + this.x, + this.y, + this.width, + this.height + ) // animate sprite this.animate(); } animate() { // iteration 2: configure frame animation + + this.drawCount ++; + + if(this.drawCount > 10) { + this.drawCount = 0; + this.sprite.horizontalFrameIndex ++; + + if (this.sprite.horizontalFrameIndex >= this.sprite.horizontalFrames) { + this.sprite.horizontalFrameIndex = 0; + } + } } move() { // iteration 2: move the y + 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 43081e1..2a4b90e 100644 --- a/assets/js/models/game.js +++ b/assets/js/models/game.js @@ -10,8 +10,12 @@ class Game { this.fps = 1000 / 60; // iteration 1: setup the background + this.background = new Background(this.ctx); // iteration 2: setup the flappy + this.bird = new FlappyBird(this.ctx, + 0, + this.canvas.height/2); this.pipes = []; this.drawPipesCount = 0; @@ -23,18 +27,32 @@ class Game { onKeyEvent(event) { // iteration 2: link flappy key events + this.bird.onKeyEvent(event); } start() { if (!this.drawIntervalId) { this.drawIntervalId = setInterval(() => { // Iteration 1: each 60f clear - move - draw - [next iterations: addPipes - checkCollisions - checkScore] + this.clear(); + + this.move(); + + this.draw(); + + this.addPipes(); + + if(this.checkCollisions()) { + this.end(); + } + }, this.fps); } } stop() { // Iteration 1: stop the game + clearInterval(this.drawIntervalId); } restart() { @@ -43,33 +61,63 @@ class Game { end() { // Iteration 4: stop the game and setup score + this.stop(); } clear() { // Iteration 1: clean the screen + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } move() { // Iteration 1: move the background + this.background.move(); // Iteration 2: move the flappy + this.bird.move(); // Iteration 3: move the pipes + this.pipes.forEach(p => p.move()); } addPipes() { // Iteration 3: each draw pipes frequency cycles concat a pair of pipes to the pipes array and reset the draw cycle + + if(this.drawPipesCount > this.pipesFrequency) { + this.drawPipesCount = 0; + + const newPipes = this.randPairOfPipes(); + this.pipes.push(newPipes[0]); + this.pipes.push(newPipes[1]); + } } randPairOfPipes() { const space = this.canvas.height - this.background.footerImg.height; - const gap = (this.flappybird.height * 2) + this.flappybird.jumpImpulse; + const gap = (this.bird.height * 2) + this.bird.jumpImpulse*7; const topSize = Math.floor(Math.random() * (space - gap) * 0.75) const bottomSize = space - topSize - gap; // Iteration 3: return two new pipes one at the top and other at the bottom - return [] + + return [ + new Pipe(this.ctx, this.canvas.width, 0, topSize, "top"), + new Pipe(this.ctx, this.canvas.width, space - bottomSize, bottomSize, "bottom") + ]; } checkCollisions() { // Iteration 4: check pipes collisions among flappy and end game if any pipe collides with the bird + + if(this.bird.y < 0 || this.bird.y + this.bird.height > 434) { + return true; + } + + if(this.pipes.find((p) => this.bird.collides(p))) { + + return true; + } + + + + return false; } checkScore() { @@ -78,8 +126,13 @@ class Game { draw() { // Iteration 1: draw the background + this.background.draw(); // Iteration 2: draw the flappy - // Iteration 2: draw the pipes + this.bird.draw(); + // Iteration 3: draw the pipes + this.pipes.forEach(p => p.draw()); + + // Bonus: draw the score this.drawPipesCount++; diff --git a/assets/js/models/pipe.js b/assets/js/models/pipe.js index cf42cd8..005cccd 100644 --- a/assets/js/models/pipe.js +++ b/assets/js/models/pipe.js @@ -9,7 +9,7 @@ class Pipe { this.mode = mode; this.img = new Image(); - // iteration 3: load the source checking the mode and setup this.with (must be the image with) + // iteration 3: load the source checking the mode and setup this.width (must be the image width) this.img.src = `assets/img/pipe-${mode}.png`; this.img.onload = () => { this.width = this.img.width; @@ -18,9 +18,26 @@ class Pipe { draw() { // iteration 3: draw the pipe don't worry if looks unscaled. You can start drawing a green rectangle + let yCrop = 0; + + if(this.mode === "top") { + yCrop = (this.height > this.img.height) ? 0 : this.img.height - this.height; + } + + this.ctx.drawImage(this.img, + 0, + yCrop, + Math.min(this.img.width, this.width), + Math.min(this.img.height, this.height), + this.x, + this.y, + this.width, + this.height + ); } move () { // iteration 3: move the pipe + this.x -= this.vx; } } diff --git a/index.html b/index.html index 9a40904..5f72f6f 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@