const canvas = document.getElementById("lienzo"); const ctx = canvas.getContext("2d"); const width = (canvas.width = 288); const height = (canvas.height = 512); ctx.scale(1, 1); const states = { IDLE: 0, RUNNING: 1, GAMEOVER: 2, }; const images = {}; const audiosFx = {}; function cargarRecursos(images, audioFx, callback) { const recursos = [ { nombre: "bg", tipo: "imagen" }, { nombre: "suelo", tipo: "imagen" }, { nombre: "tuberias", tipo: "imagen" }, { nombre: "flappybird", tipo: "imagen" }, { nombre: "logo", tipo: "imagen" }, { nombre: "ready", tipo: "imagen" }, { nombre: "gameover", tipo: "imagen" }, { nombre: "taptap", tipo: "imagen" }, { nombre: "gameover", tipo: "audio" }, { nombre: "coin", tipo: "audio" }, { nombre: "flap", tipo: "audio" }, ]; let recursosCargados = 0; recursos.forEach((recurso) => { if (recurso.tipo === "imagen") { const img = new Image(); img.src = `./assets/${recurso.nombre}.png`; img.onload = () => { recursosCargados++; if (recursosCargados === recursos.length) { callback(); } }; images[recurso.nombre] = img; } else if (recurso.tipo === "audio") { const audio = new Audio(); audio.src = `./assets/${recurso.nombre}.ogg`; audio.addEventListener("canplaythrough", () => { recursosCargados++; if (recursosCargados === recursos.length) { callback(); } }); audioFx[recurso.nombre] = audio; } }); } class State { constructor(state) { this.state = state; } } class StateIdle extends State { constructor(sprites) { super("IDLE"); this.bird = sprites[0]; this.tuberias = [sprites[1], sprites[2]]; sprites.push(new Ready(), new TapTap()); } enter() { this.bird.velY = this.bird.gravedad = 0; this.tuberias.forEach((tuberia) => { tuberia.velocidad = 0; }); } } class StateRunnig extends State { constructor(sprites) { super("RUNNING"); this.bird = sprites[0]; this.tuberias = [sprites[1], sprites[2]]; this.suelo = sprites[3]; sprites.splice(-2); } enter() { this.bird.inicializa(); this.bird.velY = 0; this.bird.gravedad = 0.2; this.bird.nFramesAnim = 3; this.tuberias.forEach((tuberia) => { tuberia.inicializa(); tuberia.velocidad = 1.5; }); this.suelo.velocidad = 1.5; } } class StateGameOver extends State { constructor(sprites) { super("GAMEOVER"); this.bird = sprites[0]; this.tuberias = [sprites[1], sprites[2]]; this.suelo = sprites[3]; sprites.push(new GameOver(), new TapTap()); } enter() { this.bird.velY = this.bird.gravedad = 0; this.bird.nFramesAnim = 1; this.tuberias.forEach((tuberia) => { tuberia.velocidad = 0; }); this.suelo.velocidad = 0; } } class Logo { constructor() { this.logoImg = images["logo"]; this.posX = width / 2 - this.logoImg.width / 2; this.posY = height - this.logoImg.height - 20; } update() {} draw() { ctx.drawImage(this.logoImg, this.posX, this.posY); } } class Ready { constructor() { this.readyImg = images["ready"]; this.posX = width / 2 - this.readyImg.width / 2; this.posY = height / 4 - this.readyImg.height / 2; } update() {} draw() { ctx.drawImage(this.readyImg, this.posX, this.posY); } } class GameOver { constructor() { this.gameOverImg = images["gameover"]; this.posX = width / 2 - this.gameOverImg.width / 2; this.posY = height / 4 - this.gameOverImg.height / 2; } update() {} draw() { ctx.drawImage(this.gameOverImg, this.posX, this.posY); } } class TapTap { constructor() { this.tapTapImg = images["taptap"]; this.posX = width / 2 - this.tapTapImg.width / 2; this.posY = height / 2 - this.tapTapImg.height / 2; } update() {} draw() { ctx.drawImage(this.tapTapImg, this.posX, this.posY); } } class Suelo { constructor() { this.sueloImg = images["suelo"]; this.posX = 0; this.posY = height - this.sueloImg.height; this.velocidad = 1; } update() { this.posX -= this.velocidad; if (this.posX + this.sueloImg.width <= 0) this.posX = 0; } draw() { ctx.drawImage(this.sueloImg, this.posX, this.posY); ctx.drawImage(this.sueloImg, this.posX + width, this.posY); } } class Tuberia { constructor(posX, tipo) { this.tuberiaImg = images["tuberias"]; this.tipo = tipo; this.width = 52; this.height = 320; this.initialPosX = posX; this.puntuada = false; this.inicializa(); } inicializa() { this.separacion = 120; this.velocidad = 0; this.posX = this.initialPosX; this.posY = Math.floor(Math.random() * (300 - 100 + 1)) + 100 - Math.floor(this.height * 2 + this.separacion) / 2; } generaPos() { this.puntuada = false; this.posX = width; this.posY = Math.floor(Math.random() * (300 - 100 + 1)) + 100 - Math.floor(this.height * 2 + this.separacion) / 2; } update() { this.posX -= this.velocidad; if (this.posX <= 0 - this.width) { this.aumentaDificultad(); this.generaPos(); } } draw() { ctx.drawImage( this.tuberiaImg, 1 * this.width, this.tipo * this.height, this.width, this.height, this.posX, this.posY, this.width, this.height ); ctx.drawImage( this.tuberiaImg, 0 * this.width, this.tipo * this.height, this.width, this.height, this.posX, this.posY + this.height + this.separacion, this.width, this.height ); } detectarColision(bird) { const xA = bird.posX; const yA = Math.floor(bird.posY); const wA = bird.width; const hA = bird.height; const xB = this.posX; const yB = this.posY; const wB = this.width; const hB = this.height; const xC = this.posX; const yC = this.posY + this.height + this.separacion; const wC = this.width; const hC = this.height; const colisionConTuberia1 = xA < xB + wB && xA + wA > xB && yA < yB + hB && yA + hA > yB; const colisionConTuberia2 = xA < xC + wC && xA + wA > xC && yA < yC + hC && yA + hA > yC; return colisionConTuberia1 || colisionConTuberia2; } superada(bird) { if (bird.posX > this.posX + this.width && this.puntuada === false) { this.puntuada = true; return true; } return false; } aumentaDificultad() { if (this.separacion >= 80) this.separacion--; } } class Bird { constructor() { this.birdImg = images["flappybird"]; this.width = 34; this.height = 24; this.nFramesAnim = 3; this.animFrame = 0; this.gameFrame = 0; this.inicializa(); } inicializa() { this.posY = height / 2; this.posX = width / 8; this.gravedad = 0; this.velY = 0; } impulsar() { this.velY = -4; } update() { this.velY += this.gravedad; this.posY += this.velY; this.animFrame = Math.floor(this.gameFrame / 10) % this.nFramesAnim; this.gameFrame++; } draw() { if (this.velY === 0) { ctx.drawImage( this.birdImg, this.animFrame * this.width, 0, this.width, this.height, this.posX, this.posY, this.width, this.height ); return; } ctx.save(); if (this.velY > 0) { ctx.translate(this.posX + this.width / 2, this.posY + this.height / 2); ctx.rotate((30 * Math.PI) / 180); ctx.drawImage( this.birdImg, this.animFrame * this.width, 0, this.width, this.height, -this.width / 2, -this.height / 2, this.width, this.height ); } else { ctx.translate(this.posX + this.width / 2, this.posY + this.height / 2); ctx.rotate((-30 * Math.PI) / 180); ctx.drawImage( this.birdImg, this.animFrame * this.width, 0, this.width, this.height, -this.width / 2, -this.height / 2, this.width, this.height ); } ctx.restore(); } detectarColisiones(tuberias, suelo) { if (this.posY + this.height >= suelo.posY) { return true; } if ( tuberias[0].detectarColision(this) || tuberias[1].detectarColision(this) ) { return true; } return false; } pasaTuberia(tuberias) { if (tuberias[0].superada(this) || tuberias[1].superada(this)) { return true; } return false; } } function iniciarJuego() { let bucle; let puntuacion = 0; ctx.font = "24px 'Press Start 2P'"; ctx.fillStyle = "white"; const sprites = [ new Bird(), new Tuberia(width + 100, 1), new Tuberia(width + 100 + width * 0.5 + 25, 0), new Suelo(), new Logo(), ]; let state = new StateIdle(sprites); state.enter(); document.addEventListener("keydown", function (event) { if (event.code === "Space") { if (state.state === "IDLE" || state.state === "GAMEOVER") { puntuacion = 0; state = new StateRunnig(sprites); state.enter(); } sprites[0].impulsar(); audiosFx["flap"].play(); } }); function gameLoop() { ctx.clearRect(0, 0, width, height); ctx.drawImage(images["bg"], 0, 0, width, height); sprites.forEach((sprite) => { sprite.update(); sprite.draw(); }); if (sprites[0].pasaTuberia([sprites[1], sprites[2]])) { audiosFx["coin"].play(); puntuacion++; } ctx.fillText(puntuacion, 4, 30); const colision = sprites[0].detectarColisiones( [sprites[1], sprites[2]], sprites[3] ); if (colision && state.state !== "GAMEOVER") { state = new StateGameOver(sprites); state.enter(); audiosFx["gameover"].play(); window.cancelAnimationFrame(bucle); } bucle = requestAnimationFrame(gameLoop); } gameLoop(); } cargarRecursos(images, audiosFx, iniciarJuego);