CSSE 2 Final Week 21
Personal blog for CSSE 2 Final Week 21
Day 1:
Tried integrating the adventure game with the platformer game. Was wholly unsuccessful, but I’ll try another approach. My origianl approach was loading the adventure game files and platformer files into the same repo, the connecting them using imports + integration file.
Here’s my integration file: PlatformerIntegration.js
:
// File: _site/assets/js/adventureGame/PlatformerIntegration.js
import GameControl from '../platformer/GameControl.js';
import GameEnv from '../platformer/GameEnv.js';
export function transitionToPlatformerLevel(levelTag) {
// Find the platformer game level by tag
//const level = GameEnv.levels.find(l => l.tag === levelTag);
console.log("transiitoning to level", levelTag);
if (level) {
// Transition to the platformer game level
console.log(`Transitioned to level with tag ${levelTag}.`);
GameControl.transitionToLevel(levelTag);
} else {
console.error(`Level with tag ${levelTag} not found.`);
}
}
and here’s adventureGame.md
:
<script type="module">
import AdventureGameControl from '/lucas_2025/assets/js/adventureGame/GameControl.js';
import Prompt from '/lucas_2025/assets/js/adventureGame/Prompt.js';
import { getStats } from '/lucas_2025/assets/js/adventureGame/StatsManager.js';
import GameSetup from '/lucas_2025/assets/js/platformer/GameSetup.js';
import PlatformerGameControl from '/lucas_2025/assets/js/platformer/GameControl.js';
import SettingsControl from '/lucas_2025/assets/js/platformer/SettingsControl.js';
import GameEnv from '/lucas_2025/assets/js/platformer/GameEnv.js';
import Leaderboard from '/lucas_2025/assets/js/platformer/Leaderboard.js';
which amounted to 37 changed files of 1860 additions.
This looks to not be working, but I think I’ll try writing the platformer game partially from scratch so I can better start envs inside adventure. Painnnnnn.
Day 2:
Tried some integration again but was futile. For a bit tried to build the platformer game from scratch in the adventure game environment, but the attempt was futile (didn’t really know where to start).
Following the rather dissapointing attempt, I had inspiration to actually build a boss fight level with actual fighting mechanics. I’ve made a few additions:
I built this file to operate bossfights called BossFight.js
:
import GameEnv from './GameEnv.js';
import GameControl from './GameControl.js';
import Enemy from './Enemy.js';
import hpBar from './hpBar.js';
export class Boss extends Enemy {
// instantiation: constructor sets up player object
constructor(canvas, image, data, xPercentage, yPercentage, name, minPosition) {
super(canvas, image, data, xPercentage, yPercentage, name, minPosition);
this.storeSpeed = this.speed;
this.animationSpeed = data?.animationSpeed || 1; //higher "animationSpeed" means slower animation
this.counter = data?.animationSpeed;
this.enemySpeed();
//Hp Bar
this.maxHp = 100; // Maximum health points
this.currentHp = 100; // Current health points
this.hpBar = new hpBar(100, 15, this.canvasWidth, this.canvasHeight, this.maxHp, this.currentHp, this.x, this.y)
this.attackRange = 50;
this.laserCooldown = 100; // Cooldown period for firing lasers
this.laserCounter = 0;
}
//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();
this.hpBar.destroy();
}
}
}
//overwrite the method
updateMovement(){
if (this.state.animation === "right") {
this.speed = Math.abs(this.storeSpeed)
}
else if (this.state.animation === "left") {
this.speed = -Math.abs(this.storeSpeed);
}
else if (this.state.animation === "death") {
this.speed = 0
}
else if (this.state.animation === "idleL") {
this.speed = 0
}
else if (this.state.animation === "idleR") {
this.speed = 0
}
// Move the enemy
this.x += this.speed;
this.playerBottomCollision = false;
}
fireLaser() {
if (this.laserCounter <= 0) {
// Logic to fire laser beams downwards
const laser = {
x: this.x,
y: this.y + this.canvasHeight,
width: 5,
height: 20,
speed: 5,
update: function() {
this.y += this.speed;
},
draw: function(ctx) {
ctx.fillStyle = 'red';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
};
this.lasers = this.lasers || [];
this.lasers.push(laser);
this.laserCounter = this.laserCooldown;
} else {
this.laserCounter--;
}
}
update() {
super.update();
this.updateMovement();
this.updateFrameX();
this.fireLaser();
this.hpBar.updateHpBar(this.currentHp, this.x, this.y, this.canvasWidth, this.canvasHeight);
}
//overwrite the method
collisionAction() {
if (this.collisionData.touchPoints.other.id === "player") {
if (this.collisionData.touchPoints.other.right && !this.collisionData.touchPoints.other.bottom) {
this.x -= 10;
this.state.direction = "left";
this.state.animation = "attackL";
this.speed = 0;
}
else if(this.collisionData.touchPoints.other.left && !this.collisionData.touchPoints.other.bottom){
this.x += 10;
this.state.direction = "right";
this.state.animation = "attackR";
this.speed = 0;
}
else if(this.collisionData.touchPoints.other.bottom && this.immune == 0){
GameEnv.goombaBounce = true;
}
}
else{
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.playSound("goombaDeath");
}
else{
if (GameEnv.playerAttack && (Math.abs((this.x + this.canvasWidth)/2-(GameEnv.x + GameEnv.canvasWidth)/2) < (this.canvasWidth/2 + this.attackRange))) {
this.currentHp -= 1;
}
}
}
}
}
export default Boss;
and made a few changes to GameSetterBoss.js
to change the sprites. The level seems to fail, however, when I remove the boss enemy from the enemies
object of the level assets or modify it with anything else. I also happened to notice quite a few errors with key is undefined
, which is a core mechanic…
Uncaught ReferenceError: keys is not defined
updateParallaxDirection https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/GameEnv.js:240
handleKeyDown https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/PlayerBase.js:135
PlayerBase https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/PlayerBase.js:38
PlayerHills https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/PlayerHills.js:24
load https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/GameLevel.js:56
transitionToLevel https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/GameControl.js:229
gameLoop https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/GameControl.js:269
gameLoop https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/GameControl.js:280
gameLoop https://nighthawkcoders.github.io/platformer4x/assets/js/platformer/GameControl.js:280
<anonymous> https://nighthawkcoders.github.io/platformer4x/:239
GameEnv.js:240:21
Day 3 & Day 4
Behold, the boss fight!
Here we see my file BossFight.js
:
import GameEnv from './GameEnv.js';
import GameControl from './GameControl.js';
import Laser from './Laser.js';
import Enemy from './Enemy.js';
import TitanHealth from './TitanHealth.js';
export class BossFight extends Character {
// Constructor sets up Character object
constructor(canvas, image, data, xPercentage, yPercentage, name, minPosition) {
super(canvas, image, data);
// Titan properties
this.name = name;
this.y = yPercentage;
this.x = xPercentage * GameEnv.innerWidth;
this.minPosition = minPosition * GameEnv.innerWidth;
this.maxPosition = this.x + xPercentage * GameEnv.innerWidth;
// Health properties
this.maxHp = 100; // Maximum health points
this.currentHp = 100; // Current health points
this.titanHealthBar = new TitanHealth(
150, 10, // Width and height of the health bar
this.canvas.width, this.canvas.height, // Titan dimensions
this.maxHp, this.currentHp, // Titan's max and current health
this.x, this.y // Titan's position
);
// State properties
this.state = {
isDead: false // New state for checking if Titan is dead
};
// Laser-related properties
this.immune = 0;
this.debounce = 0;
this.laser = document.getElementById("Laser");
this.laserHeight = this.laser.innerHeight;
// Hide the laser element
this.laser.style.display = "none";
// New property to randomize laser delay
this.laserFireDelay = this.getRandomLaserDelay();
GameEnv.playSound("regicide");
}
hpLoss() {
if (GameEnv.playerAttack && !this.state.isDead) {
this.currentHp -= 1;
}
}
// Method to handle Titan's death state (makes the Titan disappear)
handleDeath() {
if (this.currentHp <= 0 && !this.state.isDead) {
this.state.isDead = true; // Set the Titan as dead
GameEnv.invincible = true; // Make invincible
this.canvas.style.display = "none"; // Hide the Titan's canvas (makes it disappear)
GameEnv.playSound("goombaDeath"); // Play the death sound
}
}
// Method to get a random delay between 1 and 10 seconds (converted to frames)
getRandomLaserDelay() {
const minDelay = 60; // 1 second = 60 frames
const maxDelay = 600; // 10 seconds = 600 frames
return Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
}
// Method to handle player death (recycled from SkibidiTitan.js)
kill(target) {
target.canvas.style.transition = "transform 0.5s";
target.canvas.style.transform = "rotate(-90deg) translate(-26px, 0%)";
GameEnv.playSound("PlayerDeath");
if (target.state.isDying === false) {
target.state.isDying = true;
setTimeout(async () => {
await GameControl.transitionToLevel(GameEnv.levels[GameEnv.levels.indexOf(GameEnv.currentLevel)]);
console.log("level restart");
target.state.isDying = false;
}, 900);
}
}
update() {
super.update();
// Check if the Titan should take damage
this.hpLoss();
// Check if the Titan should die and disappear
this.handleDeath();
// Only continue if Titan is not dead
if (!this.state.isDead) {
// Health bar update
this.titanHealthBar.updateTitanHealth(
this.currentHp,
this.x,
this.y,
this.canvas.width,
this.canvas.height
);
this.titanHealthBar.update();
// Laser-related logic
this.immune = 1;
if (this.debounce > 0) {
this.debounce = -240;
// Show red placeholder boxes in random areas
const explosionCount = 5;
for (let i = 0; i < explosionCount; i++) {
// Set up explosions as HTML DOM objects
// Will fix this later with actual explosion img
const explosionX = Math.random() * GameEnv.innerWidth;
const explosionY = 0.65 * GameEnv.innerHeight;
const explosion = document.createElement('div');
explosion.style.position = 'absolute';
explosion.style.left = `${explosionX}px`;
explosion.style.top = `${explosionY}px`;
explosion.style.width = '100px';
explosion.style.height = '100px';
explosion.style.backgroundColor = 'red';
explosion.style.opacity = 0;
explosion.style.transition = 'opacity 1s ease-in-out';
document.body.appendChild(explosion);
// Fade in the explosion
setTimeout(() => {
explosion.style.opacity = 1;
}, 100);
// Flash the explosion and check player position
setTimeout(() => {
explosion.style.opacity = 0;
const playerX = GameEnv.PlayerPosition.playerX;
const playerY = GameEnv.PlayerPosition.playerY;
const distance = Math.sqrt(Math.pow(playerX - explosionX, 2) + Math.pow(playerY - explosionY, 2));
if (distance < 100) {
this.kill(GameEnv.player);
}
}, 1100);
// Remove explosion after some time
setTimeout(() => {
document.body.removeChild(explosion);
}, 2000);
}
}
// Can probably delete this part right here
// Additional difficulty-specific adjustments
if (GameEnv.difficulty === "hard") {
this.canvas.style.filter = "invert(100%)";
this.canvas.style.scale = 1.25;
this.immune = 1;
} else if (GameEnv.difficulty === "impossible") {
this.canvas.style.filter = 'brightness(1000%)';
this.immune = 1;
}
// Positioning and movement adjustments
this.y = 0.25 * GameEnv.innerHeight;
this.playerBottomCollision = false;
// Update the ticker
this.debounce += 1;
}
}
}
export default BossFight;
Andd a bunch of changes to a new level!! It may be noted that it’s actually quite jank. I also update the local storage logging for time because that was logging time every tick and slowing down the game BUNCH.
Anyhow, the boss fight functions, but is still a bit jank. I couldn’t get an image explosion set up, so instead I’ve implemented red placeholder blocks. It may also be noted that I was having weird problems trying to erase the laser
object from the GameSetter, and I’m still not sure why it even does that when laser is not used. I’m guessing it was some dependency in some far off referenced file that JS refuses to mark as a problem. Why JS, whyyyyyy.
I’ve updated these files with 694 additions and 5 deletions:
assets/js/platformer/BossFight.js
assets/js/platformer/GameControl.js
assets/js/platformer/GameSetterBoss.js
(where I updated some hitboxes, effectively breaking the level but whatever, will be fixed soon)assets/js/platformer/GameSetterBossFight
assets/js/platformer/GameSetup.js
images/platformer/explosion.png
images/platformer/oryx.png
There’s quite a few improvements I’ve got to make:
- Update hitboxes of explosions
- Add real explosion images
- My idea to finish the boss fight is to run around collecting some item that slowly damages the boss.
- Fix oryx (boss) to move, and create an overlay to hide the bottom half of his body
- Random QoL stuff I discover I have to fix along the way
Day 5
Mainly just presentations and meetings. Nothing much for development. Started a new Rust project. See next week’s blog! I don’t have much code to show for it though unfortunately.