chore: hopefully made the repo more consistent

This commit is contained in:
koneko 2025-01-12 01:22:13 +01:00
parent 2817f689fe
commit 6875a9b0e8
14 changed files with 82 additions and 80 deletions

View File

@ -81,6 +81,7 @@ export default class GameAssets {
}
private static async LoadMissions() {
// When adding missions, make sure to keep order.
GameAssets.Missions = [await this.LoadMission('/assets/missions/mission_01.json')];
}

View File

@ -12,9 +12,6 @@ import NotificationManager from './game/NotificationManager';
export class Engine {
public static app: PIXI.Application;
public static GameMaster: GameMaster;
public static WindowHeight: number;
public static WindowWidth: number;
public static AspectRatio: number = 16 / 9;
public static Grid: Grid;
public static WaveManager: WaveManager;
public static TowerManager: TowerManager;
@ -22,11 +19,14 @@ export class Engine {
public static NotificationManager: NotificationManager;
public static GameScene: GameScene;
public static latestCommit: string;
public static GridCellSize: number = 64;
public static GridColumns: number = 25;
public static GridRows: number = 17;
}
export default class GameMaster {
public currentScene: Scene;
private gameScene: GameScene;
private GameObjects: GameObject[] = [];
constructor() {
@ -35,12 +35,12 @@ export default class GameMaster {
public _CreateGuiObject(object: GuiObject) {
this.currentScene.gui.push(object);
Engine.app.stage.addChild(object.container);
Engine.GameMaster.currentScene.stage.addChild(object.container);
}
public _RemoveGuiObject(object: GuiObject) {
this.currentScene.gui.splice(this.currentScene.gui.indexOf(object), 1);
Engine.app.stage.removeChild(object.container);
Engine.GameMaster.currentScene.stage.removeChild(object.container);
}
public changeScene(newScene: Scene) {

View File

@ -44,7 +44,7 @@ export class FadeInOut extends Animateable {
} else {
this.pixiObject.alpha -= 1 / this.fadeTime;
}
if (this.ticks >= this.fadeTime || this.pixiObject.alpha <= 0) this.Finish();
if (this.ticks >= this.fadeTime) this.Finish();
}
}
@ -67,14 +67,11 @@ export class Tween extends Animateable {
public update(deltaMS) {
super.update(deltaMS);
this.ticks += deltaMS;
// Calculate the fraction of time elapsed
const progress = this.ticks / (this.tweenTime * 16.67); // Assuming 60 FPS, 1 frame = 16.67ms
const progress = this.ticks / (this.tweenTime * 16.67);
// Update the position based on the progress
this.pixiObject.x = (this.goalX - this.pixiObject.x) * progress + this.pixiObject.x;
this.pixiObject.y = (this.goalY - this.pixiObject.y) * progress + this.pixiObject.y;
// Finish the animation if the time is up
if (this.ticks >= this.tweenTime * 16.67) {
this.pixiObject.x = this.goalX;
this.pixiObject.y = this.goalY;
@ -89,6 +86,9 @@ export class AnimationManager {
this.AnimationQueue.push(animatable);
}
public update(ms) {
// Explanation: we go from the back of the array so that we can remove items freely without messing with
// foreach. From experience it gets a little screwy if you do this.AnimationQueue.forEach and doesn't properly
// remove elements.
for (let i = this.AnimationQueue.length - 1; i >= 0; i--) {
const anim = this.AnimationQueue[i];
if (anim.finished) {

View File

@ -30,24 +30,26 @@ export default class Creep extends GameObject {
constructor(creepType: CreepType, path: PathDefinition, id) {
super();
this.creepType = creepType;
// Structured clone is used just in case, so that 1 creep doesnt alter stats for all creeps.
this.stats = structuredClone(Assets.CreepStats[this.creepType]);
this.sprite = new PIXI.Sprite({
texture: GameAssets.BasicCreepTexture,
});
this.id = id;
// because wave manager spawns all instantly and i dont want
// it to look like a shit game (they all spawn in top left corner)
// i want to hide minion - mario
// Explanation: WaveManager spawns all creeps instantly, and since I don't want
// them to show up on the beginning while they are waiting, I put them outside the visible
// part of the currentScene.stage map.
this.container.x = -70;
this.container.y = -50;
this.sprite.width = 64;
this.sprite.height = 64;
this.sprite.width = Engine.GridCellSize;
this.sprite.height = Engine.GridCellSize;
this.speed = this.stats.speed;
this.health = this.stats.health;
this.maxHealth = this.stats.health;
this.path = path;
this.x = path[0][1] * 64 + 32; // centered
this.y = path[0][0] * 64 + 32;
// Added + 32 to center them.
this.x = path[0][1] * Engine.GridCellSize + Engine.GridCellSize / 2;
this.y = path[0][0] * Engine.GridCellSize + Engine.GridCellSize / 2;
Engine.GameScene.events.on(CreepEvents.TakenDamage, (creepID, damage) => {
if (creepID != this.id) return;
this.health -= damage;
@ -60,6 +62,8 @@ export default class Creep extends GameObject {
if (this.health <= 0) {
Engine.GameScene.events.emit(CreepEvents.Died, this.maxHealth, this);
this.destroy();
// The reason for setting this.dead instead of deleting self is because
// I need to allow WaveManager/Grid to manage their death and keep array up to date.
this.dead = true;
return;
}
@ -72,8 +76,9 @@ export default class Creep extends GameObject {
const currentCell = this.path[this.pathIndex];
const targetCell = this.path[this.pathIndex + 1];
const targetX = targetCell[1] * 64 + 32;
const targetY = targetCell[0] * 64 + 32;
// Added + 32 for centering.
const targetX = targetCell[1] * Engine.GridCellSize + Engine.GridCellSize / 2;
const targetY = targetCell[0] * Engine.GridCellSize + Engine.GridCellSize / 2;
const directionX = targetCell[1] - currentCell[1];
const directionY = targetCell[0] - currentCell[0];
let deltaX = this.speed * elapsedMS * directionX;
@ -114,7 +119,7 @@ export default class Creep extends GameObject {
}
}
public override destroy() {
public destroy() {
super.destroy();
this.container.removeChildren();
}

View File

@ -19,10 +19,10 @@ export class Cell extends GameObject {
this.row = row;
this.column = column;
this.isPath = isPath;
this.bb.x = this.column * 64;
this.bb.y = this.row * 64;
this.bb.width = 64;
this.bb.height = 64;
this.bb.x = this.column * Engine.GridCellSize;
this.bb.y = this.row * Engine.GridCellSize;
this.bb.width = Engine.GridCellSize;
this.bb.height = Engine.GridCellSize;
Engine.Grid.container.addChild(this.container);
this.container.x = this.bb.x;
this.container.y = this.bb.y;
@ -35,7 +35,7 @@ export class Cell extends GameObject {
this.clickDetector.fill({ color: 0xff0000, alpha: 0 });
this.container.addChild(this.clickDetector);
this.clickDetector.onpointerdown = (e) => {
Engine.Grid._gridCellClicked(row, column);
Engine.Grid.onGridCellClicked(row, column);
};
if (!GameAssets.DebuggingEnabled) return;
@ -92,9 +92,9 @@ export class Grid extends GameObject {
Engine.Grid = this;
this.bb.x = 0;
this.bb.y = 0;
this.bb.width = 64 * 30;
this.bb.height = 64 * 17;
Engine.app.stage.addChild(this.container);
this.bb.width = Engine.GridCellSize * Engine.GridColumns;
this.bb.height = Engine.GridCellSize * Engine.GridRows;
Engine.GameMaster.currentScene.stage.addChild(this.container);
let background = new PIXI.Sprite(GameAssets.MissionBackgrounds[missionIndex]);
background.x = 0;
@ -126,7 +126,6 @@ export class Grid extends GameObject {
this.gridShown = !this.gridShown;
}
public addCreep(creep: Creep) {
console.log('ADD CREEP');
this.creeps.push(creep);
creep.events.on(CreepEvents.Died, (diedCreep) => {
this.onCreepDiedOrEscaped(diedCreep);
@ -150,9 +149,5 @@ export class Grid extends GameObject {
public getCellByRowAndCol(row, column) {
return this.cells.filter((item) => item.row == row && item.column == column)[0];
}
public _gridCellClicked(row, column) {
// function will be assigned by GameScene, but must be predefined here.
this.onGridCellClicked(row, column);
}
public onGridCellClicked(row, column) {}
}

View File

@ -50,7 +50,7 @@ export default class MissionStats extends GameObject {
this.gold = initialGold;
this.container.x = 0;
this.container.y = 20;
Engine.app.stage.addChild(this.container);
Engine.GameMaster.currentScene.stage.addChild(this.container);
this.healthText = new PIXI.Text({
text: `${this.hp}`,
style: new PIXI.TextStyle({

View File

@ -58,10 +58,8 @@ export default class NotificationManager extends GameObject {
}
public Notify(text, type: NotificationType) {
let x = 0;
let y = this.notifications.length * 30;
let y = this.notifications.length * 32;
this.notifications.push(new Notification(text, type, x, y, this.ticks + 180));
console.log('CREATED NOTIFICATION ');
console.log(text, type, x, y, this.ticks + 180);
}
public update(_) {
this.ticks++;

View File

@ -20,7 +20,6 @@ export default class Projectile extends GameObject {
public timeToLive: number = 1;
constructor(x, y, spriteTexture, angle, damage) {
super();
console.log('I SHOOTTED!');
this.x = x;
this.y = y;
this.damage = damage;
@ -30,7 +29,7 @@ export default class Projectile extends GameObject {
this.container.x = this.x;
this.container.y = this.y;
this.container.addChild(this.sprite);
Engine.app.stage.addChild(this.container);
Engine.GameMaster.currentScene.stage.addChild(this.container);
this.angle = angle;
this.speed = 0.9;
@ -59,7 +58,6 @@ export default class Projectile extends GameObject {
}
public onCollide(creep) {
console.log('COLLIDED WITH' + creep);
Engine.GameScene.events.emit(CreepEvents.TakenDamage, creep.id, this.damage);
}
@ -68,12 +66,5 @@ export default class Projectile extends GameObject {
let mybb = this.copyContainerToBB();
let otherbb = creep.copyContainerToBB();
return mybb.getBounds().intersects(otherbb.getBounds());
// console.log(boundsA, boundsB);
// return (
// boundsA.x < boundsB.x + boundsB.width &&
// boundsA.x + boundsA.width > boundsB.x &&
// boundsA.y < boundsB.y + boundsB.height &&
// boundsA.y + boundsA.height > boundsB.y
// );
}
}

View File

@ -50,36 +50,40 @@ export class Tower extends GameObject {
let parent: Cell = Engine.Grid.getCellByRowAndCol(row, column);
this.sprite = new PIXI.Sprite({
texture: texture,
height: 64,
width: 64,
height: Engine.GridCellSize,
width: Engine.GridCellSize,
zIndex: 10,
});
this.container.addChild(this.sprite);
parent.container.addChild(this.container);
parent.clickDetector.onmouseenter = (e) => {
this.graphics.circle(this.column * 64 + 32, this.row * 64 + 32, this.definition.stats.range * 64);
this.graphics.circle(
this.column * Engine.GridCellSize + Engine.GridCellSize / 2,
this.row * Engine.GridCellSize + Engine.GridCellSize / 2,
this.definition.stats.range * Engine.GridCellSize
);
this.graphics.fill({ color: 0xff0000, alpha: 0.5 });
};
parent.clickDetector.onmouseleave = (e) => {
this.graphics.clear();
};
Engine.app.stage.addChild(this.graphics);
Engine.GameMaster.currentScene.stage.addChild(this.graphics);
}
public GetCreepsInRange() {
let creeps = Engine.Grid.creeps;
return creeps.filter((creep) => {
const x = creep.x;
const y = creep.y;
const towerX = this.column * 64 + 32;
const towerY = this.row * 64 + 32;
const radius = this.definition.stats.range * 64;
const towerX = this.column * Engine.GridCellSize + Engine.GridCellSize / 2;
const towerY = this.row * Engine.GridCellSize + Engine.GridCellSize / 2;
const radius = this.definition.stats.range * Engine.GridCellSize;
const d = distance(towerX, towerY, x, y);
return d < radius;
});
}
public Shoot(creep: Creep) {
let x = this.column * 64 + 32;
let y = this.row * 64 + 32;
let x = this.column * Engine.GridCellSize + Engine.GridCellSize / 2;
let y = this.row * Engine.GridCellSize + Engine.GridCellSize / 2;
let angle = calculateAngleToPoint(x, y, creep.x, creep.y);
this.projectiles.push(
new Projectile(x, y, GameAssets.BasicProjectileTexture, angle, this.definition.stats.damage)

View File

@ -18,9 +18,6 @@ export default class GemTab extends GuiObject {
rightWidth: 1000,
bottomHeight: 1000,
});
// this.towerTabSprite = new PIXI.Sprite({
// texture: GameAssets.FrameBackground,
// });
this.gemTabSprite.x = 0;
this.gemTabSprite.y = 0;
this.gemTabSprite.width = this.bounds.width;

View File

@ -10,7 +10,6 @@ import NotificationManager from './classes/game/NotificationManager';
(async () => {
const app = new PIXI.Application();
Engine.app = app;
log('main - init()');
await app.init({
width: 1920, // Base width
height: 1080, // Base height
@ -19,7 +18,6 @@ import NotificationManager from './classes/game/NotificationManager';
backgroundColor: 0xffffff,
sharedTicker: true,
});
log('main - init() complete');
document.body.appendChild(app.canvas);

View File

@ -10,7 +10,6 @@ import Scene from './Scene';
import * as PIXI from 'pixi.js';
import MissionStats from '../classes/game/MissionStats';
import TowerManager from '../classes/game/TowerManager';
import NotificationManager from '../classes/game/NotificationManager';
import { MissionPickerScene } from './MissionPicker';
enum RoundMode {
@ -30,6 +29,7 @@ export class GameScene extends Scene {
private currentRound: number = 0;
private isWaveManagerFinished: boolean = false;
private playerWon: boolean = false;
private destroyTicker: boolean = false;
constructor(name: string) {
super();
@ -45,9 +45,11 @@ export class GameScene extends Scene {
this.ticker = new PIXI.Ticker();
this.ticker.maxFPS = 60;
this.ticker.minFPS = 30;
this.ticker.add(() => this.update(this.ticker.elapsedMS)); // bruh
this.ticker.add(() => {
if (this.update) this.update(this.ticker.elapsedMS);
});
this.ticker.start();
const SidebarRect = new PIXI.Rectangle(64 * 30 - 360, 0, 360, Engine.app.canvas.height);
const SidebarRect = new PIXI.Rectangle(Engine.GridCellSize * 30 - 360, 0, 360, Engine.app.canvas.height);
const changeRoundButtonRect = new PIXI.Rectangle(50, Engine.app.canvas.height - 100, 310, 100);
new Grid(this.mission.gameMap, this.missionIndex);
new TowerManager();
@ -85,6 +87,13 @@ export class GameScene extends Scene {
this.MissionStats = new MissionStats(100, 200);
}
public update(elapsedMS) {
if (this.isGameOver) {
if (this.destroyTicker) {
this.destroyTicker = false;
this.ticker.destroy();
}
return;
}
Engine.WaveManager.update(elapsedMS);
Engine.Grid.update(elapsedMS);
Engine.TowerManager.update(elapsedMS);
@ -110,16 +119,13 @@ export class GameScene extends Scene {
if (this.MissionStats.getHP() <= 0) {
this.isGameOver = true;
this.ShowScoreScreen(true);
this.ticker.stop();
} else if (this.playerWon) {
this.isGameOver = true;
this.ShowScoreScreen(false);
this.ticker.stop();
}
}
private ShowScoreScreen(lost) {
this.ticker.stop();
// TODO: show to player for real
if (lost) {
console.log('LOSE!');
@ -141,9 +147,16 @@ export class GameScene extends Scene {
}
}
public destroy(): void {
super.destroy();
this.isGameOver = true;
this.destroyTicker = true;
Engine.GameScene = null;
}
private ReturnToMain() {
this.destroy();
Engine.app.stage.removeChildren();
Engine.GameMaster.currentScene.stage.removeChildren();
Engine.GameMaster.changeScene(new MissionPickerScene());
}
public onTowerPlaced() {}

View File

@ -39,7 +39,7 @@ export class MainScene extends Scene {
},
});
text.x = text.x - text.width / 5;
Engine.app.stage.addChild(text);
Engine.GameMaster.currentScene.stage.addChild(text);
let text2 = new PIXI.Text({
x: 0,
y: 0,
@ -50,17 +50,17 @@ export class MainScene extends Scene {
fontWeight: 'bold',
},
});
Engine.app.stage.addChild(text2);
Engine.GameMaster.currentScene.stage.addChild(text2);
const button01 = new Button(NewGameButton.rect, NewGameButton.caption, NewGameButton.texture, true);
button01.onClick = (e) => {
Engine.app.stage.removeChild(text);
Engine.app.stage.removeChild(text2);
Engine.GameMaster.currentScene.stage.removeChild(text);
Engine.GameMaster.currentScene.stage.removeChild(text2);
Engine.GameMaster.changeScene(new MissionPickerScene());
};
let b2 = new Button(SettingsButton.rect, SettingsButton.caption, SettingsButton.texture, true);
b2.onClick = (e) => {
alert('Does nothing for now, just placeholder.');
Engine.NotificationManager.Notify('Not finished.', 'info');
};
}
}

View File

@ -1,21 +1,21 @@
import { Engine } from '../classes/Bastion';
import GuiObject from '../classes/GuiObject';
import * as PIXI from 'pixi.js';
export default class Scene {
public stage: PIXI.Container = new PIXI.Container();
public gui: GuiObject[] = [];
private _events: PIXI.EventEmitter = new PIXI.EventEmitter();
constructor() {
Engine.app.stage.addChild(this.stage);
}
public destroy() {
this.stage.destroy();
this.gui.forEach((element) => {
element.destroy();
});
}
public GetGuiObject(object: GuiObject) {
return this.gui.find((obj) => obj == object);
}
public GetGuiObjectByName(name: string) {
return this.gui.filter((obj) => obj.name == name);
}
public get events(): PIXI.EventEmitter {
return this._events;