class Scene extends Screen { constructor(canvas, player) { super(canvas); this.player = player; this.currentHeight = 0; if (this.maxHeight == undefined) { this.maxHeight = 0; } if (this.collectableNumber == undefined) { this.collectableNumber = 0; } this.initialize(); this.addEventListener('touchstart', (event) => { this.onTouchStart(event); }); } get maxHeight() { return window.localStorage.getItem("maxHeight"); } set maxHeight(value) { window.localStorage.setItem("maxHeight", value); } get collectableNumber() { return window.localStorage.getItem("collectableNumber"); } set collectableNumber(value) { window.localStorage.setItem("collectableNumber", value); } getCollectable(i) { this.collectableNumber = parseInt(this.collectableNumber, 10) + i; } initialize() { super.initialize(); this.player.reset(); this.platforms = []; this.collectables = []; this.cameraHeight = 0; this.cameraSpeed = 0; this.currentHeight = 0; this.started = false; this.newRecord = false; // The the line for the high score this.currentMaxHeight = this.maxHeight; } start() { if (!this.started) { this.started = true; this.cameraSpeed = 0.0005; } } drawBackground() { let pattern = this.context.createPattern(Scene.background, 'repeat'); this.context.fillStyle = pattern; this.context.beginPath(); this.context.rect(0, - this.cameraHeight * this.height(), this.width(), this.height()); this.context.fill(); } update(time = 0.02) { if (this.wakingTimer !== undefined) { this.wakingTimer -= time; if (this.wakingTimer < 0) { this.wakingTimer = undefined; this.status = Status.Running; } } if (this.status !== Status.Running) { return; } // Check if the game is lost if (this.player.y + this.player.size < this.cameraHeight) { this.status = Status.GameOver; } while(true) { // The last platform is the highest. let last = this.platforms[this.platforms.length - 1]; // If the last platform is on screen, we need to create the next platform. if (last === undefined || last.y <= this.cameraHeight + 1) { let lastY = last === undefined ? 0 : last.y; let diff = 0.1 + Math.random() * 0.4; let platform = new Platform(Math.random(), lastY + diff, 0.2); this.platforms.push(platform); if (last !== undefined && Math.random() < 0.2) { // Create a random coin let x = Math.random(); let y = lastY + Math.random(); this.collectables.push(new Collectable(x, y)); } } else { break; } } // Increase position of the camera this.cameraHeight += this.cameraSpeed * time * this.height(); // Update high score this.currentHeight = Math.max(this.player.y, this.currentHeight); if (this.currentHeight > this.maxHeight) { this.maxHeight = this.currentHeight; this.newRecord = true; } let previous = { x: this.player.x, y: this.player.y + this.player.size / 2, }; this.player.update(time); let next = { x: this.player.x, y: this.player.y + this.player.size / 2, size: this.player.size, }; // Detect collision with platform for (let platform of this.platforms) { let p = { x: platform.x, y: platform.y + platform.height / 2, width: platform.width, height: platform.height, }; if (previous.y >= p.y && next.y < p.y) { if (next.x >= p.x - p.width / 2 - next.size / 4) { if (next.x <= p.x + p.width / 2 + next.size / 4) { // Collision detected this.player.collide(p.y - next.size/2); } } } } // Detect collision with collectable for (let collectable of this.collectables) { if (collectable.collected) { continue; } // Ugly but don't care // Copied and adapted from sdz if( (collectable.x - collectable.size / 2 >= next.x + next.size / 4) || (collectable.x + collectable.size / 2 <= next.x - next.size / 4) || (collectable.y - collectable.size / 2 >= next.y + next.size / 4) || (collectable.y + collectable.size / 2 <= next.y - next.size / 4)) { } else { collectable.collect(); this.getCollectable(1); } } // Collision with the ground if (this.player.y <= 0) { this.player.collide(0); } // Collisions with the border of the screen this.player.x = Math.max(this.player.x, this.player.size / 4); this.player.x = Math.min(this.player.x, 1 - this.player.size / 4); // Update coin frames this.collectables.map((c) => c.update(time)); } addPlatform(object) { this.platforms.push(object); } render() { this.clear(); this.context.translate(0, this.cameraHeight * this.height()); this.drawBackground(); this.drawHighScoreLine(); for (let platform of this.platforms) { this.drawPlatform(platform); } for (let collectable of this.collectables) { this.drawCollectable(collectable); } this.drawObject(this.player); this.context.resetTransform(); this.drawHud(); if (this.status === Status.GameOver) { this.drawGameOver(); } } drawCollectable(object) { object.drawOn(this.canvas, this.context); } drawHighScoreLine() { if (this.currentMaxHeight <= 0) { return; } let height = this.height() * (1 - this.currentMaxHeight - this.player.size / 2); this.context.save(); this.context.translate(0, height); let pattern = this.context.createPattern(Scene.highscore, 'repeat'); this.context.fillStyle = pattern; this.context.beginPath(); this.context.rect(0, 0, this.width(), 32); this.context.fill(); this.context.restore(); this.context.font = "15px Dimbo"; this.context.fillStyle = "rgb(255, 255, 255)"; this.context.fillText("High score: " + heightToScore(this.currentMaxHeight), 0, height - 5); } drawObject(object) { let size = object.size * this.width(); this.context.drawImage( Box.texture, 0, 64 * this.player.frameNumber, 64, 64, (object.x - object.size / 2) * this.width(), (1 - object.y - object.size / 2) * this.height(), size, size ); } drawPlatform(object) { let width = object.width * this.width(); let height = object.height * this.height(); this.context.drawImage( Platform.texture, 0, 0, 64, 64, (object.x - object.width / 2) * this.width(), (1 - object.y - object.height / 2) * this.height(), width, width, ); } drawHud() { // Draw scores let fontSize = 20; this.context.font = fontSize + "px Dimbo"; this.context.fillStyle = 'rgb(255, 255, 255)'; let text = "Score: " + Math.floor(100 * this.currentHeight); if (this.newRecord) { text += " ★"; } this.context.fillText(text, 0, 20); // Draw coin number this.context.drawImage(Collectable.texture, 0, 0, 64, 64, 0, fontSize + 5, 0.05 * this.width(), 0.05 * this.width()); this.context.fillText(this.collectableNumber, 0.05 * this.width() + 5, 2 * fontSize) if (this.status === Status.Running) { // Draw pause button let box = this.makePauseBox(); this.context.fillStyle = 'rgba(255, 255, 255, 0.5)'; this.context.beginPath(); this.context.rect(box.x, box.y, box.width, box.height); this.context.fill(); this.context.fillStyle = 'rgba(0, 0, 0, 0.5)'; this.context.beginPath(); this.context.rect( box.x + box.width * 0.3, box.y + box.height * 0.1, box.width * 0.1, box.height * 0.8, ); this.context.fill(); this.context.beginPath(); this.context.rect( box.x + box.width * 0.6, box.y + box.height * 0.1, box.width * 0.1, box.height * 0.8, ); this.context.fill(); } if (this.status === Status.Paused || this.status === Status.Waking) { let fontSize = 50; this.context.fillStyle = 'rgba(255, 255, 255, 0.5)'; this.context.font = fontSize + "px Dimbo"; this.context.beginPath(); this.context.rect(0, 0, this.width(), this.height()); this.context.fill(); let text = this.status === Status.Paused ? "Paused" : Math.floor(this.wakingTimer + 1); text = text + ""; this.context.fillStyle = 'rgb(255, 255, 255)'; this.centerFillText(text, this.height() / 2); this.centerStrokeText(text, this.height() / 2); } } drawGameOver() { this.context.fillStyle = 'rgba(255, 255, 255, 0.5)'; this.context.beginPath(); this.context.rect(0, 0, this.width(), this.height()); this.context.fill(); let text = this.newRecord ? "New Record!" : "Game Over"; this.context.fillStyle = 'rgb(255, 255, 255)'; this.context.font = "50px Dimbo"; this.centerFillText(text, this.height() / 6); this.centerStrokeText(text, this.height() / 6); let size = 40; this.context.font = size + "px Dimbo"; let currentScoreText = "Your score: " + heightToScore(this.currentHeight); this.centerFillText(currentScoreText, this.height() / 3); this.centerStrokeText(currentScoreText, this.height() / 3); let highScoreText = "Highest score: " + heightToScore(this.maxHeight); this.centerFillText(highScoreText, this.height() / 3 + 1.5 * size); this.centerStrokeText(highScoreText, this.height() / 3 + 1.5 * size); let retryBox = this.makeRetryBox(); this.context.fillText("Retry", retryBox.x, retryBox.y + 40); this.context.strokeText("Retry", retryBox.x, retryBox.y + 40); let exitBox = this.makeExitBox(); this.context.fillText("Exit", exitBox.x, exitBox.y + 40); this.context.strokeText("Exit", exitBox.x, exitBox.y + 40); } makeExitBox() { let fontSize = 40; let text = "Exit"; this.context.font = fontSize + "px Dimbo"; this.context.fillStyle = "rgb(255, 255, 255)"; this.context.strokeStyle = "rgb(0, 0, 0)"; let width = this.context.measureText(text).width; return { x: 2 * this.width() / 3 - width / 2, y: 3 * this.height() / 4, width: width, height: fontSize, } } makeRetryBox() { let fontSize = 40; let text = "Retry"; this.context.font = fontSize + "px Dimbo"; this.context.fillStyle = "rgb(255, 255, 255)"; this.context.strokeStyle = "rgb(0, 0, 0)"; let width = this.context.measureText(text).width; return { x: this.width() / 3 - width / 2, y: 3 * this.height() / 4, width: width, height: fontSize, } } makePauseBox() { let startX = 0.85; let startY = 0.05; let size = 0.1; return { x: startX * this.width(), y: startY * this.width(), width: size * this.width(), height: size * this.width(), }; } onTouchStart(event) { let e = event.changedTouches[0]; if (this.started) { if (this.status === Status.Running && isInBox(position(e), this.makePauseBox())) { this.status = Status.Paused; return; } else if (this.status === Status.Paused) { this.status = Status.Waking; this.wakingTimer = 3; return; } } if (this.status === Status.Paused) { return; } this.start(); if (this.status === Status.Running) { if (e !== undefined) { this.player.jump(e.clientX / window.innerWidth, e.clientY / window.innerHeight); } } else if (this.status === Status.GameOver) { let p = position(e); if (isInBox(p, enlargeBox(this.makeRetryBox()))) { // Retry game this.initialize(); this.status = Status.Running; } else if (isInBox(p, enlargeBox(this.makeExitBox()))) { // Go back to menu this.status = Status.Finished; this.after = Screens.Menu; } } } } Scene.background = new Image(); Scene.background.src = "img/background.png"; Scene.highscore = new Image(); Scene.highscore.src = "img/highscore.png";