dead-wastes/sketch.js

836 lines
25 KiB
JavaScript

var floorPos_y;
var cameraPosX;
var processStop_timer;
var fps_recent_values = [];
var showDebugData = false;
var gameChar;
var gravity;
var game_score;
var finish_position_x;
var trees_x = [];
var clouds = [];
var mountains = [];
var collectables = [];
var canyons = [];
var flagpole;
var text_size;
// Variables to set colors. Set in setup()
var palette;
function setup() {
createCanvas(1024, 576);
palette = {
body_color: color("white"),
head_color: color("darkgreen"),
sky_color: color("#8E9887"),
ground_color0: color("#684622"),
ground_color1: color("#734E26"),
ground_color2: color("#7F562A"),
canyon_river_color: color("#56C525"),
canyon_river_wave_color: color("#4BAD21"),
canyon_color: color("#7B672A"),
pine_leaves_color: color("#3F4834"),
maple_leaves_color: color("#4E3D1F"),
pine_stem: color("#644D0D"),
maple_stem: color("#936907"),
mountain: color("#5a5a5a"),
mountain_shadow: color("#3c3c3c"),
cloud0: color(200),
cloud1: color(220),
cloud2: color(255),
coin_outer: color("#FDC334"),
coin_middle: color("#ababab"),
coin_inner: color("#FDC334"),
heart_color: color("darkred"),
enemy_head_color: color("red"),
enemy_body_color: color("red"),
};
startGame((full_start = true), (update_objects = true));
}
function startGame(full_start, update_objects) {
floorPos_y = 432;
cameraPosX = 0;
if (full_start) {
gameChar = {
x: width / 2,
y: floorPos_y + (height - floorPos_y) / 6,
x_step: 5,
y_step: 8,
scale: 1,
sprite: 1,
speed: 5,
baseLives: 3,
curLives: 3,
isRight: false,
isLeft: false,
isFalling: false,
isJumping: false,
isPlummeting: false,
possibleGroundPosY: [
floorPos_y + (height - floorPos_y) / 6,
floorPos_y + (3 * (height - floorPos_y)) / 6,
floorPos_y + (5 * (height - floorPos_y)) / 6,
],
curGroundPosYIndex: 0,
getCurGroundPosY() {
return this.possibleGroundPosY[this.curGroundPosYIndex];
},
goUp() {
memorizedIndex = this.curGroundPosYIndex;
this.curGroundPosYIndex = max(0, this.curGroundPosYIndex - 1);
if (memorizedIndex != this.curGroundPosYIndex) {
gameChar.y -= (height - floorPos_y) / 3;
gameChar.scale -= 0.1;
}
},
goDown() {
memorizedIndex = this.curGroundPosYIndex;
this.curGroundPosYIndex = min(
this.possibleGroundPosY.length - 1,
this.curGroundPosYIndex + 1
);
if (memorizedIndex != this.curGroundPosYIndex) {
gameChar.y += (height - floorPos_y) / 3;
gameChar.scale += 0.1;
}
},
};
} else {
gameChar.x = width / 2;
gameChar.y = floorPos_y + (height - floorPos_y) / 6;
gameChar.scale = 1;
gameChar.curGroundPosYIndex = 0;
gameChar.isPlummeting = false;
gameChar.isFalling = false;
gameChar.isJumping = false;
gameChar.isLeft = false;
gameChar.isRight = false;
}
gravity = 1;
game_score = 0;
finish_position_x = 2000;
if (update_objects || full_start) {
// Assigning values of trees, clouds, mountains, canyons, collectables.
for (i = 0; i < 150; i++) {
trees_x[i] = random(-100, 10000);
}
for (i = 0; i < 100; i++) {
clouds[i] = {
x: random(-100, 10000),
y: random(0, floorPos_y / 2),
size: random(0.4, 2),
};
}
// Latter mountains.
for (i = 0; i < finish_position_x / 90; i++) {
mountains[i] = {
x: random(-200, finish_position_x + 200),
width: random(60, 150),
height: random(floorPos_y - 50, floorPos_y - 150),
skew: random(-20, 20),
};
// To prevent spiky mountains which are quite ugly
mountains[i].height = max(mountains[i].width, mountains[i].height);
}
// Start mountains.
for (i = 0; i < 6; i++) {
mountains[i] = {
x: width / (i + 1.5),
width: width / (i + random(2, 4)),
height: height / (2 + 0.8 * i),
skew: (random(-10, 20) * i) / 2,
};
}
for (i = 0; i < 100; i++) {
if (i == 0) {
canyons[0] = {
x: 700,
width: 50,
points: [],
};
} else {
if (canyons[i - 1].x + 600 > finish_position_x) break;
canyons[i] = {
x: canyons[i - 1].x + 300 + 200 * random(0.5, 1),
width: 50 + 30 * random(),
points: [],
};
}
}
for (i = 0; i < 100; i++) {
if (i == 0) {
collectables[0] = {
x: 600,
y: gameChar.possibleGroundPosY[0],
size: 75,
isFound: false,
};
} else {
if (collectables[i - 1].x + 200 > finish_position_x) {
break;
}
collectables[i] = {
x: collectables[i - 1].x + 50 + 100 * random(0.5, 1),
y: gameChar.possibleGroundPosY[
floor(random(0, gameChar.possibleGroundPosY.length))
],
size: 75,
isFound: false,
};
}
}
}
flagpole = {
x: finish_position_x,
isReached: false,
size_vert: 4,
size_hor: 7,
cell_size: 20,
cell_size_v: 20,
cell_size_h: 20,
};
text_size = width / 20;
textSize(text_size);
}
function draw() {
// -------- SKY ----------------
background(palette.sky_color);
// -------- GROUND -------------
drawGround();
push(); // Scrolling
fill(0);
translate(-cameraPosX, 0); // Scrolling
if (gameChar.x < finish_position_x) cameraPosX = gameChar.x - width / 2; // Scrolling
// -------- CLOUDS --------------
drawClouds();
// -------- MOUNTAINS -----------
drawMountains();
// -------- CANYONS -------------
for (i = 0; i < canyons.length; i++) {
canyon = canyons[i];
drawCanyon(canyon);
if (!gameChar.isFalling && !gameChar.isJumping) checkCanyon(canyon);
}
// -------- TREES ---------------
drawTrees();
// -------- COLLECTABLES --------
for (i = 0; i < collectables.length; i++) {
collectable = collectables[i];
if (!collectable.isFound) drawCollectable(collectable);
checkCollectable(collectable);
}
// -------- FLAGPOLE ------------
renderFlagpole();
if (!flagpole.isReached) checkFlagpole();
// -------- GAME CHARACTER ------
{
// Behavior
if (!gameChar.isPlummeting) {
if (gameChar.isLeft && !gameChar.isRight) {
gameChar.x -= gameChar.speed;
} else if (gameChar.isRight && !gameChar.isLeft) {
gameChar.x += gameChar.speed;
}
if (gameChar.isJumping) {
gameChar.y -= gameChar.jumpingStrength;
gameChar.jumpingStrength -= gravity;
if (gameChar.jumpingStrength <= 0) {
gameChar.isFalling = true;
gameChar.isJumping = false;
}
} else if (gameChar.isFalling) {
gameChar.y -= gameChar.jumpingStrength;
gameChar.jumpingStrength -= gravity;
if (gameChar.y >= gameChar.getCurGroundPosY()) {
gameChar.y = gameChar.getCurGroundPosY();
gameChar.isFalling = false;
}
} else {
}
}
checkPlayerDie();
// Setting sprite
{
if (gameChar.isPlummeting) {
gameChar.sprite = 2;
} else if (gameChar.isFalling) {
gameChar.sprite = 2;
if (gameChar.isLeft && !gameChar.isRight) gameChar.sprite = 5;
else if (gameChar.isRight && !gameChar.isLeft)
gameChar.sprite = 6;
} else {
if (gameChar.isLeft && !gameChar.isRight) gameChar.sprite = 4;
else if (gameChar.isRight && !gameChar.isLeft)
gameChar.sprite = 3;
else gameChar.sprite = 1;
}
}
// Depicting plummeting
if (gameChar.isPlummeting) {
gameChar.y += 3;
}
// Drawing a sprite
drawGameCharacter();
}
pop(); // Scrolling
// -------- INTERFACE -----------
push();
fill(0);
text("Score: " + game_score, 12, text_size);
drawLives();
if (gameChar.curLives < 1)
text("Game over. Press space to continue...", 0, height / 2);
else if (flagpole.isReached)
text("Level complete. Press space to continue...", 0, height / 2);
if (showDebugData) {
text(gameChar.curGroundPosYIndex, 99, 99);
text(gameChar.getCurGroundPosY(), 188, 99);
text(gameChar.sprite, 277, 99);
drawFps();
}
pop();
}
function drawGameCharacter() {
strokeWeight(1);
stroke(1);
gameChar.x_step *= gameChar.scale;
gameChar.y_step *= gameChar.scale;
function _drawBody(jumping = false) {
fill(palette.body_color);
if (jumping) {
triangle(
gameChar.x - gameChar.x_step * 2,
gameChar.y - gameChar.y_step * 1.5,
gameChar.x,
gameChar.y - gameChar.y_step * 4,
gameChar.x + gameChar.x_step * 2,
gameChar.y - gameChar.y_step * 1.5
);
} else {
triangle(
gameChar.x - gameChar.x_step * 2,
gameChar.y,
gameChar.x,
gameChar.y - gameChar.y_step * 4,
gameChar.x + gameChar.x_step * 2,
gameChar.y
);
}
}
function _drawHead() {
fill(palette.head_color);
ellipse(
gameChar.x,
gameChar.y - gameChar.y_step * 5,
gameChar.x_step * 6,
gameChar.y_step * 4
);
}
function _drawEyes(draw_left, draw_right, eyes_height) {
fill(0);
if (draw_left) {
ellipse(
gameChar.x - gameChar.x_step * 1.2,
gameChar.y - gameChar.y_step * eyes_height,
gameChar.x_step / 1.5,
gameChar.y_step / 2
);
}
if (draw_right) {
ellipse(
gameChar.x + gameChar.x_step * 1.2,
gameChar.y - gameChar.y_step * eyes_height,
gameChar.x_step / 1.5,
gameChar.y_step / 2
);
}
}
// Standing, facing frontwards
if (gameChar.sprite == 1) {
// Body
_drawBody();
// Head
_drawHead();
// Eyes
_drawEyes(true, true, 4.2);
}
// Jumping, facing forwards
else if (gameChar.sprite == 2) {
// Body
_drawBody(true);
// Hands. (Hands!)
line(
gameChar.x - gameChar.x_step * 1,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x - gameChar.x_step * 3.2,
gameChar.y - gameChar.y_step * 3.2
);
line(
gameChar.x + gameChar.x_step * 1,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x + gameChar.x_step * 3.2,
gameChar.y - gameChar.y_step * 3.2
);
// Head
_drawHead();
// Eyes
_drawEyes(true, true, 3.8);
}
// Walking right
else if (gameChar.sprite == 3) {
// Body
_drawBody();
// Hand. (Hand!)
line(
gameChar.x,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x + gameChar.x_step * 0.8,
gameChar.y - gameChar.y_step * 1
);
// Head
_drawHead();
// Eyes
_drawEyes(false, true, 4.2);
}
// Walking left
else if (gameChar.sprite == 4) {
// Body
_drawBody();
// Hand. (Hand!)
line(
gameChar.x,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x - gameChar.x_step * 0.8,
gameChar.y - gameChar.y_step * 1
);
// Head
_drawHead();
// Eyess
_drawEyes(true, false, 4.2);
}
// Jumping left
else if (gameChar.sprite == 5) {
// Body
_drawBody(true);
// Hands. (Hands!)
line(
gameChar.x - gameChar.x_step * 1,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x - gameChar.x_step * 3.2,
gameChar.y - gameChar.y_step * 3.2
);
line(
gameChar.x + gameChar.x_step * 1,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x + gameChar.x_step * 3.2,
gameChar.y - gameChar.y_step * 3.2
);
// Head
_drawHead();
// Eyes
_drawEyes(true, false, 3.8);
}
// Jumping right
else if (gameChar.sprite == 6) {
// Body
_drawBody(true);
// Hands. (Hands!)
line(
gameChar.x - gameChar.x_step * 1,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x - gameChar.x_step * 3.2,
gameChar.y - gameChar.y_step * 3.2
);
line(
gameChar.x + gameChar.x_step * 1,
gameChar.y - gameChar.y_step * 2.5,
gameChar.x + gameChar.x_step * 3.2,
gameChar.y - gameChar.y_step * 3.2
);
// Head
_drawHead();
// Eyes
_drawEyes(false, true, 3.8);
} else {
text("Bad sprite number!", 10, 10);
console.error("Bad gameChar sprite number: " + gameChar.sprite);
}
gameChar.x_step /= gameChar.scale;
gameChar.y_step /= gameChar.scale;
}
function checkCanyon(t_canyon) {
if (
gameChar.x > canyon.x - canyon.width / 2 &&
gameChar.x < canyon.x + canyon.width / 2
) {
gameChar.isPlummeting = true;
}
}
function checkCollectable(t_collectable) {
if (
!t_collectable.isFound &&
collectable.y == gameChar.getCurGroundPosY() &&
dist(
t_collectable.x,
t_collectable.y,
gameChar.x,
gameChar.y - gameChar.x_step * 4
) <
t_collectable.size / 2
) {
t_collectable.isFound = true;
game_score++;
}
}
function drawGround() {
push();
noStroke();
fill(palette.ground_color0);
rect(0, floorPos_y, width, height - floorPos_y);
fill(palette.ground_color1);
rect(
0,
floorPos_y + (2 * (height - floorPos_y)) / 6,
width,
floorPos_y + (2 * (height - floorPos_y)) / 6
);
fill(palette.ground_color2);
rect(
0,
floorPos_y + (4 * (height - floorPos_y)) / 6,
width,
floorPos_y + (4 * (height - floorPos_y)) / 6
);
stroke(1);
translate(-cameraPosX, 0);
for (i = -width/2; i < finish_position_x + width/2; i += 20) {
line(
i,
floorPos_y + (2 * (height - floorPos_y)) / 6,
i + 1,
floorPos_y + (2 * (height - floorPos_y)) / 6
);
line(
i,
floorPos_y + (4 * (height - floorPos_y)) / 6,
i + 1,
floorPos_y + (4 * (height - floorPos_y)) / 6
);
}
pop();
}
function drawCanyon(t_canyon) {
push();
fill(palette.canyon_river_color);
rect(t_canyon.x - t_canyon.width / 2, floorPos_y, t_canyon.width, height);
// Waves animation.
if (frameCount % 2 == 0) {
PointX = random(
t_canyon.x - t_canyon.width / 2 + 5,
t_canyon.x + t_canyon.width / 2 - 5
);
PointY = random(floorPos_y, height);
if (t_canyon.points.length > 2) {
t_canyon.points.shift();
}
t_canyon.points.push([PointX, PointY, random(20, 100)]);
}
stroke(palette.canyon_river_wave_color);
strokeWeight(5);
for (k = 0; k < t_canyon.points.length; k++) {
line(
t_canyon.points[k][0],
t_canyon.points[k][1],
t_canyon.points[k][0],
t_canyon.points[k][1] + t_canyon.points[k][2]
);
}
pop();
}
function drawCollectable(t_collectable) {
push();
// animating the coin's jiggle
a = 0.1;
b = 3;
c = 0.2;
d = 2; // a - vert. intensity, c - hor. intensity, b - vert. speed, d - hor. speed
t_collectable.y -= sin(frameCount * a) * b;
t_collectable.x += sin(frameCount * c) * d;
stroke(0.2);
fill(palette.coin_outer);
ellipse(t_collectable.x, t_collectable.y, 0.7 * t_collectable.size);
fill(palette.coin_middle);
ellipse(t_collectable.x, t_collectable.y, 0.6 * t_collectable.size);
fill(palette.coin_inner);
ellipse(t_collectable.x, t_collectable.y, 0.5 * t_collectable.size);
// restoring coin's coordinates to preserve game logic
t_collectable.y += sin(frameCount * a) * b;
t_collectable.x -= sin(frameCount * c) * d;
pop();
}
function drawClouds() {
for (i = 0; i < clouds.length; i++) {
push();
translate((frameCount / clouds[i].y) * 20, 0); // imitating clouds movement, upper ones should go faster
// Adding counter-translating to implement parallax, the feeling of depth
translate(cameraPosX / 1.1, 0);
noStroke();
fill(palette.cloud0);
ellipse(
clouds[i].x - 20,
clouds[i].y - 10,
20 * clouds[i].size,
30 * clouds[i].size
);
fill(palette.cloud1);
ellipse(
clouds[i].x + 20,
clouds[i].y - 20,
70 * clouds[i].size,
50 * clouds[i].size
);
fill(palette.cloud2);
ellipse(
clouds[i].x,
clouds[i].y,
90 * clouds[i].size,
40 * clouds[i].size
);
ellipse(
clouds[i].x + 45,
clouds[i].y - 10,
50 * clouds[i].size,
35 * clouds[i].size
);
pop();
}
}
function drawMountains() {
for (i = 0; i < mountains.length; i++) {
push();
// slowing down translation to add parallax, the feeling of depth
translate(cameraPosX / 2, 0);
noStroke();
fill(palette.mountain_shadow);
triangle(
mountains[i].x - mountains[i].width,
floorPos_y,
mountains[i].x + mountains[i].skew,
mountains[i].height,
mountains[i].x + mountains[i].width,
floorPos_y
);
fill(palette.mountain);
triangle(
mountains[i].x - mountains[i].width,
floorPos_y,
mountains[i].x + mountains[i].skew,
mountains[i].height,
mountains[i].x - mountains[i].width / 1.5,
floorPos_y
);
pop();
}
}
function drawHeart(isEmpty, x, y, size) {
push();
if (isEmpty) {
noFill();
stroke(0);
} else {
fill(palette.heart_color);
stroke(palette.heart_color);
}
triangle(x, y, x + size, y - size, x + size * 2, y);
triangle(x - size * 2, y, x - size, y - size, x, y);
triangle(x, y, x + size, y + size, x - size, y + size);
triangle(x - size * 2, y, x, y + size * 2, x + size * 2, y);
pop();
}
function drawLives() {
push();
stroke(255, 0, 0);
noFill();
var heart_size = 20;
for (i = gameChar.baseLives; i > 0; i--) {
drawHeart(
true,
width - i * heart_size * 4.5,
heart_size * 1.5,
heart_size
);
}
for (i = gameChar.curLives; i > 0; i--) {
drawHeart(
false,
width - i * heart_size * 4.5,
heart_size * 1.5,
heart_size
);
}
pop();
}
function drawTrees() {
for (i = 0; i < trees_x.length; i++) {
// Draw pine in case the integer part of tree
// coordinate is even.
if (Math.trunc(trees_x[i]) % 2 == 0) {
fill(palette.pine_stem);
triangle(
trees_x[i] - 15,
floorPos_y,
trees_x[i],
floorPos_y - 150,
trees_x[i] + 15,
floorPos_y
);
fill(palette.pine_leaves_color);
triangle(
trees_x[i] - 45,
floorPos_y - 45,
trees_x[i],
floorPos_y - 120,
trees_x[i] + 45,
floorPos_y - 45
);
triangle(
trees_x[i] - 45,
floorPos_y - 85,
trees_x[i],
floorPos_y - 180,
trees_x[i] + 45,
floorPos_y - 85
);
}
// Draw maple
else {
fill(palette.maple_stem);
triangle(
trees_x[i] - 10,
floorPos_y,
trees_x[i],
floorPos_y - 120,
trees_x[i] + 10,
floorPos_y
);
fill(palette.maple_leaves_color);
ellipse(trees_x[i], floorPos_y - 50, 80, 30);
ellipse(trees_x[i], floorPos_y - 70, 100, 30);
ellipse(trees_x[i], floorPos_y - 90, 80, 30);
ellipse(trees_x[i], floorPos_y - 110, 40, 30);
}
}
}
function renderFlagpole() {
// NB - This function is implemented a bit differently than how it was requested in Part 6.
// It has no states to switch between - instead, the flag gradually goes into the opposite direction
// as the player crosses it, what is done by manipulating flagpole's parameters.
// This is done for animation purposes.
push();
strokeWeight(0);
fill(0);
// Movement animation, as mentioned in NB.
if (flagpole.isReached && flagpole.cell_size_h > -flagpole.cell_size)
flagpole.cell_size_h -= 0.8;
// the flagpole pole
rect(flagpole.x, floorPos_y / 2, 2, 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,
floorPos_y / 2 + flagpole.cell_size_v * j,
flagpole.cell_size_h,
flagpole.cell_size_v
);
}
}
pop();
}
function checkFlagpole() {
// Considering the linear nature of the game, the flagpole is triggered whenever player is beyond it by x.
// If to imagine a hypothetical jump where the player somehow jumps beyond the pole in one frame,
// this function will trigger anyway.
if (gameChar.x >= finish_position_x) {
flagpole.isReached = true;
}
}
function checkPlayerDie() {
if (gameChar.y >= height) {
if (gameChar.curLives > 1) {
gameChar.curLives--;
startGame();
} else if (gameChar.curLives == 1) {
gameChar.curLives--;
} else {
gameChar.curLives = 0;
}
}
}
function drawFps() {
push();
fps = Math.round(frameRate() * 10) / 10;
if (fps_recent_values.length < 200) fps_recent_values.push(fps);
else fps_recent_values.shift();
fps_recent_values.push(fps);
fill("red");
text(fps, 400, 99);
stroke("black");
beginShape(LINES);
for (i = 1; i < fps_recent_values.length; i++) {
vertex(i, fps_recent_values[i]);
}
endShape();
pop();
}
function keyPressed() {
console.log(frameCount + " pressed " + key + " " + keyCode);
if (gameChar.curLives < 1 || flagpole.isReached) {
if (keyCode == 32 /*Space*/) {
/*Hard resets the game*/
startGame((full_start = true), (update_objects = true));
}
} else if (!gameChar.isPlummeting) {
if (keyCode == 65 /*A*/) gameChar.isLeft = true;
if (keyCode == 68 /*D*/) gameChar.isRight = true;
if (keyCode == 83 /*S*/) gameChar.goDown();
if (keyCode == 87 /*W*/) gameChar.goUp();
// Rewrote jumping routine to make it more natural and be able to support platforms and different player dimensions
if (
keyCode == 32 /*Space*/ &&
!gameChar.isFalling &&
!gameChar.isJumping
) {
gameChar.isJumping = true;
gameChar.jumpingStrength = 15;
}
}
if (keyCode == 77 /*M*/) showDebugData = !showDebugData;
}
function keyReleased() {
console.log(frameCount + " released " + key + " " + keyCode);
if (keyCode == 65 /*A*/) gameChar.isLeft = false;
if (keyCode == 68 /*D*/) gameChar.isRight = false;
}