Open LiliWuu opened 1 month ago
We created a new team level called Greece and added new game objects in GameSetterGreece.js.
// Hills Game Level defintion...
const objects = [
// GameObject(s), the order is important to z-index...
{ name: 'greece', id: 'background', class: Background, data: assets.backgrounds.greece },
{ name: 'grass', id: 'platform', class: Platform, data: assets.platforms.grass },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.2, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.2368, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.2736, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.3104, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.3472, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.384, yPercentage: 0.76 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.4208, yPercentage: 0.70 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.5090, yPercentage: 0.64 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.5642, yPercentage: 0.34 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.5274, yPercentage: 0.34 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.4906, yPercentage: 0.34 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 1 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.94 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.88 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.76 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.70 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.64 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.58 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.52 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.46 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.40 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.34 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.28 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.22 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6368, yPercentage: 0.64 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.16 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.1 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.6, yPercentage: 0.06 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 1 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.94 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.88 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.82 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.76 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.70 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.64 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.58 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.52 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.46 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.40 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.34 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.28 },
{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.22 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.16 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.1 },
//{ name: 'sandstone', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.sandstone, xPercentage: 0.75, yPercentage: 0.06 },
{ name: 'cerberus', id: 'cerberus', class: Cerberus, data: assets.enemies.cerberus, xPercentage: 0.2, minPosition: 0.09, difficulties: ["normal", "hard", "impossible"] },
{ name: 'cerberus', id: 'cerberus', class: Cerberus, data: assets.enemies.cerberus, xPercentage: 0.5, minPosition: 0.3, difficulties: ["normal", "hard", "impossible"] },
{ name: 'cerberus', id: 'cerberus', class: Cerberus, data: assets.enemies.cerberus, xPercentage: 0.7, minPosition: 0.1, difficulties: ["normal", "hard", "impossible"] },//this special name is used for random event 2 to make sure that only one of the Goombas ends the random event
{ name: 'dragon', id: 'dragon', class: Dragon, data: assets.enemies.dragon, xPercentage: 0.5, minPosition: 0.05 },
{ name: 'knight', id: 'player', class: PlayerGreece, data: assets.players.knight },
{ name: 'flyingIsland', id: 'flyingIsland', class: FlyingIsland, data: assets.platforms.island, xPercentage: 0.82, yPercentage: 0.55 },
{ name: 'tubeU', id: 'minifinishline', class: FinishLine, data: assets.obstacles.tubeU, xPercentage: 0.66, yPercentage: 0.9 },
{ name: 'flag', id: 'finishline', class: FinishLine, data: assets.obstacles.flag, xPercentage: 0.875, yPercentage: 0.275 },
{ name: 'hillsEnd', id: 'background', class: BackgroundTransitions, data: assets.transitions.hillsEnd },
{ name: 'lava', id: 'lava', class: Lava, data: assets.platforms.lava, xPercentage: 0, yPercentage: 1 },
];
Code from GameSetterGreece.js is then passed through the function in GameSetup.js that initializes the game levels:
// Initialize Game Levels
function GameLevelSetup(GameSetter, path, callback, passive = false) {
var gameObjects = new GameSet(GameSetter.assets, GameSetter.objects, path);
return new GameLevel({ tag: GameSetter.tag, callback: callback, objects: gameObjects.getGameObjects(), passive: passive });
}
// code hidden
GameLevelSetup(GameSetterGreece, this.path, this.playerOffScreenCallBack);
We created a new dragon enemy that changes direction every time the dragon changes direction:
if (this.speed < 0) {
this.canvas.style.transform = 'scaleX(1)'; }
else { this.canvas.style.transform = 'scaleX(-1)'; }
https://github.com/CyberLord09/CSSE1_Final/assets/142454293/d0cef575-f3ee-4648-826a-80d13a6a1b81
Before the lava erupts, there are a couple of things that the game uses to inform the player that lava is rising. The first is a large warning symbol.
this.warningSymbol = document.createElement('img');
this.warningSymbol.src = "/platformer3x/images/platformer/sprites/alert.gif";
this.warningSymbol.style.position = 'absolute';
this.warningSymbol.style.top = '35%';
this.warningSymbol.style.left = '50%';
this.warningSymbol.style.transform = 'translate(-50%, -50%)';
this.warningSymbol.style.display = 'none'; // Initially hidden
document.body.appendChild(this.warningSymbol);
https://github.com/CyberLord09/CSSE1_Final/assets/142454293/30589952-0e06-4d46-8179-d980121d8fce
In order for the player to interact with the lava, we added a case in playerGreece.js for lava collisions. The HP bar is drawn in the drawHPbox function, and the parameters are placed in the constructor. Each time the player collides with the lava, 1/3 of the player health is lost (33 out of 99 health) and the player is sent up in the air as a jump. Upon the last collision, the player loses all HP (with the HP bar cleared) and dies.
case "lava": // Note: Goomba.js and Player.js could be refactored
if (this.collisionData.touchPoints.other.id === "lava") {
if (GameEnv.difficulty === "normal" || GameEnv.difficulty === "hard") {
if (this.state.isDying == false) {
if(this.currentHp == 33){
this.currentHp -= 33;
this.drawHpBox();
this.state.isDying = true;
this.canvas.style.transition = "transform 0.5s";
this.canvas.style.transform = "rotate(-90deg) translate(-26px, 0%)";
GameEnv.playSound("PlayerDeath");
setTimeout(async() => {
await GameControl.transitionToLevel(GameEnv.levels[GameEnv.levels.indexOf(GameEnv.currentLevel)]);
}, 900);
} else{
this.setY(this.y - (this.bottom * 0.6));
this.currentHp -= 33;
}
}
We created a new mini level that acts as a level that allows the player to collect coins without getting killed by enemies. We did this by adding new game objects to our Greece mini level in GameSetterGreeceMini.js:
const objects = [
{ name: 'mini', id: 'background', class: Background, data: assets.backgrounds.mini },
{ name: 'rockslava', id: 'platform', class: Platform, data: assets.platforms.rockslava },
// { name: 'rock', id: 'platform', class: Platform, data: assets.platforms.rock },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.59, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.6268, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3368, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3, yPercentage: 0.85 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3368, yPercentage: 0.85 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.4684, yPercentage: 0.85 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.6, yPercentage: 0.85 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.6368, yPercentage: 0.85 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3736, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3736, yPercentage: 0.4334 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3736, yPercentage: 0.5167 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.3736, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.4104, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.4472, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.484, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.5208, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.5576, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.5576, yPercentage: 0.5167 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.5576, yPercentage: 0.4334 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.5576, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.8576, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.8576, yPercentage: 0.5167 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.8576, yPercentage: 0.4334 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.8576, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.8576, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.8208, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.784, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.7472, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.7104, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.6736, yPercentage: 0.6 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.1736, yPercentage: 0.35 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.1736, yPercentage: 0.4334 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.1736, yPercentage: 0.5167 },
{ name: 'blocks', id: 'jumpPlatform', class: BlockPlatform, data: assets.platforms.lava, xPercentage: 0.1736, yPercentage: 0.6 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.28, yPercentage: 0.25 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.32, yPercentage: 0.25 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.29, yPercentage: 0.75 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.33, yPercentage: 0.75 },
{ name: 'star', id: 'star', class: Star, data: assets.obstacles.star, xPercentage: 0.4584, yPercentage: 0.75 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.40, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.42, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.44, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.46, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.48, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.5, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.59, yPercentage: 0.75 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.63, yPercentage: 0.75 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.58, yPercentage: 0.25 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.62, yPercentage: 0.25 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.6475, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.6675, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.6875, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.7075, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.7275, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.7475, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.7675, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.7875, yPercentage: 0.5 },
{ name: 'coin', id: 'coin', class: Coin, data: assets.obstacles.coin, xPercentage: 0.8075, yPercentage: 0.5 },
{ name: 'knight', id: 'player', class: PlayerMini, data: assets.players.knight },
{ name: 'tubeD', id: 'finishline', class: FinishLine, data: assets.obstacles.tubeD, xPercentage: 0, yPercentage: 0.0685 },
{ name: 'tubeU', id: 'finishline', class: FinishLine, data: assets.obstacles.tubeU, xPercentage: 0.85, yPercentage: 0.85 },
{ name: 'greeceEnd', id: 'background', class: BackgroundTransitions, data: assets.transitions.greeceEnd },
];
The mini level has two tubes, one at the top left corner and the other at the bottom right corner. We created a new property in PlayerMini.js to have the player's y position start at the top of the screen and drop down from where the first tube is:
constructor(canvas, image, data) {
super(canvas, image, data);
const scaledHeight = GameEnv.innerHeight * (100 / 832);
const finishlineX = .01 * GameEnv.innerWidth;
this.setX(finishlineX);
this.hillsStart = true;
// Goomba variables, deprecate?
this.timer = false;
GameEnv.invincible = false; // Player is not invincible
}
// code hidden
update(){
super.update();
if (this.hillsStart) {
this.setY(0);
this.hillsStart = false;
}
}
The second tube in the mini level should then transition from the mini level back to the Greece level.
We created a new boss level that acts as the final boss at the end of all the levels. First, we added code to GameSetterBoss.js so that there is a Enemy called boss with its properties and spritesheet:
boss: {
src: "/images/platformer/sprites/boss.png",
width: 64,
height: 64,
scaleSize: 320,
speedRatio: 0.6,
animationSpeed: 6,
idleL: { row: 9, frames: 0, idleFrame: { column: 1, frames: 0 } },
idleR: { row: 11, frames: 0, idleFrame: { column: 1, frames: 0 } },
left: { row: 9, frames: 8, idleFrame: { column: 7, frames: 0 } },
right: { row: 11, frames: 8, idleFrame: { column: 7, frames: 0 } },
attackL: { row: 13, frames: 5 },
attackR: { row: 15, frames: 5 },
death: { row: 20, frames: 5 },
hitbox: { widthPercentage: 0.3, heightPercentage: 0.8 }
},
To understand how the random event work, first we need to know what is the function "setTimeout()"
The setTimeout(func, delay) Javascript method is used to call a function after a certain period of time. The time after which the function will be called is given by the user in milliseconds.
Here, we first set up a variable called "randomNum" which will be assigned a random number from 3 to 10 (represent 3 to 10 sec after the game starts) And we have two setTimeout(), one inside another. This causes setTimeout() to run forever and cause the random event (the code inside the setTimeout() function) to run repeatedly and randomly for each 3 to 10 sec.
function RandomEvent() {
let randomNum = Math.floor(Math.random() * 8) + 3; // random num from 3 to 10 sec
let startRandomEvent = setTimeout(function request() {
GameControl.startRandomEvent("boss")
randomNum = Math.floor(Math.random() * 8) + 3; // reset the random num from 3 to 10 sec
GameControl.startRandomEvent("narwhalboss")
randomNum = Math.floor(Math.random() * 8) + 3; // reset the random num from 3 to 10 sec
startRandomEvent = setTimeout(request, randomNum * 1000);
}, randomNum * 1000); //each random event will happen each 3 to 10 sec
}
We also added to the random event code in GameControl.js. Because the game already has a Global random event, we can assign the original function an "input" called "event" to make a random event for a specific object. So that for each specific input to the function, the game will run a specific if-statement inside the function and change the variable to a specific number which will run the specific random event we want::
startRandomEvent(event) {
if(event === "game"){ //game random event
this.randomEventState = 1;
this.randomEventId = Math.floor(Math.random() * 3) + 1; //The number multiplied by Math.random() is the number of possible events.
/**Random Event Key
* 1: Inverts the Color of the Background
* 2: Time Stops all Goombas
* 3: Kills a Random Goomba
*/
}
else if(event === "boss"){ //zombie event
this.randomEventState = 2;
this.randomEventId = Math.floor(Math.random() * 3) + 1; //The number multiplied by Math.random() is the number of possible events.
/**Random Event Key
* 1: Stop the Zombie
* 2: Let the Zombie to walk left
* 3: Let the Zombie to walk right
*/
}
},
if (GameControl.randomEventId === 1 && GameControl.randomEventState === 2){ //event: stop the zombie
this.direction = "idle";
GameControl.endRandomEvent();
}
if (GameControl.randomEventId === 2 && GameControl.randomEventState === 2){ //event: stop the zombie
this.direction = "a";
GameControl.endRandomEvent();
}
if (GameControl.randomEventId === 3 && GameControl.randomEventState === 2){ //event: stop the zombie
this.direction = "d";
GameControl.endRandomEvent();
}
If we want an Enemy to play a Death animation after it has been hit on top by the player. We can't just simply change the animation to "death". Because different devices have a unique speed to play the animation frame by frame, so if we just change the animation to "death" and use a setTimeout() function and destroy function to set up a delayed death, hoping it will play the completed animation before it was destroyed, probably it won't work and for some devices, the death animation may play twice and for other it not even able to play once.
if(this.collisionData.touchPoints.other.bottom && this.immune == 0){
this.state.animation = "death";
if(!this.state.isDying && this.state.animation == "death"){
this.frameX = 0;
}
this.state.isDying = true;
GameEnv.invincible = true;
GameEnv.goombaBounce = true;
GameEnv.playSound("goombaDeath");
}
We can add an if-statement to check if the enemy has been hit on top by the player if it is, then we set the animation to "death", and frameX to 0 by checking the property "this.state.isDying" (Finite State Machine). We turn the this.state.isDying to true for the next check.
//overwrite the method
updateFrameX() {
// Update animation frameX of the object
if(!this.state.isDying || this.state.animation != "death"){
if (this.frameX < this.maxFrame) {
if(this.counter > 0){
this.frameX = this.frameX;
this.counter--;
}
else{
this.frameX++
this.counter = this.animationSpeed;
}
} else {
this.frameX = this.minFrame;
}
}
else if(this.state.isDying && this.state.animation == "death"){
this.animationSpeed = 50;
if (this.frameX < this.maxFrame) {
if(this.counter > 0){
this.frameX = this.frameX;
this.counter--;
}
else{
this.frameX++
this.counter = this.animationSpeed;
}
} else {
this.destroy();
}
}
}
We first created the canvas for the HP bar:
this.maxHp = 100; // Maximum health points
this.currentHp = 100; // Current health points
this.hpBar = document.createElement("canvas");
this.hpBar.width = 100;
this.hpBar.height = 15;
document.querySelector("#canvasContainer").appendChild(this.hpBar);
Then, we ceated a function called drawHPBox which gets called repeatedly to draw the HPBox on the screen:
drawHpBox() { //Hp box
// Position and size of the health bar
const hpBarWidth = this.hpBar.width; // The width of the health bar matches the boss's width
const hpBarHeight = this.hpBar.height; // A fixed height for the health bar
const hpBarX = (this.x + this.canvasWidth/2 - this.hpBar.width/2); // Position above the boss
const hpBarY = this.y - this.canvasHeight/40; // 20 pixels above the boss
this.hpBar.id = "hpBar"; // Calculate health percentage
const hpPercentage = this.currentHp / this.maxHp;
this.hpBar.getContext('2d').fillStyle = 'gray'; // Draw the background (gray)
this.hpBar.getContext('2d').fillRect(0, 0, hpBarWidth, hpBarHeight); // Draw the health bar (green, based on current health)
this.hpBar.getContext('2d').fillStyle = 'green';
this.hpBar.getContext('2d').fillRect(0, 0, hpBarWidth * hpPercentage, hpBarHeight);
this.hpBar.style.position = 'absolute'; //code from Flag.js, define the style of the Hp Bar
this.hpBar.style.left = `${hpBarX}px`;
this.hpBar.style.top = `${hpBarY}px`;
this.hpBar.style.borderRadius = '5px';
this.hpBar.style.width = `${hpBarWidth}px`;
this.hpBar.style.height = `${hpBarHeight}px`;
this.hpBar.style.border = '2px solid black';
}
Finally, we had to reduce the HP whenever a collision occurs with the player and the boss. Naturally, this code will exist in the collision action section. Here, we check for collisions with the player. First, we check if the currentHp is 0 (which means that the boss has been hit 5 times). If the currentHP is 0, we set the current animation state to be death. We then set the isDying state, invincible, goombaBounce to be true. Then, we play the GoombaDeath sound as the boss dies:
Else, we reduce the currentHP by 20, and in the loop, the drawHPBox function gets called again, so the amount of Green in the Bar gets reduced and the Gray shows.
else if(this.collisionData.touchPoints.other.bottom && this.immune == 0){
if(this.currentHp == 0){
this.state.animation = "death";
if(!this.state.isDying && this.state.animation == "death"){
this.frameX = 0;
}
this.state.isDying = true;
GameEnv.invincible = true;
GameEnv.goombaBounce = true;
GameEnv.playSound("goombaDeath");
}
else{
this.currentHp -= 20;
GameEnv.goombaBounce = true;
}
}
We added an attack range property to the boss so that the player can attack the boss from further away. This is so that the player can press the shift key and actually do damage to the Boss' HP, and makes the already challenging level a little bit easier.
this.attackRange = 50;
if (GameEnv.playerAttack && (Math.abs((this.x + this.canvasWidth)/2-(GameEnv.x + GameEnv.canvasWidth)/2) < (this.canvasWidth/2 + this.attackRange))) {
this.currentHp -= 1;
}
Same as the Base file for Enemy, we can have a new base file for FlyingEnemy to clean up the code Before we create the Base file, we can see that the codes for the FlyingEnemy are not consistent from level to level Some of them just copy and paste all the code from FlyingGoomba.js and some of them choose to extend the FlyingGoomba
Both ways have repeated code segments that aren't necessary and are easier to confuse others (CSSE students for next year).
How to fix?:
Create a javascript file called FlyingEnemy, this file is for FlyingEnemy that has a symmetrical sprite/image like Goomba. Create a javascript file called FlyingEnemyOneD, this file is for FlyingEnemy that doesn't have a symmetrical sprite/image like Dragon. Copy and paste the code from FlyingGoomba into Both FlyingEnemy.js and FlyingEnemyOneD.js, we will use this code as basic to build our Base file. We need to take out all the code inside the update() function and apply the single responsibility principle, creating different functions and categorizing the take-out code into the functions based on their purposes. Change the class extended for each flying enemy to either FlyingEnemy or FlyingEnemyOneD (if your sprite/image is symmetrical, then extend the FlyingEnemy, if not, extend the FlyingEnemyOneD Lastly, remove all other code except the update function, and for the update function, it should include only super.update() And we will have a simple code for our flying enemy which is easier to read and more pleasing our eyes.
import FlyingEnemy from './FlyingEnemy.js';
export class FlyingGoomba extends FlyingEnemy {
// constructors sets up Character object
constructor(canvas, image, data, xPercentage, yPercentage, name, minPosition){
super(canvas, image, data, xPercentage, yPercentage, name, minPosition);
this.enemySpeed();
}
update() {
super.update();
}
}
export default FlyingGoomba;
As you see, the player will change to a zombie when it hits the item block.
At first, we try to achieve that by removing the original player(Mario) and replacing it with our zombie player during the gameplay. We tried, and we failed. Because of the time limit, we don't want to spend more time on this hard way. So according to Mr.M's advice, we plan to make the "parallel players".
Instead of replacing the player, we plan to add two players to our level and make one, the zombie player, invisible in the beginning.
We create PlayerZombie.js for our zombie player and PlayerBoss.js for the Mario player(therefore we don't need to change the PlayerHills.js)
And because our zombie sprite sheet has only one direction, so we added a new Base Player file, PlayerBaseOneD.js, for sprites that only have one direction.
The only difference between PlayerBaseOneD.js and PlayerBase.js is we added a if-statement to the updateAnimation() function.
if(this.state.direction == "left"){
this.canvas.style.transform = 'scaleX(-1)';
}
else{
this.canvas.style.transform = 'scaleX(1)';
}
updateAnimation() {
switch (this.state.animation) {
case 'idle':
if(this.state.direction == "left"){
this.canvas.style.transform = 'scaleX(-1)';
}
else{
this.canvas.style.transform = 'scaleX(1)';
}
this.setSpriteAnimation(this.playerData.idle);
break;
//...code hidden
}
}
The PlayerZombie.js extends the new base file PlayerBaseOneD.js we created instead of extending the old PlayerBase.js.
To make the player invisible, we create a new property to the constructor called "this.invisible" for both player files and we set it equal to true.
constructor(canvas, image, data) {
super(canvas, image, data);
this.invisible = true;
// Goomba variables, deprecate?
this.timer = false;
GameEnv.invincible = false; // Player is not invincible
}
And then we overwrite the function draw() and add an if-statement to it so that when "this.invisible"=true, the player image won't draw/won't appear and therefore the player is invisible.
if (!this.invisible) {
this.ctx.fillText(this.name, 0, this.canvas.height / 4);
this.ctx.drawImage(
this.image,
this.frameX * this.spriteWidth,
this.frameY * this.spriteHeight,
this.spriteWidth,
this.spriteHeight,
0,
0,
this.canvas.width,
this.canvas.height
);
}
startRandomEvent("game") is the global event includes time stop, background change, and randomly kill a Goomba in the Hill level.
startRandomEvent("zombie"); is the new event we create to change the "this.invisible"
So that when Mario player hits our itemBlock, it will run the function startRandomEvent("zombie"); and change "this.invisible" to false and let the zombie player appear and the Mario player disappear/invisible.
General Revisions to the Game
Enemy Movement
https://github.com/CyberLord09/CSSE1_Final/assets/142454293/50bb76a1-0a48-4e36-8f46-b1dbb3803a48
We wanted certain enemies to move back and forth. We first added new properties that handle the direction and animation of the enemy to the Enemy class.
For the collisions with the boundaries of the game environment, we created a method that checks for the boundaries of the enemy to the minPosition, which is set based on the GameEnv.innerWidth so that the direction of the enemy will change from left to right or right to left. The movement of the enemy is also set by changing the speed:
We also change the direction of the enemy when it collides with certain game objects:
Animations
We wanted the animations to be smoother for some animating game objects. We did this in the Boss.js file by adding two new attributes:
We created a new method that used the counter to control frame change based on animation speed. Then, we added animationSpeed as a property to the boss character in GameSetterBoss.js:
Transitions Between Levels
We refactored transitions to use level tag rather level number. This is so that if any levels get reordered around, they would look nicer with their specifically-matched transition. The way this code works is by creates a constant names index, which finds the index of the level with the tag named "Greece." It then transitions to index, which in this case, is "Greece." We implemented this for all levels: