diff --git a/public/assets/Towers.json b/public/assets/Towers.json index 01f0acd..58b6fea 100644 --- a/public/assets/Towers.json +++ b/public/assets/Towers.json @@ -1,6 +1,8 @@ [ { "name": "Basic Tower", + "sprite": "basic_tower", + "description": "The building block of society, nothing more basic exists.", "stats": { "damage": 2, "cooldown": 2, diff --git a/public/assets/gui/background_01.png b/public/assets/gui/background_01.png new file mode 100755 index 0000000..3fa5d9b Binary files /dev/null and b/public/assets/gui/background_01.png differ diff --git a/public/assets/gui/gui_01_button_01.png b/public/assets/gui/gui_01_button_01.png new file mode 100755 index 0000000..5df1548 Binary files /dev/null and b/public/assets/gui/gui_01_button_01.png differ diff --git a/public/assets/gui/gui_01_checkbox_01_bg01.png b/public/assets/gui/gui_01_checkbox_01_bg01.png new file mode 100755 index 0000000..deaf98e Binary files /dev/null and b/public/assets/gui/gui_01_checkbox_01_bg01.png differ diff --git a/public/assets/towers/basic_tower.png b/public/assets/towers/basic_tower.png new file mode 100644 index 0000000..6d127c0 Binary files /dev/null and b/public/assets/towers/basic_tower.png differ diff --git a/src/classes/Assets.ts b/src/classes/Assets.ts index 0c0b484..848a00a 100644 --- a/src/classes/Assets.ts +++ b/src/classes/Assets.ts @@ -8,12 +8,12 @@ export default class GameAssets { const text = new PIXI.Text({ text: 'Loading textures. This might take a while.', style: new PIXI.TextStyle({ - fill: 0xffffff, + fill: 0x333333, fontSize: 50, }), }); - text.x = Globals.WindowWidth / 2; - text.y = Globals.WindowHeight / 2; + text.x = Globals.app.canvas.width / 2; + text.y = Globals.app.canvas.height / 2; text.anchor.set(0.5, 0.5); Globals.app.stage.addChild(text); GameAssets.Button01Texture = await PIXI.Assets.load({ @@ -34,18 +34,30 @@ export default class GameAssets { GameAssets.FrameTowerTab = await PIXI.Assets.load({ src: '/assets/gui/background_02.png', }); - GameAssets.FrameVioletTexture = await PIXI.Assets.load({ + GameAssets.VioletBackground = await PIXI.Assets.load({ src: '/assets/gui/frame_violet.png', }); + GameAssets.RedBackground = await PIXI.Assets.load({ + src: '/assets/gui/frame_red.png', + }); + GameAssets.GreenBackground = await PIXI.Assets.load({ + src: '/assets/gui/frame_green.png', + }); GameAssets.HealthTexture = await PIXI.Assets.load({ src: '/assets/gui/heart.png', }); GameAssets.GoldTexture = await PIXI.Assets.load({ src: '/assets/gui/money.png', }); + GameAssets.BasicCreepTexture = await PIXI.Assets.load({ src: '/assets/creeps/basic.jpg', }); + + GameAssets.BasicTowerTexture = await PIXI.Assets.load({ + src: '/assets/towers/basic_tower.png', + }); + await this.LoadMissions(); await this.LoadTowers(); await this.LoadCreepStats(); @@ -66,6 +78,13 @@ export default class GameAssets { const res = await fetch('/assets/Towers.json'); const towers = await res.json(); GameAssets.Towers = towers; + towers.forEach(async (tower) => { + let index = this.TowerSprites.length - 1; + if (index == -1) index = 0; + this.TowerSprites[index] = await PIXI.Assets.load({ + src: `/assets/towers/${tower.sprite}.png`, + }); + }); } private static async LoadMission(missionUrl: string) { @@ -85,17 +104,22 @@ export default class GameAssets { public static BasicCreepTexture: PIXI.Texture; + public static BasicTowerTexture: PIXI.Texture; + public static Frame01Texture: PIXI.Texture; public static Frame02Texture: PIXI.Texture; public static FrameBackground: PIXI.Texture; public static FrameTowerTab: PIXI.Texture; - public static FrameVioletTexture: PIXI.Texture; + public static VioletBackground: PIXI.Texture; + public static RedBackground: PIXI.Texture; + public static GreenBackground: PIXI.Texture; public static Button01Texture: PIXI.Texture; public static Button02Texture: PIXI.Texture; public static HealthTexture: PIXI.Texture; public static GoldTexture: PIXI.Texture; public static MissionBackgrounds: PIXI.Texture[] = []; + public static TowerSprites: PIXI.Texture[] = []; public static Missions: MissionDefinition[]; public static Towers: TowerDefinition[]; public static CreepStats: CreepStatsDefinition[]; diff --git a/src/classes/Bastion.ts b/src/classes/Bastion.ts index 5eee98c..41c0b5f 100644 --- a/src/classes/Bastion.ts +++ b/src/classes/Bastion.ts @@ -4,6 +4,8 @@ import GuiObject from './GuiObject'; import Scene from '../scenes/Scene'; import { Grid } from './game/Grid'; import WaveManager from './game/WaveManager'; +import TowerManager from './game/TowerManager'; +import { GameScene } from '../scenes/Game'; export class Globals { public static app: PIXI.Application; @@ -13,19 +15,17 @@ export class Globals { public static AspectRatio: number = 16 / 9; public static Grid: Grid; public static WaveManager: WaveManager; + public static TowerManager: TowerManager; + public static GameScene: GameScene; } export default class GameMaster { public currentScene: Scene; + private gameScene: GameScene; private GameObjects: GameObject[] = []; - private ticker: PIXI.Ticker; constructor() { Globals.GameMaster = this; - this.ticker = new PIXI.Ticker(); - this.ticker.maxFPS = 60; - this.ticker.minFPS = 30; - this.ticker.add(() => this.update(this.ticker.elapsedMS)); } public _CreateGuiObject(object: GuiObject) { @@ -48,10 +48,4 @@ export default class GameMaster { this.currentScene = newScene; this.currentScene.init(); } - - public update(elapsedMS) { - this.GameObjects.forEach((element) => { - element.update(elapsedMS); - }); - } } diff --git a/src/classes/Definitions.ts b/src/classes/Definitions.ts index 4f8bcaa..a21cce9 100644 --- a/src/classes/Definitions.ts +++ b/src/classes/Definitions.ts @@ -8,8 +8,6 @@ export type MissionDefinition = { export type MapImageDefinition = { url: string; - width: number; - height: number; }; export type GameMapDefinition = { @@ -47,6 +45,8 @@ export type CreepResistancesDefinition = { export type TowerDefinition = { name: string; + sprite: string; + description: string; stats: TowerStatsDefinition; }; diff --git a/src/classes/game/Grid.ts b/src/classes/game/Grid.ts index c4731c9..4ca283c 100644 --- a/src/classes/game/Grid.ts +++ b/src/classes/game/Grid.ts @@ -11,6 +11,7 @@ export class Cell extends GameObject { public column: number; public isPath: boolean = false; private g: PIXI.Graphics; + private clickDetector: PIXI.Graphics; constructor(type: TerrainType, row: number, column: number, isPath: boolean) { super(); @@ -25,6 +26,18 @@ export class Cell extends GameObject { Globals.Grid.container.addChild(this.container); this.container.x = this.bb.x; this.container.y = this.bb.y; + + this.clickDetector = new PIXI.Graphics({ + zIndex: 99, + interactive: true, + }); + this.clickDetector.rect(0, 0, this.bb.width, this.bb.height); + this.clickDetector.fill({ color: 0xff0000, alpha: 0 }); + this.container.addChild(this.clickDetector); + this.clickDetector.onclick = (e) => { + Globals.Grid._gridCellClicked(row, column); + }; + if (!GameAssets.DebuggingEnabled) return; const text = new PIXI.Text({ text: `${this.row}|${this.column}`, @@ -38,10 +51,12 @@ export class Cell extends GameObject { text.anchor.set(0.5, 0.5); text.x = this.bb.width / 2; text.y = this.bb.height / 2; - if (this.isPath) text.text += 'P'; + if (isPath) text.text += 'p'; } public gDraw() { - this.g = new PIXI.Graphics(); + this.g = new PIXI.Graphics({ + zIndex: 5, + }); this.g.rect(0, 0, this.bb.width, this.bb.height); switch (this.type) { case TerrainType.Restricted: @@ -51,7 +66,7 @@ export class Cell extends GameObject { this.g.fill({ color: 0x222222, alpha: 0.5 }); break; case TerrainType.Buildable: - this.g.stroke({ color: 0x00ff00, alpha: 0.1 }); + this.g.stroke({ color: 0x00ff00, alpha: 0.9 }); break; } this.container.addChild(this.g); @@ -107,9 +122,8 @@ export class Grid extends GameObject { } else { cell.gDraw(); } - // smort - this.gridShown = !this.gridShown; }); + this.gridShown = !this.gridShown; } public addCreep(creep: Creep) { console.log('ADD CREEP'); @@ -130,4 +144,12 @@ export class Grid extends GameObject { creep.update(elapsedMS); }); } + 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) {} } diff --git a/src/classes/game/TowerManager.ts b/src/classes/game/TowerManager.ts new file mode 100644 index 0000000..47fab18 --- /dev/null +++ b/src/classes/game/TowerManager.ts @@ -0,0 +1,107 @@ +import * as PIXI from 'pixi.js'; +import { Globals } from '../Bastion'; +import { TowerDefinition } from '../Definitions'; +import GameAssets from '../Assets'; +import GameObject from '../GameObject'; + +export type TowerInstance = { + row: number; + column: number; + sprite: PIXI.Sprite; + projectiles: Array; + baseDamage: number; + damage: number; + cooldown: number; + ticksToFireAt: number; + slottedGems: Array; + cost: number; + baseRange: number; + range: number; +}; + +export enum TowerEvents { + TowerPlacedEvent = 'towerPlacedEvent', +} + +enum TowerSprite { + basic_tower = 'GameAssets.BasicTowerTexture', +} + +class Tower extends GameObject { + public row: number; + public column: number; + private sprite: PIXI.Sprite; + constructor(row, column, texture, definition) { + super(); + this.row = row; + this.column = column; + let parent = Globals.Grid.getCellByRowAndCol(row, column); + this.sprite = new PIXI.Sprite({ + texture: texture, + height: 64, + width: 64, + zIndex: 10, + }); + this.container.addChild(this.sprite); + parent.container.addChild(this.container); + } + public update(elapsedMS: any): void {} +} + +export default class TowerManager { + public isPlacingTower: boolean = false; + public canPlaceTowers: boolean = true; + private selectedTower: TowerDefinition | null = null; + private towers: Tower[] = []; + constructor() { + Globals.TowerManager = this; + } + public ToggleChoosingTowerLocation(towerName: string) { + if (!this.canPlaceTowers) return; + Globals.Grid.toggleGrid(); + if (!this.isPlacingTower) { + GameAssets.Towers.forEach((item) => { + if (item.name == towerName) { + this.selectedTower = item; + } + }); + } else { + this.selectedTower = null; + } + this.isPlacingTower = !this.isPlacingTower; + } + public PlayerClickOnGrid(row, column) { + if (!this.canPlaceTowers) return; + if (this.isPlacingTower) { + if (!this.selectedTower) throw console.warn('TowerManager.selectedTower is null.'); + this.PlaceTower(this.selectedTower, row, column); + } + } + public GetTowerByRowAndCol(row, col) { + // i just want to say that this is really stupid, why do i need to define a type for a local + // variable i will temporarily use? TS is weird + let returnTower: Tower | null = null; + this.towers.forEach((tower) => { + if (tower.row == row && tower.column == col) returnTower = tower; + }); + return returnTower; + } + public PlaceTower(definition: TowerDefinition, row, column) { + let idx = 0; + GameAssets.Towers.forEach((item, index) => { + if (item.sprite == definition.sprite) idx = index; + }); + const sprite = GameAssets.TowerSprites[idx]; + if (!this.GetTowerByRowAndCol(row, column) && !Globals.Grid.getCellByRowAndCol(row, column).isPath) { + let tower = new Tower(row, column, sprite, definition); + this.towers.push(tower); + this.ToggleChoosingTowerLocation('RESET'); + console.log('SHOULDVE PLACED TOWER'); + console.log(this.selectedTower); + this.selectedTower = null; + Globals.GameScene.events.emit(TowerEvents.TowerPlacedEvent, definition.name); + } else { + console.warn('Can not place tower on occupied spot or path. Try again.'); + } + } +} diff --git a/src/classes/gui/GemTab.ts b/src/classes/gui/GemTab.ts index 32ba2d6..7673687 100644 --- a/src/classes/gui/GemTab.ts +++ b/src/classes/gui/GemTab.ts @@ -4,25 +4,28 @@ import GameAssets from '../Assets'; export default class GemTab extends GuiObject { private bounds: PIXI.Rectangle; - private towerTabSprite: PIXI.NineSliceSprite; + private gemTabSprite: PIXI.NineSliceSprite; constructor(bounds: PIXI.Rectangle) { super(false); this.bounds = bounds; this.container.x = this.bounds.x; this.container.y = this.bounds.y; - this.towerTabSprite = new PIXI.NineSliceSprite({ + this.gemTabSprite = new PIXI.NineSliceSprite({ texture: GameAssets.FrameTowerTab, - leftWidth: 500, - topHeight: 500, - rightWidth: 500, - bottomHeight: 500, + leftWidth: 1000, + topHeight: 1000, + rightWidth: 1000, + bottomHeight: 1000, }); - this.towerTabSprite.x = 0; - this.towerTabSprite.y = 0; - this.towerTabSprite.width = this.bounds.width; - this.towerTabSprite.height = this.bounds.height; + // this.towerTabSprite = new PIXI.Sprite({ + // texture: GameAssets.FrameBackground, + // }); + this.gemTabSprite.x = 0; + this.gemTabSprite.y = 0; + this.gemTabSprite.width = this.bounds.width; + this.gemTabSprite.height = this.bounds.height; - this.container.addChild(this.towerTabSprite); + this.container.addChild(this.gemTabSprite); } } diff --git a/src/classes/gui/TowerTab.ts b/src/classes/gui/TowerTab.ts index c62a4c9..dddf481 100644 --- a/src/classes/gui/TowerTab.ts +++ b/src/classes/gui/TowerTab.ts @@ -1,14 +1,66 @@ import * as PIXI from 'pixi.js'; import GuiObject from '../GuiObject'; import GameAssets from '../Assets'; +import { Globals } from '../Bastion'; +import { TowerEvents } from '../game/TowerManager'; + +class TowerButton extends GuiObject { + private frameSprite: PIXI.NineSliceSprite; + private background: PIXI.Sprite; + private towerName: string; + constructor(index: number, row, width, height, parent: PIXI.Container, backgroundTexture, towerName) { + if (index > 3 || row > 2 || index < 0 || row < 0) throw 'Index/row out of bounds for TowerButton.'; + super(true); + this.towerName = towerName; + this.container.x = index * width + 5; + this.container.y = row * height + 5; // 5 is padding, looks better + this.background = new PIXI.Sprite({ + texture: backgroundTexture, + }); + this.background.width = width; + this.background.height = height; + this.container.addChild(this.background); + + this.frameSprite = new PIXI.NineSliceSprite({ + texture: GameAssets.Frame02Texture, + leftWidth: 100, + topHeight: 100, + rightWidth: 100, + bottomHeight: 100, + roundPixels: true, + height: height, + width: width, + }); + this.container.addChild(this.frameSprite); + parent.addChild(this.container); + Globals.GameScene.events.on(TowerEvents.TowerPlacedEvent, (name) => { + this.frameSprite.tint = 0xffffff; // reset the tint after a tower has been placed + }); + this.container.onmouseenter = (e) => { + // add on mouse over info (banner next to sidebar) + }; + + this.container.onmouseleave = (e) => {}; + } + public onClick(e: PIXI.FederatedPointerEvent): void { + if (this.frameSprite.tint == 0x00ff00) this.frameSprite.tint = 0xffffff; + else this.frameSprite.tint = 0x00ff00; + Globals.TowerManager.ToggleChoosingTowerLocation(this.towerName); + } +} export default class TowerTab extends GuiObject { private bounds: PIXI.Rectangle; private towerTabSprite: PIXI.NineSliceSprite; + private towerList: Array = []; constructor(bounds: PIXI.Rectangle) { super(false); this.bounds = bounds; + GameAssets.Towers.forEach((twr) => { + let obj = { name: twr.name, description: twr.description, cost: twr.stats.cost }; + this.towerList.push(obj); + }); this.container.x = this.bounds.x; this.container.y = this.bounds.y; this.towerTabSprite = new PIXI.NineSliceSprite({ @@ -19,11 +71,11 @@ export default class TowerTab extends GuiObject { bottomHeight: 500, roundPixels: true, }); - this.towerTabSprite.x = 0; - this.towerTabSprite.y = 0; this.towerTabSprite.width = this.bounds.width; this.towerTabSprite.height = this.bounds.height; - this.container.addChild(this.towerTabSprite); + + new TowerButton(0, 0, 70, 70, this.container, GameAssets.RedBackground, 'Basic Tower'); + new TowerButton(0, 1, 70, 70, this.container, GameAssets.GreenBackground, 'Basic Tower'); } } diff --git a/src/main.ts b/src/main.ts index 4248852..8d069ee 100644 --- a/src/main.ts +++ b/src/main.ts @@ -50,6 +50,7 @@ import { GameScene } from './scenes/Game'; resize(); await Assets.LoadAssets(); new GameMaster(); + globalThis.Globals = Globals; Globals.GameMaster.changeScene(new MainScene()); let params = new URLSearchParams(location.href); if (params.entries().next().value[1] == 'game') Globals.GameMaster.changeScene(new GameScene('Mission 1')); diff --git a/src/scenes/Game.ts b/src/scenes/Game.ts index ce060d6..cafb9fb 100644 --- a/src/scenes/Game.ts +++ b/src/scenes/Game.ts @@ -9,6 +9,7 @@ import Button, { ButtonTexture } from '../classes/gui/Button'; import Scene from './Scene'; import * as PIXI from 'pixi.js'; import MissionStats from '../classes/game/MissionStats'; +import TowerManager, { TowerEvents } from '../classes/game/TowerManager'; enum RoundMode { Purchase = 0, @@ -23,12 +24,11 @@ export class GameScene extends Scene { public ticker: PIXI.Ticker; public changeRoundButton: Button; public sidebar: Sidebar; - public hideSidebarButton: Button; - public sidebarHidden: boolean = false; private currentRound: number = 0; constructor(name: string) { super(); + Globals.GameScene = this; GameAssets.Missions.forEach((mission, index) => { if (mission.name == name) { this.mission = mission; @@ -45,7 +45,13 @@ export class GameScene extends Scene { const SidebarRect = new PIXI.Rectangle(64 * 30 - 360, 0, 360, Globals.app.canvas.height); const changeRoundButtonRect = new PIXI.Rectangle(50, Globals.app.canvas.height - 100, 310, 100); new Grid(this.mission.gameMap, this.missionIndex); + new TowerManager(); new WaveManager(this.mission.rounds, this.mission.gameMap.paths); + Globals.Grid.onGridCellClicked = (row, column) => { + if (Globals.TowerManager.isPlacingTower) { + Globals.TowerManager.PlayerClickOnGrid(row, column); + } + }; Globals.WaveManager.events.on(WaveManagerEvents.CreepSpawned, (creep: Creep) => { Globals.Grid.addCreep(creep); creep.events.on(CreepEvents.Escaped, () => { @@ -69,6 +75,7 @@ export class GameScene extends Scene { Globals.WaveManager.update(elapsedMS); Globals.Grid.update(elapsedMS); } + public onCreepEscaped(creep: Creep) { this.MissionStats.takeDamage(creep.health); } @@ -81,4 +88,6 @@ export class GameScene extends Scene { Globals.WaveManager.end(); } } + + public onTowerPlaced() {} } diff --git a/src/scenes/Scene.ts b/src/scenes/Scene.ts index 8734d41..0aa3888 100644 --- a/src/scenes/Scene.ts +++ b/src/scenes/Scene.ts @@ -1,7 +1,10 @@ import GuiObject from '../classes/GuiObject'; +import * as PIXI from 'pixi.js'; export default class Scene { public gui: GuiObject[] = []; + private _events: PIXI.EventEmitter = new PIXI.EventEmitter(); + public destroy() { this.gui.forEach((element) => { element.destroy(); @@ -13,6 +16,11 @@ export default class Scene { public GetGuiObjectByName(name: string) { return this.gui.filter((obj) => obj.name == name); } + + public get events(): PIXI.EventEmitter { + return this._events; + } + public init() { // Definitions for scene elements. }