dead-wastes/game.js

1196 lines
38 KiB
JavaScript
Raw Normal View History

2023-03-22 21:06:30 +00:00
var translateration = 0;
var fps = 0;
var isDebug = false;
var draw_grid = false;
var plummetingSoundNotPlayedBefore = false;
var text_hor_unit;
var text_vert_unit;
var sfx = {};
var mus = {};
var skydecay = 0;
var fps_recent_values = [];
var random_values = []; // from 0 to 1
for (i = 0; i < 500; i++) {
random_values[i] = Math.random();
}
var memorized_platform;
var amounts = {
tree: 200,
cloud: 200,
mountain: 200,
canyon: 80,
collectable: 23,
enemies: 30,
platforms: 50,
};
var env = {
skyColor: 0, //assigned at setup()
floorPos_y: ((512 + 256) * 3) / 4,
groundColor: "#7F562A",
end_x: 3000,
state: "INTRO",
isSoundEnabled: false, // never set it true. it causes premature playing of the game sfx and music before loading.
isMusicEnabled: false,
};
class Entity {
constructor(x, y) {
(this.x = x),
(this.y = y),
(this.size = 1.5),
(this.speed = 8),
(this.score = 0),
(this.lives = 3),
(this.health = 100),
this.prejump_y,
(this.gravity = 1.5);
this.vert_speed = -1; // Is negative.
(this.state = {
isLeft: false,
isRight: false,
isFalling: false,
isJumping: false,
isPlummeting: false,
isDescending: false,
isAscending: false,
}),
(this.projectiles = []);
}
resetValues() {
this.size = 1.5;
this.speed = 7;
}
}
var gameChar;
var flagpole = {
x: env.end_x,
isReached: false,
size_vert: 4,
size_hor: 7,
cell_size: 20,
cell_size_v: 20,
cell_size_h: 20,
};
class Cloud {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
}
}
class Canyon {
constructor(x, width) {
this.x = x;
this.width = width;
}
}
class Collectable {
constructor(x, y, size, worth) {
this.x = x;
this.y = y;
this.size = size;
this.worth = worth;
}
}
class Enemy extends Entity {
constructor(x, y, damage, sin_x_speed, sin_y_speed) {
super(x, y);
this.damage = damage;
this.sin_x_speed = sin_x_speed;
this.sin_y_speed = sin_y_speed;
}
collidesWith(whoCollidesWith) {
let temp_x = this.x;
let temp_y = this.y;
this.x = this.x + 25 * Math.sin(frameCount / this.sin_x_speed) - 15;
this.y = this.y + 50 * Math.sin(frameCount / this.sin_y_speed) - 20;
if (
Math.abs(whoCollidesWith.x - this.x) < 24 * this.size &&
Math.abs(whoCollidesWith.y - this.y) < 24 * this.size
) {
return true;
}
this.y = temp_y;
this.x = temp_x;
return false;
}
drawEnemy() {
// lazy so its just a game character model, but with a black hat and no body.
let temp_y = this.y;
let temp_x = this.x;
this.x = this.x + 25 * Math.sin(frameCount / this.sin_x_speed);
this.y = this.y + 50 * Math.sin(frameCount / this.sin_y_speed);
drawGameChar(this.x, this.y, 1, this.size, false, color(this.damage, 100));
this.y = temp_y;
this.x = temp_x;
}
}
class Platform {
constructor(left_x, right_x, y) {
this.left_x = left_x;
this.right_x = right_x;
this.y = y;
}
isInXBorders(whoCollidesWith) {
return whoCollidesWith.x > this.left_x && whoCollidesWith.x < this.right_x;
}
isUnder(whoCollidesWith) {
return this.isInXBorders(whoCollidesWith) && whoCollidesWith.y < this.y;
}
isOver(whoCollidesWith) {
return this.isInXBorders(whoCollidesWith) && whoCollidesWith.y > this.y;
}
drawPlatform() {
push();
rectMode(CORNERS);
fill(env.groundColor);
rect(this.left_x, this.y, this.right_x, this.y + 10);
pop();
}
}
var mountains_x = [];
var trees_x = [];
var enemies = [];
var clouds = [];
var canyons = [];
var collectables = [];
var platforms = [];
var cnv;
var win_img;
var gameover_img;
function preload() {
soundFormats("mp3", "ogg");
sfx.enemy = loadSound("../assets/sfx/enemy.ogg");
sfx.falling = loadSound("../assets/sfx/falling.ogg");
sfx.gameover = loadSound("../assets/sfx/gamover.ogg");
sfx.jumping = loadSound("../assets/sfx/jumping.ogg");
sfx.levelcomplete = loadSound("../assets/sfx/levelcomplete.ogg");
sfx.plummeting = loadSound("../assets/sfx/plummeting.ogg");
sfx.coin = loadSound("../assets/sfx/coin.mp3");
/* "Midnight Tale", "Take a Chance" and "SCP-x5x" Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: Attribution 4.0 License
http://creativecommons.org/licenses/by/4.0/ */
mus.level = loadSound("../assets/Midnight Tale.mp3");
mus.win = loadSound("../assets/Take A Chance.mp3");
mus.gameover = loadSound("../assets/SCP-x5x.mp3");
win_img = loadImage(
"https://static.wikia.nocookie.net/someordinarygamers/images/8/88/Bp_1401520973_youre_winner.png"
);
gameover_img = loadImage("../assets/gameover.png");
}
function drawFlagpole() {
push();
strokeWeight(0);
fill(0);
translate(translateration, 0);
if (flagpole.isReached && flagpole.cell_size_h > -flagpole.cell_size) flagpole.cell_size_h--;
// the flagpole pole
rect(flagpole.x, env.floorPos_y / 2, 2, env.floorPos_y / 2);
// the flagpole flag
for (i = 0; i < flagpole.size_hor; i++) {
for (j = 0; j < flagpole.size_vert; j++) {
fill(((i + j) % 2) * 255);
rect(
flagpole.x + flagpole.cell_size_h * i,
env.floorPos_y / 2 + flagpole.cell_size_v * j,
flagpole.cell_size_h,
flagpole.cell_size_v
);
}
}
pop();
}
function drawGameChar(x, y, sprite, scale, has_body, hat_color, hand, width_mod, height_mod, jump_shift) {
// Note:
// The drawing starts at bottom-middlecenter! not at top-left!
// The aspect ratio is 5:8.
if (has_body == undefined) has_body = true;
if (hat_color == undefined) hat_color = "red";
if (scale == undefined) {
scale = 1;
}
if (width_mod == undefined) {
width_mod = 1;
}
if (height_mod == undefined) {
height_mod = 1;
}
if (jump_shift == undefined) {
jump_shift = 12;
}
var ax_step = 5 * scale * width_mod;
var ay_step = 8 * scale * height_mod;
jump_shift = jump_shift * scale;
function _drawDefaultHat() {
strokeWeight(1);
stroke(0);
fill(hat_color);
ellipse(x, y - ay_step * 6, ax_step * 10, ay_step * 3);
ellipse(x, y - ay_step * 6, ax_step * 4, ay_step * 1); // x +0.5 * scale is a minor fix of misalignment. May become a bug.
strokeWeight(1);
beginShape();
vertex(x - ax_step * 2, y - ay_step * 6);
vertex(x, y - ay_step * 10);
vertex(x + ax_step * 2, y - ay_step * 6);
endShape();
}
function _drawDefaultBody(jumping) {
if (jumping) {
strokeWeight(1);
stroke(1);
fill("brown");
triangle(x - ax_step * 2, y - ay_step * 1.5, x, y - ay_step * 4, x + ax_step * 2, y - ay_step * 1.5);
} else {
strokeWeight(1);
stroke(1);
fill("brown");
triangle(x - ax_step * 2, y, x, y - ay_step * 4, x + ax_step * 2, y);
}
}
function _drawDefaultHand(direction) {
switch (direction) {
// to left
case 0: {
line(x, y - ay_step * 2.5, x + ax_step * 0.8, y - ay_step * 1);
break;
}
// relaxed
case 1: {
line(x, y - ax_step * 4, x, y - ay_step);
break;
}
// to right
case 2: {
line(x, y - ay_step * 2.5, x - ax_step * 0.8, y - ay_step * 1);
break;
}
// relaxed; doubled for easy animation cycling
case 3: {
line(x, y - ax_step * 4, x, y - ay_step);
break;
}
// relaxed
default: {
line(x, y - ax_step * 2.5, x, y - ay_step);
break;
}
}
}
switch (sprite) {
// Standing, facing frontwards
case 1: {
// Body
if (has_body) _drawDefaultBody();
// Head
fill(0);
ellipse(x, y - ay_step * 5, ax_step * 6, ay_step * 4);
// Eyes
fill(255);
ellipse(x - ax_step * 1.2, y - ay_step * 4.2, ax_step / 1.5, ay_step / 2);
ellipse(x + ax_step * 1.2, y - ay_step * 4.2, ax_step / 1.5, ay_step / 2);
// Hat
_drawDefaultHat();
break;
}
// Jumping, facing forwards
case 2: {
y -= jump_shift;
// Body
if (has_body) _drawDefaultBody(true);
// Hands. (Hands!)
line(x - ax_step * 1, y - ay_step * 2.5, x - ax_step * 3.2, y - ay_step * 3.2);
line(x + ax_step * 1, y - ay_step * 2.5, x + ax_step * 3.2, y - ay_step * 3.2);
// Head
fill(0);
ellipse(x, y - ay_step * 5, ax_step * 6, ay_step * 4);
// Eyes
fill(255);
ellipse(x - ax_step * 1, y - ay_step * 3.8, ax_step / 1.5, ay_step / 2);
ellipse(x + ax_step * 1, y - ay_step * 3.8, ax_step / 1.5, ay_step / 2);
// Hat
_drawDefaultHat();
break;
}
// Walking left
case 3: {
// Body
if (has_body) _drawDefaultBody();
// Hand. (Hand!)
if (has_body) _drawDefaultHand(hand);
// Head
fill(0);
ellipse(x, y - ay_step * 5, ax_step * 6, ay_step * 4);
// Eyes
fill(255);
ellipse(x + ax_step * 1.2, y - ay_step * 4.2, ax_step / 1.5, ay_step / 2);
// Hat
_drawDefaultHat();
break;
}
// Walking right
case 4: {
// Body
if (has_body) _drawDefaultBody();
// Hand. (Hand!)
if (has_body) _drawDefaultHand(hand);
// Head
fill(0);
ellipse(x, y - ay_step * 5, ax_step * 6, ay_step * 4);
// Eyes
fill(255);
ellipse(x - ax_step * 1.2, y - ay_step * 4.2, ax_step / 1.5, ay_step / 2);
// Hat
_drawDefaultHat();
break;
}
// Jumping left
case 5: {
y -= jump_shift;
// Body
if (has_body) _drawDefaultBody(true);
// Hands. (Hands!)
line(x - ax_step * 1, y - ay_step * 2.5, x - ax_step * 3.2, y - ay_step * 3.2);
line(x + ax_step * 1, y - ay_step * 2.5, x + ax_step * 3.2, y - ay_step * 3.2);
// Head
fill(0);
ellipse(x, y - ay_step * 5, ax_step * 6, ay_step * 4);
// Eyes
fill(255);
ellipse(x - ax_step * 1, y - ay_step * 3.8, ax_step / 1.5, ay_step / 2);
// Hat
_drawDefaultHat();
break;
}
// Jumping right
case 6: {
y -= jump_shift;
// Body
if (has_body) _drawDefaultBody(true);
// Hands. (Hands!)
line(x - ax_step * 1, y - ay_step * 2.5, x - ax_step * 3.2, y - ay_step * 3.2);
line(x + ax_step * 1, y - ay_step * 2.5, x + ax_step * 3.2, y - ay_step * 3.2);
// Head
fill(0);
ellipse(x, y - ay_step * 5, ax_step * 6, ay_step * 4);
// Eyes
fill(255);
ellipse(x + ax_step * 1, y - ay_step * 3.8, ax_step / 1.5, ay_step / 2);
// Hat
_drawDefaultHat();
break;
}
// Error
default:
fill("red");
text("Character error - No or wrong sprite number provided", x, y);
break;
}
}
function drawTree(x, y, scale, type, hasLeaves) {
// x, y are the middle bottom of the tree.
noStroke();
fill(255);
if (hasLeaves == undefined) {
hasLeaves = true;
}
if (scale == undefined) {
scale = 1;
}
if (type == 0) {
fill(110, 87, 20);
triangle(x - 15 * scale, y, x, y - 150 * scale, x + 15 * scale, y);
fill(63, 72, 52);
if (hasLeaves) triangle(x - 45 * scale, y - 45 * scale, x, y - 120 * scale, x + 45 * scale, y - 45 * scale);
if (hasLeaves) triangle(x - 45 * scale, y - 85 * scale, x, y - 180 * scale, x + 45 * scale, y - 85 * scale);
} else if (type == 1) {
fill(147, 105, 7);
triangle(x - 10 * scale, y, x, y - 120 * scale, x + 10 * scale, y);
fill(78, 61, 31);
if (hasLeaves) {
ellipse(x, y - 50 * scale, 80 * scale, 30 * scale);
ellipse(x, y - 70 * scale, 100 * scale, 30 * scale);
ellipse(x, y - 90 * scale, 80 * scale, 30 * scale);
ellipse(x, y - 110 * scale, 40 * scale, 30 * scale);
}
}
}
function drawCloud(cloud) {
if (cloud.size == undefined) {
scale = 1;
}
noStroke();
fill(200);
ellipse(cloud.x - 20, cloud.y - 10, 50 * cloud.size, 30 * cloud.size);
fill(220);
ellipse(cloud.x + 20, cloud.y - 20, 70 * cloud.size, 50 * cloud.size);
fill(255);
ellipse(cloud.x, cloud.y, 90 * cloud.size, 40 * cloud.size);
ellipse(cloud.x + 45, cloud.y - 10, 50 * cloud.size, 35 * cloud.size);
}
function drawMountain(x, m_width, m_height, skew) {
noStroke();
fill(60);
triangle(x - m_width, env.floorPos_y, x + skew, m_height, x + m_width, env.floorPos_y);
fill(90);
triangle(x - m_width, env.floorPos_y, x + skew, m_height, x - m_width / 1.5, env.floorPos_y);
fill(170);
triangle(
(4 * x - m_width + 3 * skew) / 4,
(env.floorPos_y + 3 * m_height) / 4,
x + skew,
m_height,
(4 * x + m_width + 3 * skew) / 4,
(env.floorPos_y + 3 * m_height) / 4
);
}
function drawCanyon(canyon) {
noStroke();
let x = canyon.x;
let c_width = canyon.width;
fill(90, 30, 0);
quad(
x - c_width / 2,
env.floorPos_y,
x + c_width / 2,
env.floorPos_y,
x + c_width / 2,
height,
x - c_width / 2,
height
);
fill("blue");
quad(
x - c_width / 20,
env.floorPos_y,
x + c_width / 20,
env.floorPos_y,
x + c_width / 20,
height,
x - c_width / 20,
height
);
fill(30);
quad(
x - c_width / 2,
env.floorPos_y,
x + c_width / 2,
env.floorPos_y,
x + c_width / 10,
env.floorPos_y + env.floorPos_y / 40,
x - c_width / 10,
env.floorPos_y + env.floorPos_y / 40
);
}
function drawCollectable(collectable) {
let x = collectable.x;
let y = collectable.y;
let size = collectable.size;
let worth = collectable.worth;
strokeWeight(0.5);
fill(253, 195, 52);
ellipse(x, y, 3.5 * size, 3.5 * size);
fill(171);
ellipse(x, y, 3 * size, 3 * size);
fill(253, 195, 52);
ellipse(x, y, 2.5 * size, 2.5 * size);
fill(200, 150, 30);
textSize(size * 1.5);
textAlign(CENTER, CENTER);
fill(0);
text(worth, x, y);
textSize(12);
textAlign(LEFT, TOP);
}
function drawSky() {
background(env.skyColor);
}
function drawAudioSymbol() {
push();
fill(0);
strokeWeight(4);
triangle(
width - text_vert_unit * 5,
text_hor_unit * 7,
width - text_vert_unit * 4,
text_hor_unit * 5,
width - text_vert_unit * 4,
text_hor_unit * 9
);
if (env.isSoundEnabled) {
arc(width - text_vert_unit * 3, text_hor_unit * 7, text_hor_unit, text_vert_unit * 1.5, (3 * PI) / 2, PI / 2);
arc(width - text_vert_unit * 3.5, text_hor_unit * 7, text_hor_unit, text_vert_unit, (3 * PI) / 2, PI / 2);
}
textSize(text_vert_unit / 2);
strokeWeight(0);
pop();
}
function drawMusicSymbol() {
push();
fill(0);
strokeWeight(4);
rectMode(CORNERS);
if (env.isMusicEnabled) {
line(width - text_vert_unit * 4.5, text_hor_unit * 15, width - text_vert_unit * 4.5, text_hor_unit * 11);
line(width - text_vert_unit * 2.5, text_hor_unit * 14, width - text_vert_unit * 2.5, text_hor_unit * 10);
line(width - text_vert_unit * 4.5, text_hor_unit * 11, width - text_vert_unit * 2.5, text_hor_unit * 10);
}
ellipse(width - text_vert_unit * 4.5, text_hor_unit * 15, text_vert_unit / 2);
ellipse(width - text_vert_unit * 2.5, text_hor_unit * 14, text_vert_unit / 2);
pop();
}
function playSound(sound, volume, isMusicEnabled) {
if (volume == undefined) volume = 1;
if (env.isSoundEnabled) {
sound.play(0, 1, volume);
} else {
sound.stop();
}
}
function playMusic(sound, volume) {
if (volume == undefined) volume = 1;
if (env.isMusicEnabled) {
sound.play(0, 1, volume);
} else {
sound.stop();
}
}
function drawMountains() {
push();
translate(translateration * 0.3, 0);
for (i = 0; i < mountains_x.length; i++) {
drawMountain(
mountains_x[i],
100 + 200 * random_values[i + 121],
400 - 50 * random_values[i + 211],
50 * random_values[i + 8]
);
}
pop();
}
function drawClouds(areMoving) {
push();
translate(translateration * 0.6, 0);
for (i = 0; i < clouds.length; i++) {
push();
if (areMoving) translate(frameCount * random_values[i], 0);
drawCloud(clouds[i]);
pop();
}
pop();
}
function drawCanyons() {
push();
translate(translateration, 0);
for (i = 0; i < canyons.length; i++) {
drawCanyon(canyons[i]);
fill(200, 150, 0);
}
pop();
}
function drawTrees() {
push();
translate(translateration, 0);
for (i = 0; i < trees_x.length; i++) {
drawTree(trees_x[i], env.floorPos_y, 0.5 + random_values[i], Math.round(trees_x[i]) % 2);
}
pop();
}
function drawCollectables() {
push();
translate(translateration, 0);
for (i = 0; i < collectables.length; i++) {
drawCollectable(collectables[i]);
}
pop();
}
function drawPlatforms() {
push();
translate(translateration, 0);
for (i = 0; i < platforms.length; i++) {
platforms[i].drawPlatform();
}
pop();
}
function manageEnemies() {
push();
translate(0, 0);
for (i = 0; i < enemies.length; i++) {
enemies[i].drawEnemy();
for (j = 0; j < gameChar.projectiles.length; j++) {
if (enemies[i].collidesWith(gameChar.projectiles[j])) {
enemies.splice(i, 1);
gameChar.projectiles.splice(j, 1);
i = 0;
j = 0;
playSound(sfx.enemy);
}
}
if (enemies[i].collidesWith(gameChar) && env.state == "LEVEL") {
background(255 * random(), 255 * random(), 255 * random());
playSound(sfx.enemy, enemies[i].damage / 10);
gameChar.state.isPlummeting = true;
}
}
pop();
}
function drawInterface() {
push();
translate(-translateration, 0);
fill("black");
rect(0, 0, width, text_vert_unit * 2);
stroke("red");
line(0, text_vert_unit * 2, width, text_vert_unit * 2);
fill("red");
textFont("Courier New");
textSize(text_vert_unit);
text("Score: " + gameChar.score, width - text_vert_unit * 7, text_hor_unit * 2);
text("Lives: " + gameChar.lives, width - text_vert_unit * 14, text_hor_unit * 2);
// Debug things
drawAudioSymbol();
drawMusicSymbol();
if (isDebug) debug();
pop();
}
function shootProjectile() {
playSound(sfx.coin, 0, 1, 0.5);
let projectile = { color: "red", x: gameChar.x, y: gameChar.y - 45, direction: 0 };
if (gameChar.state.isLeft) {
projectile.direction = -15;
gameChar.projectiles.push(projectile);
} else {
projectile.direction = 15;
gameChar.projectiles.push(projectile);
}
}
function updateProjectiles() {
for (i = 0; i < gameChar.projectiles.length; i++) {
push();
stroke(gameChar.projectiles[i].color);
strokeWeight(4);
line(
gameChar.projectiles[i].x,
gameChar.projectiles[i].y,
gameChar.projectiles[i].x - 30,
gameChar.projectiles[i].y
);
pop();
gameChar.projectiles[i].x += gameChar.projectiles[i].direction;
if (gameChar.projectiles[i].x > width - translateration || gameChar.projectiles[i].x < 0 - translateration) {
gameChar.projectiles.splice(i, 1);
}
}
}
function manageMovement() {
if (gameChar.state.isPlummeting) {
gameChar.y += gameChar.speed / 2;
gameChar.size *= 0.98;
gameChar.speed *= 0.96;
if (gameChar.size < 0.2) restart(1);
} else if (!(gameChar.state.isJumping || gameChar.state.isFalling)) {
// Checking if the character is beyond the platform, then making them fall.
if (memorized_platform != undefined && !memorized_platform.isInXBorders(gameChar)) {
gameChar.state.isFalling = true;
memorized_platform = undefined;
} else {
// Falling into canyons
if (isOverCanyon(gameChar) && memorized_platform == undefined) {
gameChar.state.isPlummeting = true;
if (!plummetingSoundNotPlayedBefore) {
playSound(sfx.plummeting);
plummetingSoundNotPlayedBefore = true;
}
}
if (gameChar.state.isAscending && gameChar.y > floorPos_y) {
gameChar_y -= gameChar.speed;
}
if (gameChar.state.isDescending && gameChar_y < height) {
gameChar_y += gameChar.speed;
}
if (gameChar.state.isLeft) {
gameChar.x -= gameChar.speed;
}
if (gameChar.state.isRight) {
gameChar.x += gameChar.speed;
}
}
} else if (gameChar.state.isJumping) {
// Forget the platform as it is irrelevant when we gain height.
memorized_platform = undefined;
gameChar.y += gameChar.vert_speed;
gameChar.vert_speed += gameChar.gravity;
if (gameChar.vert_speed > 0) {
gameChar.state.isJumping = false;
gameChar.state.isFalling = true;
playSound(sfx.falling);
}
if (gameChar.state.isLeft) {
gameChar.x -= gameChar.speed;
}
if (gameChar.state.isRight) {
gameChar.x += gameChar.speed;
}
} else if (gameChar.state.isFalling) {
// Checking is a platform under the player, and therefore, if it is, save it and check the next frame did the character
// go under the platform - if so, end them on the platform.
for (i = 0; i < platforms.length; i++) {
if (platforms[i].isUnder(gameChar)) memorized_platform = platforms[i];
}
if (memorized_platform != undefined && memorized_platform.isOver(gameChar)) {
gameChar.y = memorized_platform.y;
gameChar.state.isJumping = false;
gameChar.state.isFalling = false;
} else {
gameChar.y += gameChar.vert_speed;
gameChar.vert_speed += gameChar.gravity;
if (gameChar.y >= env.floorPos_y + 50) {
gameChar.y = env.floorPos_y + 50;
gameChar.state.isJumping = false;
gameChar.state.isFalling = false;
}
if (gameChar.state.isLeft) {
gameChar.x -= gameChar.speed;
}
if (gameChar.state.isRight) {
gameChar.x += gameChar.speed;
}
}
}
// Borders of the world check
if (gameChar.x <= 50) {
gameChar.x = 50;
}
}
// Fun fact - the following function is made using ChatGPT!
function drawIsogonalBrickWall(x, y, size, hSkew, vSkew) {
push();
translate(translateration, 0);
// Set the fill color of the bricks to a redish
fill(150, 100, 110);
// Calculate the width and height of each brick
let brickWidth = size / 5;
let brickHeight = size / 10;
// Draw the bricks in a 5x10 grid, with a small gap between each brick
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 10; j++) {
let xPos = x + i * (brickWidth + 1) + j * hSkew;
let yPos = y + j * (brickHeight + 1) + i * vSkew;
rect(xPos, yPos, brickWidth, brickHeight);
}
}
pop();
}
function checkCollectable(whoCollidesWith) {
for (i = 0; i < collectables.length; i++) {
if (
Math.abs(whoCollidesWith.x - collectables[i].x) < collectables[i].size * 2 &&
Math.abs(whoCollidesWith.y - collectables[i].y) < collectables[i].size * 2
) {
whoCollidesWith.score += collectables[i].worth;
collectables.splice(i, 1);
playSound(sfx.coin);
}
}
}
function isOverCanyon(who) {
for (i = 0; i < canyons.length; i++) {
if (Math.abs(who.x - canyons[i].x) < 43 + 25 * random_values[i + 124]) {
return true;
}
}
return false;
}
function scrolling(centerOnWho) {
if (centerOnWho.x > width - width / 2.5 - translateration) {
translateration -= centerOnWho.speed;
} else if (centerOnWho.x < width / 2.5 - translateration) {
translateration += centerOnWho.speed;
}
}
function checkFlagpole(withWho) {
if (withWho.x > env.end_x) {
flagpole.isReached = true;
env.state = "WIN";
withWho.isWinner = true;
playSound(sfx.levelcomplete);
}
}
function restart(decreaseLives) {
if (env.state != "LEVEL") window.location.reload(); // Reloads the web page.
gameChar.state.isPlummeting = false;
gameChar.lives -= decreaseLives;
if (gameChar.lives < 1) {
env.state = "GAMEOVER";
playSound(sfx.gameover);
return;
}
gameChar.x = width / 2;
gameChar.y = env.floorPos_y + 50;
gameChar.resetValues();
translateration = -gameChar.x;
plummetingSoundNotPlayedBefore = false;
}
function getSpriteNumber() {
if (gameChar.state.isJumping && gameChar.state.isLeft) return 5;
else if (gameChar.state.isJumping && gameChar.state.isRight) return 6;
else if (gameChar.state.isJumping) return 2;
else if (gameChar.state.isFalling && gameChar.state.isLeft) return 5;
else if (gameChar.state.isFalling && gameChar.state.isRight) return 6;
else if (gameChar.state.isFalling) return 2;
else if (gameChar.state.isPlummeting) return 2;
else if (gameChar.state.isLeft) return 4;
else if (gameChar.state.isRight) return 3;
else if (gameChar.state.isDescending) return 1;
else return 1;
}
function debug() {
function _drawGridLines() {
push();
console.log("works");
stroke(255);
strokeWeight(1);
for (i = text_vert_unit; i < width; i += text_vert_unit) {
line(0, i, width, i);
}
for (j = text_hor_unit; j < height * 2; j += text_hor_unit) {
line(j, 0, j, height);
}
pop();
}
this.drawGrid = false;
if (gameChar.state.isJumping) text("JUMPING", text_vert_unit * 6, text_hor_unit * 2);
if (gameChar.state.isFalling) text("FALLING", text_vert_unit * 6, text_hor_unit * 2);
if (gameChar.state.isPlummeting) text("PLUMMETING", text_vert_unit * 11, text_hor_unit * 2);
if (frameCount % 2 == 0) {
fps = Math.round(frameRate() * 10) / 10;
if (fps_recent_values.length < 200) fps_recent_values.push(fps);
else fps_recent_values.splice(0, 1);
fps_recent_values.push(fps);
}
fill("red");
text(fps, text_vert_unit, text_hor_unit * 2);
stroke("black");
beginShape(LINES);
for (i = 1; i < fps_recent_values.length; i++) {
vertex((text_vert_unit + i) * 1.5, (text_hor_unit * 6 - fps_recent_values[i]) * 2);
}
endShape();
text(gameChar.x, text_vert_unit * 18, text_hor_unit * 2);
if (draw_grid) drawGridLines();
}
function createEnemies() {
enemies[0] = new Enemy(1100, 600, random(10, 50), random(20, 50), random(10, 30));
for (let i = 1; i < amounts.enemies; i++) {
let elevation;
if (random_values[i] <= 1 / 3) elevation = 350;
else if (random_values[i] > 1 / 3 && random_values <= 2 / 3) elevation = 300;
else elevation = 400;
enemies[i] = new Enemy(
enemies[i - 1].x + 200 + 250 * random(),
elevation,
random(10, 50),
random(20, 50),
random(10, 30)
);
}
}
function createTrees() {}
function setup() {
cnv = createCanvas(1024 + 256, 512 + 256);
env.skyColor = color(142, 152, 135);
gameChar = new Entity();
gameChar.x = width / 2;
gameChar.y = env.floorPos_y + 50;
platforms[0] = new Platform(500, 600, 350);
for (i = 1; i < amounts.platforms; i++) {
let left_x = platforms[i - 1].right_x + 100 + 300 * random();
let right_x = left_x + 50 + 100 * random();
if (left_x > env.end_x && right_x > env.end_x) break;
platforms[i] = new Platform(left_x, right_x, 300 + 100 * random());
}
createEnemies();
trees_x = [50];
for (i = 1; i <= amounts.tree; i++) {
trees_x[i] = trees_x[i - 1] + 20 + 50 * random_values[i];
}
clouds[0] = new Cloud(-1000, 60, 1);
for (i = 1; i <= amounts.cloud; i++) {
clouds[i] = new Cloud(
clouds[i - 1].x + 20 + 50 * random_values[i],
(env.floorPos_y / 1.9) * random_values[i + 60],
0.5 + random_values[i + 100]
);
}
mountains_x = [50];
for (i = 1; i <= amounts.mountain; i++) {
mountains_x[i] = mountains_x[i - 1] + 20 + 400 * random_values[i + 100];
}
canyons[0] = new Canyon(gameChar.x + 200, 100 + 50 * random_values[124]);
for (i = 1; i <= amounts.canyon; i++) {
let x = canyons[i - 1].x + 200 + 600 * random_values[i + 100];
if (x > flagpole.x - 250) break;
canyons[i] = new Canyon(x, 100 + 50 * random_values[i + 124]);
}
collectables[0] = new Collectable(gameChar.x + 350, env.floorPos_y + 50, 20, 10);
for (i = 1; i <= amounts.collectable; i++) {
let worth;
if (random_values[i] <= 1 / 3) worth = 15;
else if (random_values[i] > 1 / 3 && random_values <= 2 / 3) worth = 20;
else worth = 25;
let x = collectables[i - 1].x + 70 + 400 * random_values[i + 213];
if (x > flagpole.x) break;
collectables[i] = new Collectable(x, env.floorPos_y + 50, worth, worth);
}
// Pruning over-canyon trees. Literally.
for (i = 0; i < trees_x.length; i++) {
for (j = 0; j < canyons.length; j++) {
if (
trees_x[i] > canyons[j].x - (40 + 50 * random_values[j + 124]) &&
trees_x[i] < canyons[j].x + (40 + 50 * random_values[j + 124])
) {
trees_x[i] = undefined;
}
}
}
// Doing the same with collectables, for honesty.
for (i = 0; i < collectables.length; i++) {
for (j = 0; j < canyons.length; j++) {
if (
collectables[i].x > canyons[j].x - (40 + 50 * random_values[j + 124]) &&
collectables[i].x < canyons[j].x + (40 + 50 * random_values[j + 124])
) {
collectables[i].x = NaN;
}
}
}
}
function draw() {
text_hor_unit = height / 40;
text_vert_unit = width / 40;
drawSky(); //fill the sky
fill(env.groundColor);
rect(0, env.floorPos_y, width, height);
drawMountains();
// Also updates clouds movement using translate(). May be disabled.
drawClouds(true);
drawTrees();
drawCanyons();
drawFlagpole();
drawIsogonalBrickWall(-89, env.floorPos_y - 89, 200, 0, -30);
drawCollectables();
drawPlatforms();
switch (env.state) {
case "INTRO": {
push();
textAlign(CENTER);
textSize(text_vert_unit * 3);
strokeWeight(10);
textFont("Courier");
textStyle(BOLD);
fill(0);
text("The Dead Magic Run", width / 2, height / 2);
textSize(text_vert_unit);
text("Press any button to attempt a run", width / 2, height / 2 + text_vert_unit * 2);
pop();
break;
}
case "INSTRUCTIONS": {
push();
textAlign(LEFT);
textSize(text_vert_unit);
strokeWeight(3);
textFont("Courier");
rectMode(CORNERS);
fill(0, 100);
rect(text_hor_unit, text_vert_unit * 3, text_hor_unit * 55, text_vert_unit * 11);
fill(255);
text("The goal is to survive and reach the right end.", text_hor_unit, text_vert_unit * 4);
text("Press WASD to move, Space to jump", text_hor_unit, text_vert_unit * 5);
text("E or mouse click to shoot.", text_hor_unit, text_vert_unit * 6);
text("Press P to enable and disable sounds,", text_hor_unit, text_vert_unit * 7);
text("and O letter to enable and disable music.", text_hor_unit, text_vert_unit * 8);
text("Press Space to continue and begin the run. Good luck.", text_hor_unit, text_vert_unit * 10);
pop();
break;
}
case "LEVEL": {
// Update scrolling based on the character movement
scrolling(gameChar);
translate(translateration, 0); // Translating everything else to prevent next elements from scrolling too.
// Collectables collision
checkCollectable(gameChar);
checkFlagpole(gameChar);
manageMovement();
// the sprite state machine
var sprite = getSpriteNumber();
drawGameChar(
gameChar.x,
gameChar.y,
sprite,
gameChar.size,
true,
"red",
(hand = Math.floor(frameCount / 12) % 4)
);
manageEnemies();
updateProjectiles();
playMusic(mus.level);
break;
}
case "WIN": {
manageEnemies();
push();
image(win_img, width / 2 - win_img.width / 2 + 10, height / 2 - win_img.height / 2);
fill("yellow");
textSize(text_vert_unit * 1.5);
textAlign(CENTER);
strokeWeight(2);
textFont("Courier");
text("Level complete. Press R to replay.", width / 2, height / 2 - win_img.height / 2);
translate(translateration, frameCount);
pop();
scrolling(flagpole); // fix the viewpoint on the flagpole
translate(translateration, 0); // Translating everything else to prevent next elements from scrolling too.
// Forcing to run right
gameChar.state.isRight = true;
gameChar.state.isLeft = false;
// the sprite state machine
var sprite = getSpriteNumber();
manageMovement();
drawGameChar(gameChar.x, gameChar.y, sprite, gameChar.size, true, "red", Math.floor(frameCount / 12) % 4);
playMusic(mus.win);
break;
}
case "GAMEOVER": {
manageEnemies();
push();
image(gameover_img, width / 2 - 256 / 2, height / 2 - 256 / 2);
fill("black");
textSize(text_vert_unit * 1.5);
textAlign(CENTER);
strokeWeight(2);
textFont("Courier");
text("Gamover. Press R to replay.", width / 2, height / 2 - 256 / 2);
pop();
translate(translateration, 0);
playMusic(mus.gameover);
break;
}
default: {
push();
textSize(50);
fill("red");
text("Game state error!", width / 2, height / 2);
pop();
break;
}
}
drawInterface();
}
function playMusic(requestedMelody) {
// spaghetti code.
if (requestedMelody != mus.level) mus.level.stop();
if (requestedMelody != mus.win) mus.win.stop();
if (requestedMelody != mus.gameover) mus.gameover.stop();
if (!requestedMelody.isPlaying() && env.isMusicEnabled) {
requestedMelody.play(0, 1, 0.3);
} else if (!env.isMusicEnabled) requestedMelody.stop();
}
function mouseClicked() {
shootProjectile();
}
function keyPressed() {
if (env.state == "INTRO") {
env.state = "INSTRUCTIONS";
} else if (key == " " && env.state == "INSTRUCTIONS") {
env.state = "LEVEL";
}
if (keyCode == 32 && !(gameChar.state.isJumping || gameChar.state.isFalling)) {
gameChar.state.isJumping = true;
gameChar.prejump_y = gameChar.y;
gameChar.vert_speed = -30;
playSound(sfx.jumping);
}
if (key == "E" && env.state == "LEVEL") {
shootProjectile();
}
if (key == "A") {
gameChar.state.isLeft = true;
}
if (key == "D") {
gameChar.state.isRight = true;
}
if (key == "R" && (env.state == "WIN" || env.state == "GAMEOVER")) {
restart();
}
if (key == "P" && !env.isSoundEnabled) {
env.isSoundEnabled = true;
} else if (key == "P" && env.isSoundEnabled) {
env.isSoundEnabled = false;
}
if (key == "O" && !env.isMusicEnabled) {
env.isMusicEnabled = true;
} else if (key == "O" && env.isMusicEnabled) {
env.isMusicEnabled = false;
}
if (key == "G" && isDebug) draw_grid = true;
console.log("keyPressed: " + key);
console.log("keyPressed: " + keyCode);
return false;
}
function keyReleased() {
if (key == "M") {
if (isDebug) isDebug = false;
else isDebug = true;
}
if (key == "A") {
gameChar.state.isLeft = false;
}
if (key == "D") {
gameChar.state.isRight = false;
}
if (key == "G" && isDebug) draw_grid = false;
console.log("keyReleased: " + key);
console.log("keyReleased: " + keyCode);
}