escalator-web/src/scene.js

486 lines
14 KiB
JavaScript
Raw Permalink Normal View History

2018-08-21 15:58:08 +02:00
class Scene extends Screen {
2018-08-21 14:10:44 +02:00
constructor(canvas, player) {
2018-08-21 15:58:08 +02:00
super(canvas);
2018-08-21 14:10:44 +02:00
this.player = player;
2018-08-21 15:58:08 +02:00
this.currentHeight = 0;
2018-08-22 11:34:36 +02:00
if (this.maxHeight == undefined) {
2018-08-22 11:34:36 +02:00
this.maxHeight = 0;
2018-08-23 10:50:34 +02:00
}
2018-08-22 11:34:36 +02:00
if (this.collectableNumber == undefined) {
2018-08-24 11:49:25 +02:00
this.collectableNumber = 0;
}
2018-08-21 14:10:44 +02:00
this.initialize();
2018-08-21 15:58:08 +02:00
this.addEventListener('touchstart', (event) => {
this.onTouchStart(event);
});
2018-08-21 14:10:44 +02:00
}
updateCameraSpeed(height) {
2018-08-27 14:52:54 +02:00
this.cameraSpeed = 0.0005 + (height / 200) * 0.00005;
}
get maxHeight() {
return window.localStorage.getItem("maxHeight");
}
set maxHeight(value) {
window.localStorage.setItem("maxHeight", value);
}
2018-08-24 11:49:25 +02:00
get collectableNumber() {
return window.localStorage.getItem("collectableNumber");
}
set collectableNumber(value) {
window.localStorage.setItem("collectableNumber", value);
}
getCollectable(i) {
this.collectableNumber = parseInt(this.collectableNumber, 10) + i;
}
2018-08-21 14:10:44 +02:00
initialize() {
2018-08-21 15:58:08 +02:00
super.initialize();
2018-08-23 11:50:05 +02:00
2018-08-21 14:10:44 +02:00
this.player.reset();
this.platforms = [];
2018-08-24 11:49:25 +02:00
this.collectables = [];
2018-08-21 14:10:44 +02:00
this.cameraHeight = 0;
this.cameraSpeed = 0;
2018-08-21 15:58:08 +02:00
this.currentHeight = 0;
2018-08-21 14:10:44 +02:00
this.started = false;
this.newRecord = false;
2018-08-21 14:10:44 +02:00
2018-08-22 11:34:36 +02:00
// The the line for the high score
this.currentMaxHeight = this.maxHeight;
2018-08-21 14:10:44 +02:00
}
start() {
if (!this.started) {
this.started = true;
2018-08-22 11:20:45 +02:00
this.cameraSpeed = 0.0005;
2018-08-21 14:10:44 +02:00
}
2018-08-21 09:36:14 +02:00
}
drawBackground() {
2018-08-21 16:58:30 +02:00
let pattern = this.context.createPattern(Scene.background, 'repeat');
this.context.fillStyle = pattern;
2018-08-21 09:36:14 +02:00
this.context.beginPath();
2018-08-21 16:58:30 +02:00
this.context.rect(0, - this.cameraHeight * this.height(), this.width(), this.height());
2018-08-21 09:36:14 +02:00
this.context.fill();
}
2018-08-22 11:20:45 +02:00
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;
}
2018-08-21 11:02:20 +02:00
2018-08-21 14:10:44 +02:00
// Check if the game is lost
if (this.player.y + this.player.size < this.cameraHeight) {
2018-08-23 10:37:50 +02:00
this.status = Status.GameOver;
2018-08-21 14:10:44 +02:00
}
2018-08-21 11:02:20 +02:00
2018-08-24 11:49:25 +02:00
while(true) {
// The last platform is the highest.
let last = this.platforms[this.platforms.length - 1];
2018-08-21 14:10:44 +02:00
2018-08-24 11:49:25 +02:00
// 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;
}
2018-08-21 14:10:44 +02:00
}
// Increase position of the camera
this.cameraHeight += this.cameraSpeed * time * this.height();
2018-08-21 11:02:20 +02:00
2018-08-21 14:10:44 +02:00
// Update high score
2018-08-21 15:58:08 +02:00
this.currentHeight = Math.max(this.player.y, this.currentHeight);
2018-08-21 14:10:44 +02:00
if (this.currentHeight > this.maxHeight) {
this.maxHeight = this.currentHeight;
this.newRecord = true;
}
2018-08-21 14:10:44 +02:00
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,
2018-08-21 11:02:20 +02:00
};
2018-08-21 14:10:44 +02:00
if (previous.y >= p.y && next.y < p.y) {
2018-08-21 16:20:23 +02:00
if (next.x >= p.x - p.width / 2 - next.size / 4) {
if (next.x <= p.x + p.width / 2 + next.size / 4) {
2018-08-21 14:10:44 +02:00
// Collision detected
this.player.collide(p.y - next.size/2);
2018-08-21 11:02:20 +02:00
}
}
}
2018-08-21 14:10:44 +02:00
}
2018-08-21 11:02:20 +02:00
2018-08-24 11:49:25 +02:00
// 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)) {
2018-08-24 11:49:25 +02:00
} else {
2018-08-27 14:32:01 +02:00
this.getCollectable(collectable.collect());
2018-08-24 11:49:25 +02:00
}
}
2018-08-21 14:10:44 +02:00
// Collision with the ground
if (this.player.y <= 0) {
this.player.collide(0);
2018-08-21 09:36:14 +02:00
}
2018-08-21 14:10:44 +02:00
// Collisions with the border of the screen
2018-08-21 16:58:30 +02:00
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);
2018-08-26 21:53:16 +02:00
// Update coin frames
this.collectables.map((c) => c.update(time));
if (this.started) {
this.updateCameraSpeed(heightToScore(this.currentHeight));
}
2018-08-21 09:36:14 +02:00
}
2018-08-21 11:02:20 +02:00
addPlatform(object) {
this.platforms.push(object);
}
2018-08-21 09:36:14 +02:00
render() {
this.clear();
2018-08-21 16:58:30 +02:00
this.context.translate(0, this.cameraHeight * this.height());
2018-08-21 09:36:14 +02:00
this.drawBackground();
2018-08-22 11:34:36 +02:00
this.drawHighScoreLine();
2018-08-21 11:02:20 +02:00
for (let platform of this.platforms) {
this.drawPlatform(platform);
2018-08-21 09:36:14 +02:00
}
2018-08-21 11:02:20 +02:00
2018-08-24 11:49:25 +02:00
for (let collectable of this.collectables) {
this.drawCollectable(collectable);
}
2018-08-21 14:10:44 +02:00
this.drawObject(this.player);
2018-08-21 16:58:30 +02:00
this.context.resetTransform();
2018-08-21 11:02:20 +02:00
this.drawHud();
2018-08-23 10:37:50 +02:00
if (this.status === Status.GameOver) {
this.drawGameOver();
}
2018-08-21 09:36:14 +02:00
}
2018-08-21 09:48:31 +02:00
2018-08-24 11:49:25 +02:00
drawCollectable(object) {
2018-08-27 10:47:48 +02:00
object.drawOn(this.canvas, this.context);
2018-08-24 11:49:25 +02:00
}
2018-08-22 11:34:36 +02:00
drawHighScoreLine() {
if (this.currentMaxHeight <= 0) {
return;
}
let height = this.height() * (1 - this.currentMaxHeight - this.player.size / 2);
2018-08-22 16:01:12 +02:00
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();
2018-08-22 16:18:37 +02:00
this.context.font = "15px Dimbo";
this.context.fillStyle = "rgb(255, 255, 255)";
2018-08-23 11:50:05 +02:00
this.context.fillText("High score: " + heightToScore(this.currentMaxHeight), 0, height - 5);
2018-08-22 11:34:36 +02:00
}
2018-08-21 11:02:20 +02:00
drawObject(object) {
2018-08-21 09:48:31 +02:00
let size = object.size * this.width();
2018-08-21 16:20:23 +02:00
this.context.drawImage(
Box.texture,
0, 64 * this.player.frameNumber, 64, 64,
2018-08-21 11:02:20 +02:00
(object.x - object.size / 2) * this.width(),
2018-08-21 16:58:30 +02:00
(1 - object.y - object.size / 2) * this.height(),
2018-08-21 16:20:23 +02:00
size, size
2018-08-21 09:48:31 +02:00
);
}
2018-08-21 11:02:20 +02:00
drawPlatform(object) {
let width = object.width * this.width();
let height = object.height * this.height();
2018-08-21 16:38:06 +02:00
this.context.drawImage(
Platform.texture,
0, 0, 64, 64,
2018-08-21 11:02:20 +02:00
(object.x - object.width / 2) * this.width(),
2018-08-21 16:58:30 +02:00
(1 - object.y - object.height / 2) * this.height(),
2018-08-21 16:38:06 +02:00
width, width,
2018-08-21 11:02:20 +02:00
);
}
drawHud() {
2018-08-22 11:20:45 +02:00
// Draw scores
let fontSize = 20;
2018-08-22 16:18:37 +02:00
this.context.font = fontSize + "px Dimbo";
2018-08-21 11:02:20 +02:00
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);
2018-08-22 11:20:45 +02:00
2018-08-24 11:49:25 +02:00
// Draw coin number
this.context.drawImage(Collectable.texture,
2018-08-26 21:58:54 +02:00
0, 0, 64, 64,
2018-08-24 11:49:25 +02:00
0, fontSize + 5, 0.05 * this.width(), 0.05 * this.width());
this.context.fillText(this.collectableNumber, 0.05 * this.width() + 5, 2 * fontSize)
2018-08-23 10:37:50 +02:00
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();
2018-08-22 11:20:45 +02:00
2018-08-23 10:37:50 +02:00
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();
2018-08-22 11:20:45 +02:00
2018-08-23 10:37:50 +02:00
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();
}
2018-08-22 11:20:45 +02:00
if (this.status === Status.Paused || this.status === Status.Waking) {
let fontSize = 50;
this.context.fillStyle = 'rgba(255, 255, 255, 0.5)';
2018-08-22 16:18:37 +02:00
this.context.font = fontSize + "px Dimbo";
2018-08-22 11:20:45 +02:00
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);
2018-08-22 11:20:45 +02:00
}
}
2018-08-23 10:37:50 +02:00
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";
2018-08-23 10:37:50 +02:00
this.context.fillStyle = 'rgb(255, 255, 255)';
this.context.font = "50px Dimbo";
this.centerFillText(text, this.height() / 6);
this.centerStrokeText(text, this.height() / 6);
2018-08-23 10:37:50 +02:00
let size = 40;
this.context.font = size + "px Dimbo";
2018-08-23 11:50:05 +02:00
let currentScoreText = "Your score: " + heightToScore(this.currentHeight);
this.centerFillText(currentScoreText, this.height() / 3);
this.centerStrokeText(currentScoreText, this.height() / 3);
2018-08-23 10:37:50 +02:00
2018-08-23 11:50:05 +02:00
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,
}
2018-08-23 10:37:50 +02:00
}
2018-08-22 11:20:45 +02:00
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(),
};
2018-08-21 15:58:08 +02:00
}
onTouchStart(event) {
let e = event.changedTouches[0];
2018-08-22 11:20:45 +02:00
if (this.started) {
if (this.status === Status.Running && isInBox(position(e), this.makePauseBox())) {
this.status = Status.Paused;
2018-08-22 16:25:34 +02:00
return;
2018-08-22 11:20:45 +02:00
} else if (this.status === Status.Paused) {
this.status = Status.Waking;
this.wakingTimer = 3;
2018-08-22 16:25:34 +02:00
return;
2018-08-22 11:20:45 +02:00
}
}
2018-08-22 16:24:16 +02:00
if (this.status === Status.Paused) {
return;
}
2018-08-21 15:58:08 +02:00
this.start();
2018-08-23 10:37:50 +02:00
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;
}
2018-08-21 15:58:08 +02:00
}
2018-08-21 11:02:20 +02:00
}
2018-08-21 09:36:14 +02:00
}
2018-08-21 16:58:30 +02:00
Scene.background = new Image();
Scene.background.src = "img/background.png";
2018-08-22 16:01:12 +02:00
Scene.highscore = new Image();
Scene.highscore.src = "img/highscore.png";