From 7e9e178dc323315bd7e87acef3f0f1957adf029d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20=C4=8Carapi=C4=87?= Date: Sat, 28 Sep 2024 19:47:03 +0200 Subject: [PATCH] WIP --- public/assets/missions/mission_01.json | 51 ++++++++++++++++ src/base/Assets.ts | 18 ++++++ src/base/Button.ts | 7 +-- src/base/Definitions.ts | 43 +++++++++++++ src/base/Game.ts | 2 +- src/base/GameObject.ts | 19 ++++-- src/components/Grid.ts | 84 ++++++++++++++------------ src/components/MissionStats.ts | 37 +++++++++++- src/scenes/GameScene.ts | 52 ++++++++++++---- src/scenes/MainMenu.ts | 47 ++++++-------- src/scenes/MissionSelectMenu.ts | 34 +++++------ src/scenes/SettingsMenu.ts | 32 ---------- 12 files changed, 288 insertions(+), 138 deletions(-) create mode 100644 public/assets/missions/mission_01.json create mode 100644 src/base/Definitions.ts diff --git a/public/assets/missions/mission_01.json b/public/assets/missions/mission_01.json new file mode 100644 index 0000000..c784932 --- /dev/null +++ b/public/assets/missions/mission_01.json @@ -0,0 +1,51 @@ +{ + "name": "Mission 1", + "description": "This is the first mission", + "gameMap": { + "rows": 10, + "columns": 10, + "cells": [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 0, 0, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [0, 2], + [1, 2], + [2, 2], + [3, 2], + [4, 2], + [5, 2], + [6, 2], + [7, 2], + [8, 2], + [9, 2] + ] + ], + "waves": [ + { + "firstCreepSpawnTick": 120, + "spawnIntervalTicks": 60, + "creeps": [0, 0, 0, 0, 0, 1, 1, 1, 0] + }, + { + "firstCreepSpawnTick": 480, + "spawnIntervalTicks": 60, + "creeps": [0, 0, 0, 0, 0, 1, 1, 1, 0] + }, + { + "firstCreepSpawnTick": 480, + "spawnIntervalTicks": 60, + "creeps": [0, 0, 0, 0, 0, 1, 1, 1, 0] + } + ] + } +} diff --git a/src/base/Assets.ts b/src/base/Assets.ts index 01f7728..c40b167 100644 --- a/src/base/Assets.ts +++ b/src/base/Assets.ts @@ -1,11 +1,29 @@ import * as PIXI from "pixi.js"; +import { MissionDefinition } from "./Definitions"; export default class Assets { public static async LoadAssets() { + console.log("Loading Texture Assets"); Assets.ButtonTexture = await PIXI.Assets.load({ src: "/assets/gui/button_02.png", }); + console.log("Loading Missions"); + await this.LoadMissions(); + } + + private static async LoadMissions() { + Assets.Missions = [ + await this.LoadMission("/assets/missions/mission_01.json"), + ]; + } + + private static async LoadMission(missionUrl: string) { + const res = await fetch(missionUrl); + const mission = await res.json(); + return mission; } public static ButtonTexture: PIXI.Texture; + + public static Missions: MissionDefinition[]; } diff --git a/src/base/Button.ts b/src/base/Button.ts index 4f6e95a..ecb9804 100644 --- a/src/base/Button.ts +++ b/src/base/Button.ts @@ -19,9 +19,9 @@ export default class Button extends GameObject { constructor( caption: string, - bounds: PIXI.Rectangle, color: PIXI.Color, - enabled: boolean = true + enabled: boolean = true, + bounds?: PIXI.Rectangle ) { super(bounds); this.caption = caption; @@ -33,9 +33,6 @@ export default class Button extends GameObject { } protected draw() { - console.log( - `Drawing button ${this.caption} at ${JSON.stringify(this.bounds)}` - ); this.container.removeChildren(); // const button = new PIXI.Graphics(); // button.rect(0, 0, this.bounds.width, this.bounds.height); diff --git a/src/base/Definitions.ts b/src/base/Definitions.ts new file mode 100644 index 0000000..a90b442 --- /dev/null +++ b/src/base/Definitions.ts @@ -0,0 +1,43 @@ +export type MissionDefinition = { + name: string; + description: string; + mapImageUrl: string; + gameMap: GameMapDefinition; +}; + +export type GameMapDefinition = { + rows: number; + columns: number; + cells: TerrainType[][]; + paths: PathDefinition[]; +}; + +export type MissionRoundDefinition = { + waves: WaveDefinition[]; + offeredGems: GemType[]; +}; + +export type WaveDefinition = { + firstCreepSpawnTick: number; + spawnIntervalTicks: number; + creeps: CreepType[]; +}; + +export type PathDefinition = [row: number, column: number]; + +export enum CreepType { + Basic = 0, + Fast = 1, +} + +export enum TerrainType { + Restricted = 0, + Buildable = 1, +} + +export enum GemType { + Fire = 0, + Yeti = 1, + Titalium = 2, + Soulforge = 3, +} diff --git a/src/base/Game.ts b/src/base/Game.ts index 4d85c32..3bcff75 100644 --- a/src/base/Game.ts +++ b/src/base/Game.ts @@ -35,7 +35,7 @@ export default class Game extends GameObject { const missionSelectScene = new MissionMenuSelect(this.bounds); missionSelectScene.events.on("mission", (mission) => { console.log("Mission selected", mission); - this.setScene(new GameScene(this.bounds)); + this.setScene(new GameScene(mission, this.bounds)); }); missionSelectScene.events.on("back", () => { this.onMainMenu(); diff --git a/src/base/GameObject.ts b/src/base/GameObject.ts index d3dd646..a6c01bd 100644 --- a/src/base/GameObject.ts +++ b/src/base/GameObject.ts @@ -6,8 +6,19 @@ export default abstract class GameObject { private _events: PIXI.EventEmitter = new PIXI.EventEmitter(); - public setBounds(x: number, y: number, width: number, height: number) { - this.bounds = new PIXI.Rectangle(x, y, width, height); + public setBounds(bounds: PIXI.Rectangle): void; + public setBounds(x: number, y: number, width: number, height: number): void; + public setBounds( + boundsOrX: PIXI.Rectangle | number, + y?: number, + width?: number, + height?: number + ) { + if (boundsOrX instanceof PIXI.Rectangle) { + this.bounds = boundsOrX; + } else { + this.bounds = new PIXI.Rectangle(boundsOrX, y, width, height); + } this.triggerBoundsChanged(); // GameObject implements this. } @@ -32,8 +43,8 @@ export default abstract class GameObject { protected abstract draw(): void; - constructor(bounds: PIXI.Rectangle) { - this.bounds = bounds; + constructor(bounds?: PIXI.Rectangle) { + this.bounds = bounds ?? new PIXI.Rectangle(0, 0, 0, 0); this._container = new PIXI.Container(); } } diff --git a/src/components/Grid.ts b/src/components/Grid.ts index ebe12ba..db03fc3 100644 --- a/src/components/Grid.ts +++ b/src/components/Grid.ts @@ -1,18 +1,22 @@ import * as PIXI from "pixi.js"; import GameObject from "../base/GameObject"; - -export enum CellType { - Path, - NoBuild, - Build, - Undefined, -} +import { GameMapDefinition, TerrainType } from "../base/Definitions"; export class Cell extends GameObject { - public type: CellType; - constructor(bounds: PIXI.Rectangle, type: CellType) { + public type: TerrainType; + public row: number; + public column: number; + + constructor( + type: TerrainType, + row: number, + column: number, + bounds?: PIXI.Rectangle + ) { super(bounds); this.type = type; + this.row = row; + this.column = column; this.draw(); } @@ -21,17 +25,11 @@ export class Cell extends GameObject { let g = new PIXI.Graphics(); g.rect(0, 0, this.bounds.width, this.bounds.height); switch (this.type) { - case CellType.Path: - g.fill(0x00ff00); - break; - case CellType.NoBuild: + case TerrainType.Restricted: g.fill(0xff0000); break; - case CellType.Build: - g.fill(0x0000ff); - break; - case CellType.Undefined: - g.fill(0x000000); + case TerrainType.Buildable: + g.fill(0x00ff00); break; } this.container.addChild(g); @@ -41,35 +39,45 @@ export class Cell extends GameObject { } export class Grid extends GameObject { - public rows: number; - public columns: number; - public cells: Array; + private gameMap: GameMapDefinition; + private cells: Cell[] = []; - constructor(bounds: PIXI.Rectangle, rows, columns) { + constructor(map: GameMapDefinition, bounds?: PIXI.Rectangle) { super(bounds); - this.rows = rows; - this.columns = columns; - for (let y = 0; y < rows; y++) { - for (let x = 0; x < columns; x++) { - let cell = new Cell( - new PIXI.Rectangle( - x, - y, - this.gridUnitsToPixels(1), - this.gridUnitsToPixels(1) - ), - CellType.Undefined - ); + this.gameMap = map; + for (let y = 0; y < this.gameMap.rows; y++) { + for (let x = 0; x < this.gameMap.columns; x++) { + let cell = new Cell(this.gameMap.cells[x][y], x, y); this.cells.push(cell); } } + console.log(this.cells); + this.draw(); } - protected draw() {} + protected draw() { + console.log("Drawing Grid", this.bounds); + this.container.removeChildren(); + let g = new PIXI.Graphics(); + g.rect(0, 0, this.bounds.width, this.bounds.height); + g.fill(0x00aa00); + this.container.addChild(g); + for (let cell of this.cells) { + cell.setBounds( + this.gridUnitsToPixels(cell.column), + this.gridUnitsToPixels(cell.row), + this.gridUnitsToPixels(1), + this.gridUnitsToPixels(1) + ); + this.container.addChild(cell.container); + } + this.container.x = this.bounds.x; + this.container.y = this.bounds.y; + } private getPixelScalingFactor() { - const pixelScaleX = this.container.width / this.columns; - const pixelScaleY = this.container.height / this.rows; + const pixelScaleX = this.container.width / this.gameMap.columns; + const pixelScaleY = this.container.height / this.gameMap.rows; return pixelScaleX < pixelScaleY ? pixelScaleX : pixelScaleY; } diff --git a/src/components/MissionStats.ts b/src/components/MissionStats.ts index 776d67f..b3224d6 100644 --- a/src/components/MissionStats.ts +++ b/src/components/MissionStats.ts @@ -2,10 +2,41 @@ import GameObject from "../base/GameObject"; import * as PIXI from "pixi.js"; export default class MissionStats extends GameObject { - constructor(bounds: PIXI.Rectangle) { - super(bounds); + private hp: number = 100; + private gold: number = 0; + + public setHP(hp: number) { + this.hp = hp; this.draw(); } - protected draw() {} + public setGold(gold: number) { + this.gold = gold; + this.draw(); + } + + constructor(initialHP: number, initialGold: number, bounds?: PIXI.Rectangle) { + super(bounds); + this.hp = initialHP; + this.gold = initialGold; + this.draw(); + } + + protected draw() { + this.container.removeChildren(); + const g = new PIXI.Graphics(); + g.rect(0, 0, this.bounds.width, this.bounds.height); + g.fill(0x000000); + this.container.addChild(g); + const text = new PIXI.Text({ + text: `HP: ${this.hp}\nGold: ${this.gold}`, + style: new PIXI.TextStyle({ + fill: "white", + fontSize: 24, + }), + }); + this.container.addChild(text); + this.container.x = this.bounds.x; + this.container.y = this.bounds.y; + } } diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index 1349065..d269354 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -1,32 +1,62 @@ import Button from "../base/Button"; +import { MissionDefinition } from "../base/Definitions"; +import { Grid } from "../components/Grid"; +import MissionStats from "../components/MissionStats"; import SceneBase from "./SceneBase"; import * as PIXI from "pixi.js"; export default class GameScene extends SceneBase { - private _ticker: PIXI.Ticker; + private ticker: PIXI.Ticker; + private stats: MissionStats; + private grid: Grid; - constructor(bounds: PIXI.Rectangle) { + constructor(mission: MissionDefinition, bounds: PIXI.Rectangle) { super(bounds); - this._ticker = new PIXI.Ticker(); - this._ticker = new PIXI.Ticker(); - this._ticker.maxFPS = 60; - this._ticker.minFPS = 30; - this._ticker.add(this.update); - this._ticker.start(); + this.ticker = new PIXI.Ticker(); + this.ticker = new PIXI.Ticker(); + this.ticker.maxFPS = 60; + this.ticker.minFPS = 30; + this.ticker.add(this.update); + this.ticker.start(); + this.stats = new MissionStats(100, 200); + this.grid = new Grid(mission.gameMap); 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(); + this.ticker.stop(); + this.ticker.destroy(); } public update() {} protected draw() { - console.log("Creating Game Scene ", this.bounds); + console.log("Drawing Game Scene ", this.bounds); this.container.removeChildren(); + const g = new PIXI.Graphics(); + g.rect(0, 0, this.bounds.width, this.bounds.height); + g.fill(0x000033); + this.container.addChild(g); + this.stats.setBounds(this.getStatusBounds()); + this.grid.setBounds(this.getGridBounds()); + this.container.addChild(this.stats.container); + this.container.addChild(this.grid.container); this.container.x = this.bounds.x; this.container.y = this.bounds.y; } diff --git a/src/scenes/MainMenu.ts b/src/scenes/MainMenu.ts index c277423..ce8d18b 100644 --- a/src/scenes/MainMenu.ts +++ b/src/scenes/MainMenu.ts @@ -6,8 +6,16 @@ export default class MainMenu extends SceneBase { private _newGameButton: Button; private _settingsButton: Button; - constructor(bounds: PIXI.Rectangle) { + constructor(bounds?: PIXI.Rectangle) { super(bounds); + this._newGameButton = new Button("New Game", new PIXI.Color("blue")); + this._newGameButton.events.on("click", () => { + this.events.emit("newGame"); + }); + this._settingsButton = new Button("Settings", new PIXI.Color("gray")); + this._settingsButton.events.on("click", () => { + this.events.emit("settings"); + }); this.draw(); } @@ -18,34 +26,19 @@ export default class MainMenu extends SceneBase { g.rect(0, 0, this.bounds.width, this.bounds.height); g.fill(0x000000); this.container.addChild(g); - this._newGameButton = new Button( - "New Game", - new PIXI.Rectangle( - this.bounds.width / 2 - 300 / 2, - this.bounds.height / 2 - 80, - 300, - 60 - ), - new PIXI.Color("blue") + this._newGameButton.setBounds( + this.bounds.width / 2 - 300 / 2, + this.bounds.height / 2 - 80, + 300, + 60 + ); + this._settingsButton.setBounds( + this.bounds.width / 2 - 300 / 2, + this.bounds.height / 2 + 20, + 300, + 60 ); - this._newGameButton.events.on("click", () => { - this.events.emit("newGame"); - }); this.container.addChild(this._newGameButton.container); - - this._settingsButton = new Button( - "Settings", - new PIXI.Rectangle( - this.bounds.width / 2 - 300 / 2, - this.bounds.height / 2 + 20, - 300, - 60 - ), - new PIXI.Color("gray") - ); - this._settingsButton.events.on("click", () => { - this.events.emit("settings"); - }); this.container.addChild(this._settingsButton.container); this.container.x = this.bounds.x; this.container.y = this.bounds.y; diff --git a/src/scenes/MissionSelectMenu.ts b/src/scenes/MissionSelectMenu.ts index cbf78fb..00c433e 100644 --- a/src/scenes/MissionSelectMenu.ts +++ b/src/scenes/MissionSelectMenu.ts @@ -1,4 +1,6 @@ +import Assets from "../base/Assets"; import Button from "../base/Button"; +import { MissionDefinition } from "../base/Definitions"; import SceneBase from "./SceneBase"; import * as PIXI from "pixi.js"; @@ -7,42 +9,40 @@ export default class MissionMenuSelect extends SceneBase { constructor(bounds: PIXI.Rectangle) { super(bounds); + for (const mission of Assets.Missions) { + this.addMission(mission); + } + this.addButton("Back", () => { + this.events.emit("back"); + }); this.draw(); } protected draw() { this.container.removeChildren(); - this._buttons = []; const g = new PIXI.Graphics(); g.rect(0, 0, this.bounds.width, this.bounds.height); g.fill(0x000000); this.container.addChild(g); - this.addMission("Mission 1"); - this.addMission("Mission 2"); - this.addMission("Mission 3"); - this.addMission("Mission 4"); - this.addButton("Back", () => { - this.events.emit("back"); - }); + let y = 50; + for (const button of this._buttons) { + button.setBounds(this.bounds.width / 2 - 300 / 2, y, 300, 60); + y += 80; + this.container.addChild(button.container); + } this.container.x = this.bounds.x; this.container.y = this.bounds.y; } - private addMission(mission: string) { - this.addButton(mission, () => { + private addMission(mission: MissionDefinition) { + this.addButton(mission.name, () => { this.events.emit("mission", mission); }); } private addButton(caption: string, onClick: () => void) { - const yOffset = this._buttons.length * 80 + 100; - const button = new Button( - caption, - new PIXI.Rectangle(100, yOffset, this.bounds.width - 200, 60), - new PIXI.Color("white") - ); + const button = new Button(caption, new PIXI.Color("white")); button.events.on("click", onClick); this._buttons.push(button); - this.container.addChild(button.container); } } diff --git a/src/scenes/SettingsMenu.ts b/src/scenes/SettingsMenu.ts index aa7aa1f..4c4b464 100644 --- a/src/scenes/SettingsMenu.ts +++ b/src/scenes/SettingsMenu.ts @@ -3,9 +3,6 @@ import SceneBase from "./SceneBase"; import * as PIXI from "pixi.js"; export default class SettingsMenu extends SceneBase { - private _newGameButton: Button; - private _settingsButton: Button; - constructor(bounds: PIXI.Rectangle) { super(bounds); this.draw(); @@ -16,36 +13,7 @@ export default class SettingsMenu extends SceneBase { const g = new PIXI.Graphics(); g.rect(0, 0, this.bounds.width, this.bounds.height); g.fill(0x000000); - this.container.addChild(g); - this._newGameButton = new Button( - "New Game", - new PIXI.Rectangle( - 100, - this.bounds.height / 2 - 80, - this.bounds.width - 200, - 60 - ), - new PIXI.Color("blue") - ); - this._newGameButton.events.on("click", () => { - this.events.emit("newGame"); - }); - this.container.addChild(this._newGameButton.container); - this._settingsButton = new Button( - "Settings", - new PIXI.Rectangle( - 100, - this.bounds.height / 2 + 20, - this.bounds.width - 200, - 60 - ), - new PIXI.Color("gray") - ); - this._settingsButton.events.on("click", () => { - this.events.emit("settings"); - }); - this.container.addChild(this._settingsButton.container); this.container.x = this.bounds.x; this.container.y = this.bounds.y; }