From af81a95a54ca85d078e0addd80a02a72a7b6b13e Mon Sep 17 00:00:00 2001 From: koneko <67551503+koneko@users.noreply.github.com> Date: Sun, 6 Oct 2024 18:32:39 +0200 Subject: [PATCH] attempts --- .prettierrc | 3 +- public/assets/creeps/basic.jpg | Bin 0 -> 29836 bytes public/assets/missions/mission_01.json | 12 ++-- src/base/Assets.ts | 8 ++- src/base/GameObject.ts | 14 ++-- src/components/Creep.ts | 62 +++++++++++++--- src/components/Grid.ts | 52 +++++++++++--- src/components/MissionStats.ts | 10 +-- src/components/WaveManager.ts | 82 +++++++++++++++------ src/scenes/GameScene.ts | 95 ++++++++++++++++++------- 10 files changed, 248 insertions(+), 90 deletions(-) create mode 100644 public/assets/creeps/basic.jpg diff --git a/.prettierrc b/.prettierrc index f914884..141d99b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,6 @@ "trailingComma": "es5", "tabWidth": 4, "semi": true, - "singleQuote": true + "singleQuote": true, + "printWidth": 120 } diff --git a/public/assets/creeps/basic.jpg b/public/assets/creeps/basic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c803937783fec58ac09cac5785668bf67076a677 GIT binary patch literal 29836 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 D targetCell[1] + 0.5) { + // limit to center of target cell + deltaX = targetCell[1] + 0.5 - this.x; + this.pathIndex++; + } + if (this.y + deltaY > targetCell[0] + 0.5) { + // limit to center of target cell + deltaY = targetCell[0] + 0.5 - this.y; + this.pathIndex++; + } + this.x += deltaX; + this.y += deltaY; + console.log('creep moved', deltaX, deltaY); + this.events.emit(CreepEvents.Moved, this); + } + + public takeDamage(amount: number) { + this.health -= amount; + if (this.health < 0) { + this.events.emit(CreepEvents.Died, this); + } } - public update() {} protected draw() { this.container.removeChildren(); - + const sprite = new PIXI.Sprite(Assets.BasicCreepTexture); + sprite.x = 0; + sprite.y = 0; + sprite.width = this.bounds.width; + sprite.height = this.bounds.height; this.container.x = this.bounds.x; this.container.y = this.bounds.y; } diff --git a/src/components/Grid.ts b/src/components/Grid.ts index d6301d8..634ad3b 100644 --- a/src/components/Grid.ts +++ b/src/components/Grid.ts @@ -1,6 +1,7 @@ import * as PIXI from 'pixi.js'; import GameObject from '../base/GameObject'; import { GameMapDefinition, TerrainType } from '../base/Definitions'; +import Creep, { CreepEvents } from './Creep'; export class Cell extends GameObject { public type: TerrainType; @@ -8,13 +9,7 @@ export class Cell extends GameObject { public column: number; public isPath: boolean = false; - constructor( - type: TerrainType, - row: number, - column: number, - isPath: boolean, - bounds?: PIXI.Rectangle - ) { + constructor(type: TerrainType, row: number, column: number, isPath: boolean, bounds?: PIXI.Rectangle) { super(bounds); this.type = type; this.row = row; @@ -44,6 +39,7 @@ export class Cell extends GameObject { export class Grid extends GameObject { private gameMap: GameMapDefinition; private cells: Cell[] = []; + private creeps: Creep[] = []; constructor(map: GameMapDefinition, bounds?: PIXI.Rectangle) { super(bounds); @@ -52,9 +48,7 @@ export class Grid extends GameObject { for (let y = 0; y < this.gameMap.rows; y++) { for (let x = 0; x < this.gameMap.columns; x++) { let type = this.gameMap.cells[x][y]; - const isPath = this.gameMap.paths.some((path) => - path.some((p) => p[0] === x && p[1] === y) - ); + const isPath = this.gameMap.paths.some((path) => path.some((p) => p[0] === x && p[1] === y)); if (isPath) type = TerrainType.Restricted; let cell = new Cell(type, x, y, isPath); this.cells.push(cell); @@ -63,7 +57,33 @@ export class Grid extends GameObject { console.log(this.cells); this.draw(); } - + public addCreep(creep: Creep) { + this.creeps.push(creep); + creep.events.on(CreepEvents.Moved, (movedCreep) => { + this.onCreepMoved(movedCreep); + }); + creep.events.on(CreepEvents.Died, (diedCreep) => { + this.onCreepDiedOrEscaped(diedCreep); + }); + creep.events.on(CreepEvents.Escaped, (escapedCreep) => { + this.onCreepDiedOrEscaped(escapedCreep); + }); + this.draw(); + } + private onCreepMoved(movedCreep: Creep) { + movedCreep.setBounds( + new PIXI.Rectangle( + this.gridUnitsToPixels(movedCreep.x), + this.gridUnitsToPixels(movedCreep.y), + this.gridUnitsToPixels(0.3), + this.gridUnitsToPixels(0.3) + ) + ); + } + private onCreepDiedOrEscaped(creep: Creep) { + this.creeps.splice(this.creeps.indexOf(creep), 1); + this.draw(); + } protected draw() { console.log('Drawing Grid', this.bounds); this.container.removeChildren(); @@ -80,6 +100,16 @@ export class Grid extends GameObject { ); this.container.addChild(cell.container); } + for (const creep of this.creeps) { + creep.setBounds( + this.gridUnitsToPixels(creep.x), + this.gridUnitsToPixels(creep.y), + this.gridUnitsToPixels(0.3), + this.gridUnitsToPixels(0.3) + ); + // console.log(creep.getBounds()); + this.container.addChild(creep.container); + } this.container.x = this.bounds.x; this.container.y = this.bounds.y; } diff --git a/src/components/MissionStats.ts b/src/components/MissionStats.ts index 07ef765..0456d0c 100644 --- a/src/components/MissionStats.ts +++ b/src/components/MissionStats.ts @@ -5,6 +5,10 @@ export default class MissionStats extends GameObject { private hp: number = 100; private gold: number = 0; + public getHP() { + return this.hp; + } + public setHP(hp: number) { this.hp = hp; this.draw(); @@ -15,11 +19,7 @@ export default class MissionStats extends GameObject { this.draw(); } - constructor( - initialHP: number, - initialGold: number, - bounds?: PIXI.Rectangle - ) { + constructor(initialHP: number, initialGold: number, bounds?: PIXI.Rectangle) { super(bounds); this.hp = initialHP; this.gold = initialGold; diff --git a/src/components/WaveManager.ts b/src/components/WaveManager.ts index e14418f..c02f432 100644 --- a/src/components/WaveManager.ts +++ b/src/components/WaveManager.ts @@ -1,36 +1,72 @@ -import { - CreepType, - MissionRoundDefinition, - PathDefinition, -} from '../base/Definitions'; -import Creep from './Creep'; +import { CreepType, MissionRoundDefinition, PathDefinition } from '../base/Definitions'; +import * as PIXI from 'pixi.js'; +import Creep, { CreepEvents } from './Creep'; + +export enum WaveManagerEvents { + CreepSpawned = 'creepSpawned', + Finished = 'finished', +} + +type CreepInstance = { + creep: Creep; + tickToSpawnAt: number; + spawned: boolean; +}; export default class WaveManager { // Doesn't need to extend GameObject since it does not render - private currentWave: number; - private creeps: Creep[] = []; + // public currentRound: number = 0; + private creeps: CreepInstance[] = []; private rounds: MissionRoundDefinition[]; private paths: PathDefinition[]; - private spawnIntervalTicks: number; - private firstCreepSpawnTick: number; - public ticks: number = 0; + private ticks: number = 0; + private started: boolean = false; + public finished: boolean = false; + public events = new PIXI.EventEmitter(); constructor(rounds: MissionRoundDefinition[], paths: PathDefinition[]) { this.rounds = rounds; this.paths = paths; } - private updateCreeps() { + public start(roundIndex) { + this.started = true; + this.ticks = 0; + this.creeps = []; + this.finished = false; + let tickToSpawnAt = 0; + this.rounds[roundIndex].waves.forEach((wave) => { + tickToSpawnAt += wave.firstCreepSpawnTick; + wave.creeps.forEach((creep) => { + const creepObj = new Creep(creep, this.paths[0]); + const creepInstance = { + creep: creepObj, + tickToSpawnAt, + spawned: false, + }; + tickToSpawnAt += wave.spawnIntervalTicks; + this.creeps.push(creepInstance); + }); + }); + console.log(this.creeps); + } + public end() { + this.started = false; + } + public update(elapsedMS: number): void { + if (this.started == false) return; + this.ticks += elapsedMS; this.creeps.forEach((creep) => { - creep.update(); - // TODO: updating here is fine, change to make spawning emit an event - // which GameScene will catch and send to Grid who will draw the creep - // based on the coordinates that the creep calculates. + if (!creep.spawned && creep.tickToSpawnAt <= this.ticks) { + creep.spawned = true; + this.events.emit(WaveManagerEvents.CreepSpawned, creep.creep); + console.log('Wave manager creep spawned, ', creep, this.ticks); + if (!this.finished && this.creeps.every((creep) => creep.spawned)) { + this.finished = true; + console.log('wave maanger finisehd'); + this.events.emit(WaveManagerEvents.Finished); + } + } else if (creep.spawned) { + creep.creep.update(elapsedMS); + } }); } - public update(fps): void { - if (this.creeps.length != 0) this.updateCreeps(); - this.ticks++; - if (this.ticks == 200) { - this.creeps.push(new Creep(CreepType.Basic, this.paths[0])); - } - } } diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index f9fb443..1e56f6b 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -1,56 +1,87 @@ import Button from '../base/Button'; import { MissionDefinition } from '../base/Definitions'; +import Creep from '../components/Creep'; import { Grid } from '../components/Grid'; import MissionStats from '../components/MissionStats'; -import WaveManager from '../components/WaveManager'; +import WaveManager, { WaveManagerEvents } from '../components/WaveManager'; import SceneBase from './SceneBase'; import * as PIXI from 'pixi.js'; +enum RoundMode { + Purchase = 0, + Combat = 1, +} + export default class GameScene extends SceneBase { private ticker: PIXI.Ticker; private stats: MissionStats; private grid: Grid; - public waveManager: WaveManager; + private waveManager: WaveManager; + private roundMode = RoundMode.Purchase; + private changeRoundButton: Button; + private currentRound: number = 0; constructor(mission: MissionDefinition, bounds: PIXI.Rectangle) { super(bounds); - this.waveManager = new WaveManager( - mission.rounds, - mission.gameMap.paths - ); + this.waveManager = new WaveManager(mission.rounds, mission.gameMap.paths); + this.waveManager.events.on(WaveManagerEvents.CreepSpawned, (creep: Creep) => { + this.grid.addCreep(creep); + }); this.stats = new MissionStats(100, 200); this.grid = new Grid(mission.gameMap); this.ticker = new PIXI.Ticker(); this.ticker.maxFPS = 60; this.ticker.minFPS = 30; - this.ticker.add(() => this.update(this.ticker.FPS)); // bruh + this.ticker.add(() => this.update(this.ticker.elapsedMS)); // bruh this.ticker.start(); + this.changeRoundButton = new Button('Start', new PIXI.Color('white'), true); + this.changeRoundButton.events.on('click', () => { + console.log('clicked'); + this.changeRoundButton.setEnabled(false); + this.changeRoundButton.setCaption('[X]'); + this.setRoundMode(RoundMode.Combat); + }); this.draw(); } - private getStatusBounds(): PIXI.Rectangle { - // Top / Center - return new PIXI.Rectangle(this.bounds.width / 2 - 200 / 2, 0, 200, 100); - } - - private getGridBounds(): PIXI.Rectangle { - // Center / Center - return new PIXI.Rectangle( - this.bounds.width / 2 - 600 / 2, - this.bounds.height / 2 - 600 / 2, - 600, - 600 - ); - } - public destroy() { super.destroy(); this.ticker.stop(); this.ticker.destroy(); } - public update(fps) { - this.waveManager.update(fps); + public update(elapsedMS: number) { + if (this.checkGameOver()) return; + this.waveManager.update(elapsedMS); + this.checkToEndCombat(); + } + + private setRoundMode(roundMode: RoundMode) { + this.roundMode = roundMode; + if (this.roundMode == RoundMode.Combat) { + this.waveManager.start(this.currentRound); + } else { + this.waveManager.end(); + } + } + + private checkToEndCombat() { + let isFinished = false; // todo: implement + if (!this.waveManager.finished) { + isFinished = false; + } + if (isFinished) { + this.currentRound++; + this.setRoundMode(RoundMode.Purchase); + } + } + + private checkGameOver() { + if (this.stats.getHP() <= 0) { + // TODO: end game + return true; + } + return false; } protected draw() { @@ -62,9 +93,25 @@ export default class GameScene extends SceneBase { this.container.addChild(g); this.stats.setBounds(this.getStatusBounds()); this.grid.setBounds(this.getGridBounds()); + this.changeRoundButton.setBounds(this.getChangeRoundButtonBounds()); this.container.addChild(this.stats.container); this.container.addChild(this.grid.container); + this.container.addChild(this.changeRoundButton.container); this.container.x = this.bounds.x; this.container.y = this.bounds.y; } + + private getStatusBounds(): PIXI.Rectangle { + // Top / Center + return new PIXI.Rectangle(this.bounds.width / 2 - 200 / 2, 0, 200, 100); + } + + private getGridBounds(): PIXI.Rectangle { + // Center / Center + return new PIXI.Rectangle(this.bounds.width / 2 - 600 / 2, this.bounds.height / 2 - 600 / 2, 600, 600); + } + private getChangeRoundButtonBounds(): PIXI.Rectangle { + // Center / Center + return new PIXI.Rectangle(this.bounds.width - 300, this.bounds.height - 150, 300, 150); + } }