dead-wastes/sketch.js

1099 lines
35 KiB
JavaScript
Raw Normal View History

2023-03-22 21:06:30 +00:00
var floorPos_y;
var cameraPosX;
2023-03-25 19:39:36 +00:00
var camera_speed;
2023-03-22 21:06:30 +00:00
var processStop_timer;
2023-03-23 14:21:02 +00:00
var fps_recent_values = [];
var death_timer;
2023-03-23 14:21:02 +00:00
var showDebugData = false;
var debug_charTrace = [];
var shadows_enabled = false;
2023-03-22 21:06:30 +00:00
var gameChar;
var gravity;
var game_score;
var finish_position_x;
var trees_x = [];
var clouds = [];
var mountains = [];
var collectables = [];
2023-03-25 19:46:39 +00:00
var rivers = [];
var platforms = [];
var enemies = [];
2023-03-22 21:06:30 +00:00
var flagpole;
2023-03-26 14:58:24 +00:00
function GameCharacter() {
this.x = width / 2;
this.y = floorPos_y + (height - floorPos_y) / 6;
this.x_step = 5;
this.y_step = 8;
this.scale = 1;
this.sprite = 1;
this.speed = 5;
this.baseLives = 3;
this.curLives = 3;
this.baseJumpingStrength = 16;
this.curJumpingStrength = 0;
this.isRight = false;
this.isLeft = false;
this.isFalling = false;
this.isJumping = false;
this.isPlummeting = false;
2023-03-26 16:36:47 +00:00
this.isOnPlatform = false;
2023-03-26 14:58:24 +00:00
this.curGroundIndex = 0;
2023-03-26 16:36:47 +00:00
this.memorizedPlatform;
2023-03-26 14:58:24 +00:00
this._updateSprite = function () {
if (this.isPlummeting) {
this.sprite = 2;
} else if (this.isFalling) {
this.sprite = 2;
if (this.isLeft && !this.isRight) this.sprite = 5;
else if (this.isRight && !this.isLeft) this.sprite = 6;
} else {
if (this.isLeft && !this.isRight) this.sprite = 4;
else if (this.isRight && !this.isLeft) this.sprite = 3;
else this.sprite = 1;
}
};
this.draw = function () {
this._drawBody = function (jumping = false) {
fill(palette.body_color);
if (jumping) {
triangle(
this.x - this.x_step * 2,
this.y - this.y_step * 1.5,
this.x,
this.y - this.y_step * 4,
this.x + this.x_step * 2,
this.y - this.y_step * 1.5,
);
} else {
triangle(
this.x - this.x_step * 2,
this.y,
this.x,
this.y - this.y_step * 4,
this.x + this.x_step * 2,
this.y,
);
}
}
this._drawHead = function () {
2023-03-26 16:36:47 +00:00
fill(palette.ground_colors[this.curGroundIndex]);
2023-03-26 14:58:24 +00:00
ellipse(
this.x,
this.y - this.y_step * 5,
this.x_step * 6,
this.y_step * 4,
);
}
this._drawEyes = function (draw_left, draw_right, eyes_height) {
fill(0);
if (draw_left) {
ellipse(
this.x - this.x_step * 1.2,
this.y - this.y_step * eyes_height,
this.x_step / 1.5,
this.y_step / 2,
);
}
if (draw_right) {
ellipse(
this.x + this.x_step * 1.2,
this.y - this.y_step * eyes_height,
this.x_step / 1.5,
this.y_step / 2,
);
}
}
this._updateSprite();
push();
strokeWeight(1);
stroke(1);
this.x_step *= this.scale;
this.y_step *= this.scale;
// Standing, facing frontwards
if (this.sprite == 1) {
// Body
this._drawBody();
// Head
this._drawHead();
// Eyes
this._drawEyes(true, true, 4.2);
}
// Jumping, facing forwards
else if (this.sprite == 2) {
// Body
this._drawBody(true);
// Hands. (Hands!)
line(
this.x - this.x_step * 1,
this.y - this.y_step * 2.5,
this.x - this.x_step * 3.2,
this.y - this.y_step * 3.2,
);
line(
this.x + this.x_step * 1,
this.y - this.y_step * 2.5,
this.x + this.x_step * 3.2,
this.y - this.y_step * 3.2,
);
// Head
this._drawHead();
// Eyes
this._drawEyes(true, true, 3.8);
}
// Walking right
else if (this.sprite == 3) {
// Body
this._drawBody();
// Hand. (Hand!)
line(
this.x,
this.y - this.y_step * 2.5,
this.x + this.x_step * 0.8,
this.y - this.y_step * 1,
);
// Head
this._drawHead();
// Eyes
this._drawEyes(false, true, 4.2);
}
// Walking left
else if (this.sprite == 4) {
// Body
this. _drawBody();
// Hand. (Hand!)
line(
this.x,
this.y - this.y_step * 2.5,
this.x - this.x_step * 0.8,
this.y - this.y_step * 1,
);
// Head
this._drawHead();
// Eyess
this._drawEyes(true, false, 4.2);
}
// Jumping left
else if (this.sprite == 5) {
// Body
this._drawBody(true);
// Hands. (Hands!)
line(
this.x - this.x_step * 1,
this.y - this.y_step * 2.5,
this.x - this.x_step * 3.2,
this.y - this.y_step * 3.2,
);
line(
this.x + this.x_step * 1,
this.y - this.y_step * 2.5,
this.x + this.x_step * 3.2,
this.y - this.y_step * 3.2,
);
// Head
this._drawHead();
// Eyes
this._drawEyes(true, false, 3.8);
}
// Jumping right
else if (this.sprite == 6) {
// Body
this._drawBody(true);
// Hands. (Hands!)
line(
this.x - this.x_step * 1,
this.y - this.y_step * 2.5,
this.x - this.x_step * 3.2,
this.y - this.y_step * 3.2,
);
line(
this.x + this.x_step * 1,
this.y - this.y_step * 2.5,
this.x + this.x_step * 3.2,
this.y - this.y_step * 3.2,
);
// Head
this._drawHead();
// Eyes
this._drawEyes(false, true, 3.8);
} else {
text('Bad sprite number!', 10, 10);
console.error('Bad this sprite number: ' + this.sprite);
noLoop();
}
this.x_step /= this.scale;
this.y_step /= this.scale;
pop();
};
this.drawShadow = function () {
push();
noStroke();
this.x_step *= this.scale;
this.y_step *= this.scale;
fill('rgba(0,0,0,0.2)');
if (!this.isPlumetting)
shadow_y = groundPositions[this.curGroundIndex];
else shadow_y = NaN;
ellipse(
this.x,
shadow_y,
this.x_step * 5,
this.y_step * 1.5,
);
this.x_step /= this.scale;
this.y_step /= this.scale;
pop();
};
this._checkPlayerDie = function () {
if (frameCount - death_timer > 60) {
if (this.curLives > 1) {
this.curLives--;
death_timer = undefined;
startGame();
} else if (this.curLives == 1) {
this.curLives--;
} else {
this.curLives = 0;
}
}
};
this.act = function () {
if (!this.isPlummeting) {
if (this.isLeft && !this.isRight) {
this.x -= this.speed;
} else if (this.isRight && !this.isLeft) {
this.x += this.speed;
}
if (this.isJumping) {
this.y -= this.curJumpingStrength;
this.curJumpingStrength -= gravity;
2023-03-26 16:36:47 +00:00
// If the jump peak is reached, the character is considered to be falling instead.
2023-03-26 14:58:24 +00:00
if (this.curJumpingStrength <= 0) {
this.isFalling = true;
this.isJumping = false;
}
} else if (this.isFalling) {
this.y -= this.curJumpingStrength;
this.curJumpingStrength -= gravity;
2023-03-26 16:36:47 +00:00
for (i = 0; i < platforms[this.curGroundIndex].length; i++) {
// If a character is falling above some platform, memorize it
2023-03-26 17:40:53 +00:00
if (platforms[this.curGroundIndex][i].isAbove(this)) {
2023-03-26 16:36:47 +00:00
this.memorizedPlatform = platforms[this.curGroundIndex][i];
}
// If a character is below a memorized platform, make them stand on that platform
2023-03-26 17:40:53 +00:00
if (typeof this.memorizedPlatform !== 'undefined' &&
this.memorizedPlatform.isBelow(this)) {
2023-03-26 16:36:47 +00:00
this.y = this.memorizedPlatform.y;
this.isFalling = false;
this.curJumpingStrength = 0;
this.isOnPlatform = true;
}
}
// if a character is below the ground, make them stand on the ground
2023-03-26 14:58:24 +00:00
if (this.y >= this.getCurGroundY()) {
this.y = this.getCurGroundY();
this.isFalling = false;
2023-03-26 16:36:47 +00:00
this.curJumpingStrength = 0;
this.isOnPlatform = false;
2023-03-26 14:58:24 +00:00
}
2023-03-26 16:36:47 +00:00
} else if (this.isOnPlatform) {
// Not plummeting, standing or falling means being on a platform.
// Perform checks whether the player is still on the same platform,
// and fall them in case they aren't.
2023-03-26 17:40:53 +00:00
if (!this.memorizedPlatform._isWithinX(this)
2023-03-26 16:36:47 +00:00
||
this.curGroundIndex != this.memorizedPlatform.curGroundIndex) {
this.isFalling = true;
this.isOnPlatform = false;
2023-03-26 17:40:53 +00:00
this.memorizedPlatform = undefined;
2023-03-26 16:36:47 +00:00
}
2023-03-26 14:58:24 +00:00
}
}
this._checkPlayerDie();
// Doing plummeting
if (this.isPlummeting) {
// using OR operator to set to frameCount in case death_timer is undefined,
// but not updating the death_timer again in case it is not undefined.
death_timer = death_timer || frameCount;
this.y += 3;
}
};
2023-03-26 16:36:47 +00:00
2023-03-26 14:58:24 +00:00
this.getCurGroundY = function () {
return groundPositions[this.curGroundIndex];
};
this.goUp = function () {
memorizedIndex = this.curGroundIndex;
this.curGroundIndex = max(0, this.curGroundIndex - 1);
if (memorizedIndex != this.curGroundIndex) {
this.y -= (height - floorPos_y) / 3;
this.scale -= 0.1;
}
};
this.goDown = function () {
memorizedIndex = this.curGroundIndex;
this.curGroundIndex = min(
groundPositions.length - 1,
this.curGroundIndex + 1,
);
if (memorizedIndex != this.curGroundIndex) {
this.y += (height - floorPos_y) / 3;
this.scale += 0.1;
}
};
}
2023-03-26 17:40:53 +00:00
// NB: All the platforms mechanics were done
// before the task was seen on Week 20.
// Therefore, the implementation may
// differ from one in the lecture.
function Platform(curGroundIndex, x, width, y) {
this.curGroundIndex = curGroundIndex;
this.x = x;
this.width = width;
// To make platforms jumpable to each other, define a max vertical difference
// thru an arithmetic progression dependent on the game character's jumping strength and gravity.
2023-03-26 14:58:24 +00:00
this.max_y_deviation =
((gameChar.baseJumpingStrength / gravity) *
gameChar.baseJumpingStrength) /
2;
this.y = y || groundPositions[curGroundIndex] - this.max_y_deviation;
this.draw = function () {
push();
stroke(0);
strokeWeight(2);
fill(palette.ground_colors[curGroundIndex]);
rect(
this.x - this.width,
2023-03-26 16:36:47 +00:00
this.y,
width * 2,
2023-03-26 14:58:24 +00:00
height / 100,
);
pop();
};
this.drawShadow = function () {
push();
// drawing shadow on the ground
noStroke();
2023-03-26 14:58:24 +00:00
fill('rgba(0,0,0, 0.15)');
rect(
this.x - this.width,
groundPositions[curGroundIndex],
width * 2,
2023-03-26 14:58:24 +00:00
height / 100,
);
pop();
};
this._isWithinX = function (who) {
2023-03-26 17:40:53 +00:00
if (who.x >= this.x - this.width && who.x <= this.x + this.width) {
return true;
}
2023-03-26 17:40:53 +00:00
return false;
};
this.isBelow = function (who) {
2023-03-26 17:40:53 +00:00
if (this._isWithinX(who) && who.y >= this.y) {
return true;
}
2023-03-26 17:40:53 +00:00
return false;
};
this.isAbove = function (who) {
if (this._isWithinX(who) && who.y < this.y) {
return true;
}
2023-03-26 17:40:53 +00:00
return false;
};
}
2023-03-22 21:06:30 +00:00
var text_size;
// Variables to set colors. Set in setup()
var palette;
function setup() {
createCanvas(1024, 576);
palette = {
2023-03-26 14:58:24 +00:00
body_color: color('white'),
head_color: color('darkgreen'),
sky_color: color('#8E9887'),
ground_colors: [color('#874321'), color('#636721'), color('#634345')],
river_river_color: color('#56C525'),
river_river_wave_color: color('#4BAD21'),
river_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'),
2023-03-22 21:06:30 +00:00
cloud0: color(200),
cloud1: color(220),
cloud2: color(255),
2023-03-26 14:58:24 +00:00
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'),
2023-03-22 21:06:30 +00:00
};
2023-03-25 18:52:09 +00:00
startGame((level_start = true));
2023-03-22 21:06:30 +00:00
}
2023-03-25 17:41:54 +00:00
function startGame(level_start) {
2023-03-22 21:06:30 +00:00
floorPos_y = 432;
2023-03-25 17:41:54 +00:00
// levels of depth where the character, collectable and enemies stand.
2023-03-25 17:33:19 +00:00
groundPositions = [
floorPos_y + (height - floorPos_y) / 6,
floorPos_y + (3 * (height - floorPos_y)) / 6,
floorPos_y + (5 * (height - floorPos_y)) / 6,
];
2023-03-25 19:39:36 +00:00
camera_speed = 0.9;
2023-03-25 17:41:54 +00:00
gravity = 1;
game_score = 0;
finish_position_x = 2000;
textSize(width / 20);
flagpole = {
x: finish_position_x,
isReached: false,
size_vert: 4,
size_hor: 7,
cell_size: 20,
cell_size_v: 20,
cell_size_h: 20,
};
if (level_start) {
2023-03-26 14:58:24 +00:00
gameChar = new GameCharacter();
cameraPosX = gameChar.x - width / 2;
2023-03-25 19:46:39 +00:00
// Creating trees, clouds, mountains, rivers, collectables.
2023-03-25 17:41:54 +00:00
{
2023-03-25 19:46:39 +00:00
// Creating trees, clouds, mountains, rivers, collectables, platforms, enemies.
2023-03-25 17:41:54 +00:00
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
2023-03-25 18:52:09 +00:00
mountains[i].height = max(
mountains[i].width,
2023-03-26 14:58:24 +00:00
mountains[i].height,
2023-03-25 18:52:09 +00:00
);
2023-03-25 17:41:54 +00:00
}
// 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) {
2023-03-25 19:46:39 +00:00
rivers[0] = {
2023-03-25 17:41:54 +00:00
x: 700,
width: 50,
points: [],
};
} else {
2023-03-25 19:46:39 +00:00
if (rivers[i - 1].x + 600 > finish_position_x) break;
rivers[i] = {
x: rivers[i - 1].x + 300 + 200 * random(0.5, 1),
2023-03-25 17:41:54 +00:00
width: 50 + 30 * random(),
points: [],
};
}
}
for (i = 0; i < 100; i++) {
if (i == 0) {
collectables[0] = {
x: 600,
2023-03-25 18:52:09 +00:00
curGroundIndex: 0,
y:
groundPositions[0] -
10 /*taking 10 for better visuals*/,
2023-03-25 17:41:54 +00:00
size: 75,
isFound: false,
};
} else {
2023-03-25 18:52:09 +00:00
// Skipping generating coins after the finish line
2023-03-25 17:41:54 +00:00
if (collectables[i - 1].x + 200 > finish_position_x) {
break;
}
2023-03-25 18:52:09 +00:00
r = floor(random(0, groundPositions.length));
2023-03-25 17:41:54 +00:00
collectables[i] = {
x: collectables[i - 1].x + 50 + 100 * random(0.5, 1),
2023-03-25 18:52:09 +00:00
curGroundIndex: r,
y:
groundPositions[r] -
10 /*taking 10 for better visuals*/,
2023-03-25 17:41:54 +00:00
size: 75,
isFound: false,
};
2023-03-25 19:46:39 +00:00
// Checking whether the coin is over a river;
2023-03-25 17:41:54 +00:00
// marking it as isFound (making it disabled) in case if.
2023-03-25 19:46:39 +00:00
for (k = 0; k < rivers.length; k++) {
2023-03-25 17:41:54 +00:00
if (
2023-03-25 19:46:39 +00:00
rivers[k].x - rivers[k].width / 2 <
2023-03-25 17:41:54 +00:00
collectables[i].x &&
2023-03-25 19:46:39 +00:00
rivers[k].x + rivers[k].width / 2 >
2023-03-25 18:52:09 +00:00
collectables[i].x
2023-03-25 17:41:54 +00:00
) {
collectables[i].isFound = true;
}
}
}
}
for (i = 0; i < groundPositions.length; i++) {
platforms[i] = [];
}
for (i = 0; i < 100; i++) {
if (i == 0) {
platforms[0][0] = new Platform(
0,
700,
50,
2023-03-26 14:58:24 +00:00
groundPositions[0] - Platform.max_y_deviation,
);
platforms[1][0] = new Platform(
1,
800,
50,
2023-03-26 14:58:24 +00:00
groundPositions[1] - Platform.max_y_deviation,
);
platforms[2][0] = new Platform(
2,
900,
50,
2023-03-26 14:58:24 +00:00
groundPositions[2] - Platform.max_y_deviation,
);
} else {
let groundPosIndex = floor(
2023-03-26 14:58:24 +00:00
random(0, groundPositions.length),
);
// array.slice(-1)[0] gets the last element of an array without removing it,
// contrary to array.pop().
prev = platforms[groundPosIndex].slice(-1)[0];
// stopping generation after the flagpole
if (prev.x + prev.width + 155 > finish_position_x) break;
platforms[groundPosIndex].push(
new Platform(
groundPosIndex,
2023-03-26 14:58:24 +00:00
prev.x + random(prev.width + 70, prev.width + 155),
random(15, 60),
max(
2023-03-26 14:58:24 +00:00
random(
prev.y - prev.max_y_deviation,
floorPos_y - prev.max_y_deviation,
),
height / 8,
),
),
);
}
}
2023-03-25 17:41:54 +00:00
}
2023-03-22 21:06:30 +00:00
} else {
gameChar.x = width / 2;
gameChar.y = floorPos_y + (height - floorPos_y) / 6;
gameChar.scale = 1;
2023-03-25 17:33:19 +00:00
gameChar.curGroundIndex = 0;
2023-03-22 21:06:30 +00:00
gameChar.isPlummeting = false;
gameChar.isFalling = false;
gameChar.isJumping = false;
gameChar.isLeft = false;
gameChar.isRight = false;
2023-03-26 16:36:47 +00:00
gameChar.isOnPlatform = false;
2023-03-25 18:52:09 +00:00
}
2023-03-22 21:06:30 +00:00
}
function draw() {
// -------- SKY ----------------
background(palette.sky_color);
// -------- GROUND -------------
drawGround();
push();
2023-03-22 21:06:30 +00:00
fill(0);
2023-03-25 19:39:36 +00:00
translate(-cameraPosX, 0); // Scrolling everything
// Focusing on the character. lerp() ensures fluid camera movement.
if (gameChar.x < finish_position_x)
cameraPosX = lerp(gameChar.x - width / 2, cameraPosX, camera_speed);
2023-03-25 19:39:36 +00:00
else
cameraPosX = lerp(
finish_position_x - width / 2,
cameraPosX,
2023-03-26 14:58:24 +00:00
camera_speed,
);
2023-03-22 21:06:30 +00:00
// -------- CLOUDS --------------
drawClouds();
// -------- MOUNTAINS -----------
drawMountains();
// -------- CANYONS -------------
2023-03-25 19:46:39 +00:00
for (i = 0; i < rivers.length; i++) {
river = rivers[i];
drawRiver(river);
if (!gameChar.isFalling && !gameChar.isJumping) checkRiver(river);
2023-03-22 21:06:30 +00:00
}
// -------- TREES ---------------
drawTrees();
// -------- PLATFORMS SHADOWS ---
if (shadows_enabled)
2023-03-26 14:58:24 +00:00
for (i = 0; i < groundPositions.length; i++) {
drawPlatformsShadows(i);
}
2023-03-22 21:06:30 +00:00
// -------- COLLECTABLES --------
for (i = 0; i < collectables.length; i++) {
collectable = collectables[i];
if (!collectable.isFound) drawCollectable(collectable);
checkCollectable(collectable);
}
// -------- FLAGPOLE ------------
renderFlagpole();
if (!flagpole.isReached) checkFlagpole();
// GAMECHAR, PLATFORMS & ENEMIES RENDER
complexDraw();
// - ------ GAME CHARACTER ------
gameChar.act();
pop(); // Scrolling end
2023-03-22 21:06:30 +00:00
// -------- INTERFACE -----------
drawInterface();
}
function complexDraw() {
// Since platforms require being drawn in three different ordrs relative to enemies and the game character,
// a function is made to handle this and to decrease clutter.
2023-03-26 14:58:24 +00:00
for (i = 0; i <= gameChar.curGroundIndex; i++) {
drawPlatforms(i);
}
if (shadows_enabled) gameChar.drawShadow();
gameChar.draw();
2023-03-26 14:58:24 +00:00
for (i = gameChar.curGroundIndex + 1; i < groundPositions.length; i++) {
drawPlatforms(i);
}
}
function drawInterface() {
2023-03-22 21:06:30 +00:00
push();
fill(0);
stroke(0);
2023-03-26 14:58:24 +00:00
text('Score: ' + game_score, 12, text_size);
2023-03-22 21:06:30 +00:00
drawLives();
if (gameChar.curLives < 1)
2023-03-26 14:58:24 +00:00
text('Game over. Press space to continue...', 0, height / 2);
2023-03-22 21:06:30 +00:00
else if (flagpole.isReached)
2023-03-26 14:58:24 +00:00
text('Level complete. Press space to continue...', 0, height / 2);
2023-03-23 14:21:02 +00:00
if (showDebugData) {
2023-03-25 17:33:19 +00:00
text(gameChar.curGroundIndex, 99, 99);
text(gameChar.getCurGroundY(), 188, 99);
2023-03-23 14:22:33 +00:00
text(gameChar.sprite, 277, 99);
// draw game character's trajectory
drawCharTrace();
2023-03-23 14:22:33 +00:00
drawFps();
2023-03-23 14:21:02 +00:00
}
2023-03-22 21:06:30 +00:00
pop();
}
function drawCharTrace() {
debug_charTrace.push([gameChar.x, gameChar.y]);
if (debug_charTrace.length > 100) debug_charTrace.shift();
push();
stroke(255, 0, 0);
strokeWeight(3);
translate(-cameraPosX, 0);
for (i = 0; i < debug_charTrace.length; i++) {
point(debug_charTrace[i][0], debug_charTrace[i][1]);
}
translate(cameraPosX, 0);
pop();
}
2023-03-25 19:46:39 +00:00
function checkRiver(t_river) {
2023-03-22 21:06:30 +00:00
if (
2023-03-25 19:46:39 +00:00
gameChar.x > river.x - river.width / 2 &&
2023-03-26 16:36:47 +00:00
gameChar.x < river.x + river.width / 2 &&
gameChar.y > floorPos_y
2023-03-22 21:06:30 +00:00
) {
gameChar.isPlummeting = true;
}
}
function checkCollectable(t_collectable) {
if (
!t_collectable.isFound &&
2023-03-25 18:52:09 +00:00
collectable.curGroundIndex == gameChar.curGroundIndex &&
2023-03-22 21:06:30 +00:00
dist(
t_collectable.x,
t_collectable.y,
2023-03-22 21:06:30 +00:00
gameChar.x,
2023-03-26 14:58:24 +00:00
gameChar.y - gameChar.x_step * 4,
2023-03-22 21:06:30 +00:00
) <
t_collectable.size / 2
) {
t_collectable.isFound = true;
game_score++;
}
}
function drawGround() {
push();
noStroke();
fill(palette.ground_colors[0]);
2023-03-22 21:06:30 +00:00
rect(0, floorPos_y, width, height - floorPos_y);
fill(palette.ground_colors[1]);
2023-03-22 21:06:30 +00:00
rect(
0,
floorPos_y + (2 * (height - floorPos_y)) / 6,
width,
2023-03-26 14:58:24 +00:00
floorPos_y + (2 * (height - floorPos_y)) / 6,
2023-03-22 21:06:30 +00:00
);
fill(palette.ground_colors[2]);
2023-03-22 21:06:30 +00:00
rect(
0,
floorPos_y + (4 * (height - floorPos_y)) / 6,
width,
2023-03-26 14:58:24 +00:00
floorPos_y + (4 * (height - floorPos_y)) / 6,
2023-03-22 21:06:30 +00:00
);
stroke(1);
translate(-cameraPosX, 0);
for (i = -width / 2; i < finish_position_x + width / 2; i += 20) {
2023-03-22 21:06:30 +00:00
line(
i,
floorPos_y + (2 * (height - floorPos_y)) / 6,
i + 1,
2023-03-26 14:58:24 +00:00
floorPos_y + (2 * (height - floorPos_y)) / 6,
2023-03-22 21:06:30 +00:00
);
line(
i,
floorPos_y + (4 * (height - floorPos_y)) / 6,
i + 1,
2023-03-26 14:58:24 +00:00
floorPos_y + (4 * (height - floorPos_y)) / 6,
2023-03-22 21:06:30 +00:00
);
}
pop();
}
2023-03-25 19:46:39 +00:00
function drawRiver(t_river) {
2023-03-22 21:06:30 +00:00
push();
2023-03-25 19:46:39 +00:00
fill(palette.river_river_color);
rect(t_river.x - t_river.width / 2, floorPos_y, t_river.width, height);
2023-03-23 12:49:47 +00:00
// Waves animation.
if (frameCount % 2 == 0) {
2023-03-22 21:06:30 +00:00
PointX = random(
2023-03-25 19:46:39 +00:00
t_river.x - t_river.width / 2 + 5,
2023-03-26 14:58:24 +00:00
t_river.x + t_river.width / 2 - 5,
2023-03-22 21:06:30 +00:00
);
PointY = random(floorPos_y, height);
2023-03-25 19:46:39 +00:00
if (t_river.points.length > 2) {
t_river.points.shift();
2023-03-22 21:06:30 +00:00
}
2023-03-25 19:46:39 +00:00
t_river.points.push([PointX, PointY, random(20, 100)]);
2023-03-22 21:06:30 +00:00
}
2023-03-25 19:46:39 +00:00
stroke(palette.river_river_wave_color);
2023-03-22 21:06:30 +00:00
strokeWeight(5);
2023-03-25 19:46:39 +00:00
for (k = 0; k < t_river.points.length; k++) {
2023-03-23 12:49:47 +00:00
line(
2023-03-25 19:46:39 +00:00
t_river.points[k][0],
t_river.points[k][1],
t_river.points[k][0],
2023-03-26 14:58:24 +00:00
t_river.points[k][1] + t_river.points[k][2],
2023-03-23 12:49:47 +00:00
);
}
2023-03-22 21:06:30 +00:00
pop();
}
function drawCollectable(t_collectable) {
push();
// animating the coin's jiggle
2023-03-25 18:52:09 +00:00
// a - vert. intensity, c - hor. intensity, b - vert. speed, d - hor. speed
2023-03-23 13:46:18 +00:00
a = 0.1;
b = 3;
c = 0.2;
2023-03-25 18:52:09 +00:00
d = 2;
2023-03-23 13:46:18 +00:00
t_collectable.y -= sin(frameCount * a) * b;
t_collectable.x += sin(frameCount * c) * d;
2023-03-22 21:06:30 +00:00
stroke(0.2);
fill(palette.coin_outer);
ellipse(t_collectable.x, t_collectable.y, 0.7 * t_collectable.size);
2023-03-22 21:06:30 +00:00
fill(palette.coin_middle);
ellipse(t_collectable.x, t_collectable.y, 0.6 * t_collectable.size);
2023-03-22 21:06:30 +00:00
fill(palette.coin_inner);
ellipse(t_collectable.x, t_collectable.y, 0.5 * t_collectable.size);
// restoring coin's coordinates to preserve game logic
2023-03-23 13:46:18 +00:00
t_collectable.y += sin(frameCount * a) * b;
t_collectable.x -= sin(frameCount * c) * d;
pop();
2023-03-22 21:06:30 +00:00
}
function drawClouds() {
for (i = 0; i < clouds.length; i++) {
push();
2023-03-25 18:52:09 +00:00
// imitating clouds movement, upper ones should go faster
translate((frameCount / clouds[i].y) * 20, 0);
2023-03-23 13:39:49 +00:00
// Adding counter-translating to implement parallax, the feeling of depth
2023-03-23 13:46:18 +00:00
translate(cameraPosX / 1.1, 0);
2023-03-23 14:22:33 +00:00
2023-03-22 21:06:30 +00:00
noStroke();
fill(palette.cloud0);
ellipse(
clouds[i].x - 20,
clouds[i].y - 10,
20 * clouds[i].size,
2023-03-26 14:58:24 +00:00
30 * clouds[i].size,
2023-03-22 21:06:30 +00:00
);
fill(palette.cloud1);
ellipse(
clouds[i].x + 20,
clouds[i].y - 20,
70 * clouds[i].size,
2023-03-26 14:58:24 +00:00
50 * clouds[i].size,
2023-03-22 21:06:30 +00:00
);
fill(palette.cloud2);
ellipse(
clouds[i].x,
clouds[i].y,
90 * clouds[i].size,
2023-03-26 14:58:24 +00:00
40 * clouds[i].size,
2023-03-22 21:06:30 +00:00
);
ellipse(
clouds[i].x + 45,
clouds[i].y - 10,
50 * clouds[i].size,
2023-03-26 14:58:24 +00:00
35 * clouds[i].size,
2023-03-22 21:06:30 +00:00
);
pop();
}
}
function drawMountains() {
for (i = 0; i < mountains.length; i++) {
2023-03-23 13:35:28 +00:00
push();
// slowing down translation to add parallax, the feeling of depth
2023-03-23 14:21:02 +00:00
translate(cameraPosX / 2, 0);
2023-03-22 21:06:30 +00:00
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,
2023-03-26 14:58:24 +00:00
floorPos_y,
2023-03-22 21:06:30 +00:00
);
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,
2023-03-26 14:58:24 +00:00
floorPos_y,
2023-03-22 21:06:30 +00:00
);
2023-03-23 13:35:28 +00:00
pop();
2023-03-22 21:06:30 +00:00
}
}
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,
2023-03-26 14:58:24 +00:00
heart_size,
2023-03-22 21:06:30 +00:00
);
}
for (i = gameChar.curLives; i > 0; i--) {
drawHeart(
false,
width - i * heart_size * 4.5,
heart_size * 1.5,
2023-03-26 14:58:24 +00:00
heart_size,
2023-03-22 21:06:30 +00:00
);
}
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,
2023-03-26 14:58:24 +00:00
floorPos_y,
2023-03-22 21:06:30 +00:00
);
fill(palette.pine_leaves_color);
triangle(
trees_x[i] - 45,
floorPos_y - 45,
trees_x[i],
floorPos_y - 120,
trees_x[i] + 45,
2023-03-26 14:58:24 +00:00
floorPos_y - 45,
2023-03-22 21:06:30 +00:00
);
triangle(
trees_x[i] - 45,
floorPos_y - 85,
trees_x[i],
floorPos_y - 180,
trees_x[i] + 45,
2023-03-26 14:58:24 +00:00
floorPos_y - 85,
2023-03-22 21:06:30 +00:00
);
}
// Draw maple
else {
fill(palette.maple_stem);
triangle(
trees_x[i] - 10,
floorPos_y,
trees_x[i],
floorPos_y - 120,
trees_x[i] + 10,
2023-03-26 14:58:24 +00:00
floorPos_y,
2023-03-22 21:06:30 +00:00
);
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 drawPlatforms(rowIndex) {
push();
2023-03-26 14:58:24 +00:00
for (k = 0; k < platforms[rowIndex].length; k++) {
platforms[rowIndex][k].draw();
}
pop();
}
function drawPlatformsShadows(rowIndex) {
push();
2023-03-26 14:58:24 +00:00
for (k = 0; k < platforms[rowIndex].length; k++) {
platforms[rowIndex][k].drawShadow();
}
pop();
}
2023-03-22 21:06:30 +00:00
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,
2023-03-26 14:58:24 +00:00
flagpole.cell_size_v,
2023-03-22 21:06:30 +00:00
);
}
}
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;
}
}
2023-03-23 14:21:02 +00:00
function drawFps() {
push();
2023-03-23 14:22:33 +00:00
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);
2023-03-26 14:58:24 +00:00
fill('red');
2023-03-23 14:21:02 +00:00
text(fps, 400, 99);
2023-03-26 14:58:24 +00:00
stroke('black');
2023-03-23 14:21:02 +00:00
beginShape(LINES);
for (i = 1; i < fps_recent_values.length; i++) {
vertex(i, fps_recent_values[i]);
}
endShape();
pop();
}
2023-03-22 21:06:30 +00:00
function keyPressed() {
2023-03-26 14:58:24 +00:00
console.log(frameCount + ' pressed ' + key + ' ' + keyCode);
2023-03-22 21:06:30 +00:00
if (gameChar.curLives < 1 || flagpole.isReached) {
if (keyCode == 32 /*Space*/) {
/*Hard resets the game*/
2023-03-25 18:52:09 +00:00
startGame((level_start = true));
2023-03-22 21:06:30 +00:00
}
} else if (!gameChar.isPlummeting) {
if (keyCode == 65 /*A*/ || keyCode == LEFT_ARROW)
gameChar.isLeft = true;
if (keyCode == 68 /*D*/ || keyCode == RIGHT_ARROW)
gameChar.isRight = true;
if (keyCode == 83 /*S*/ || keyCode == DOWN_ARROW) gameChar.goDown();
if (keyCode == 87 /*W*/ || keyCode == UP_ARROW) gameChar.goUp();
2023-03-26 14:58:24 +00:00
// Rewrote jumping routine to make it more natural and be able to
// support platforms and different player dimensions
2023-03-22 21:06:30 +00:00
if (
(keyCode == 32 /*Space*/ || keyCode == 88) /*X*/ &&
2023-03-22 21:06:30 +00:00
!gameChar.isFalling &&
!gameChar.isJumping
) {
gameChar.isJumping = true;
gameChar.curJumpingStrength = gameChar.baseJumpingStrength;
2023-03-22 21:06:30 +00:00
}
}
2023-03-23 14:21:02 +00:00
if (keyCode == 77 /*M*/) showDebugData = !showDebugData;
2023-03-26 17:43:30 +00:00
if (keyCode == 78 /*N*/) shadows_enabled = !shadows_enabled;
2023-03-22 21:06:30 +00:00
}
function keyReleased() {
2023-03-26 14:58:24 +00:00
console.log(frameCount + ' released ' + key + ' ' + keyCode);
if (keyCode == 65 /*A*/ || keyCode == LEFT_ARROW) gameChar.isLeft = false;
if (keyCode == 68 /*D*/ || keyCode == RIGHT_ARROW) gameChar.isRight = false;
2023-03-25 17:33:19 +00:00
}