1196 lines
38 KiB
JavaScript
1196 lines
38 KiB
JavaScript
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);
|
|
}
|