a lot of working late at night. working on placing towers and making them operate as normal.

This commit is contained in:
koneko 2025-01-04 02:26:47 +01:00
parent 8d20a4a9f5
commit 483667c64b
15 changed files with 261 additions and 39 deletions

View File

@ -1,6 +1,8 @@
[ [
{ {
"name": "Basic Tower", "name": "Basic Tower",
"sprite": "basic_tower",
"description": "The building block of society, nothing more basic exists.",
"stats": { "stats": {
"damage": 2, "damage": 2,
"cooldown": 2, "cooldown": 2,

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -8,12 +8,12 @@ export default class GameAssets {
const text = new PIXI.Text({ const text = new PIXI.Text({
text: 'Loading textures. This might take a while.', text: 'Loading textures. This might take a while.',
style: new PIXI.TextStyle({ style: new PIXI.TextStyle({
fill: 0xffffff, fill: 0x333333,
fontSize: 50, fontSize: 50,
}), }),
}); });
text.x = Globals.WindowWidth / 2; text.x = Globals.app.canvas.width / 2;
text.y = Globals.WindowHeight / 2; text.y = Globals.app.canvas.height / 2;
text.anchor.set(0.5, 0.5); text.anchor.set(0.5, 0.5);
Globals.app.stage.addChild(text); Globals.app.stage.addChild(text);
GameAssets.Button01Texture = await PIXI.Assets.load({ GameAssets.Button01Texture = await PIXI.Assets.load({
@ -34,18 +34,30 @@ export default class GameAssets {
GameAssets.FrameTowerTab = await PIXI.Assets.load({ GameAssets.FrameTowerTab = await PIXI.Assets.load({
src: '/assets/gui/background_02.png', src: '/assets/gui/background_02.png',
}); });
GameAssets.FrameVioletTexture = await PIXI.Assets.load({ GameAssets.VioletBackground = await PIXI.Assets.load({
src: '/assets/gui/frame_violet.png', 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({ GameAssets.HealthTexture = await PIXI.Assets.load({
src: '/assets/gui/heart.png', src: '/assets/gui/heart.png',
}); });
GameAssets.GoldTexture = await PIXI.Assets.load({ GameAssets.GoldTexture = await PIXI.Assets.load({
src: '/assets/gui/money.png', src: '/assets/gui/money.png',
}); });
GameAssets.BasicCreepTexture = await PIXI.Assets.load({ GameAssets.BasicCreepTexture = await PIXI.Assets.load({
src: '/assets/creeps/basic.jpg', src: '/assets/creeps/basic.jpg',
}); });
GameAssets.BasicTowerTexture = await PIXI.Assets.load({
src: '/assets/towers/basic_tower.png',
});
await this.LoadMissions(); await this.LoadMissions();
await this.LoadTowers(); await this.LoadTowers();
await this.LoadCreepStats(); await this.LoadCreepStats();
@ -66,6 +78,13 @@ export default class GameAssets {
const res = await fetch('/assets/Towers.json'); const res = await fetch('/assets/Towers.json');
const towers = await res.json(); const towers = await res.json();
GameAssets.Towers = towers; 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) { private static async LoadMission(missionUrl: string) {
@ -85,17 +104,22 @@ export default class GameAssets {
public static BasicCreepTexture: PIXI.Texture; public static BasicCreepTexture: PIXI.Texture;
public static BasicTowerTexture: PIXI.Texture;
public static Frame01Texture: PIXI.Texture; public static Frame01Texture: PIXI.Texture;
public static Frame02Texture: PIXI.Texture; public static Frame02Texture: PIXI.Texture;
public static FrameBackground: PIXI.Texture; public static FrameBackground: PIXI.Texture;
public static FrameTowerTab: 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 Button01Texture: PIXI.Texture;
public static Button02Texture: PIXI.Texture; public static Button02Texture: PIXI.Texture;
public static HealthTexture: PIXI.Texture; public static HealthTexture: PIXI.Texture;
public static GoldTexture: PIXI.Texture; public static GoldTexture: PIXI.Texture;
public static MissionBackgrounds: PIXI.Texture[] = []; public static MissionBackgrounds: PIXI.Texture[] = [];
public static TowerSprites: PIXI.Texture[] = [];
public static Missions: MissionDefinition[]; public static Missions: MissionDefinition[];
public static Towers: TowerDefinition[]; public static Towers: TowerDefinition[];
public static CreepStats: CreepStatsDefinition[]; public static CreepStats: CreepStatsDefinition[];

View File

@ -4,6 +4,8 @@ import GuiObject from './GuiObject';
import Scene from '../scenes/Scene'; import Scene from '../scenes/Scene';
import { Grid } from './game/Grid'; import { Grid } from './game/Grid';
import WaveManager from './game/WaveManager'; import WaveManager from './game/WaveManager';
import TowerManager from './game/TowerManager';
import { GameScene } from '../scenes/Game';
export class Globals { export class Globals {
public static app: PIXI.Application; public static app: PIXI.Application;
@ -13,19 +15,17 @@ export class Globals {
public static AspectRatio: number = 16 / 9; public static AspectRatio: number = 16 / 9;
public static Grid: Grid; public static Grid: Grid;
public static WaveManager: WaveManager; public static WaveManager: WaveManager;
public static TowerManager: TowerManager;
public static GameScene: GameScene;
} }
export default class GameMaster { export default class GameMaster {
public currentScene: Scene; public currentScene: Scene;
private gameScene: GameScene;
private GameObjects: GameObject[] = []; private GameObjects: GameObject[] = [];
private ticker: PIXI.Ticker;
constructor() { constructor() {
Globals.GameMaster = this; 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) { public _CreateGuiObject(object: GuiObject) {
@ -48,10 +48,4 @@ export default class GameMaster {
this.currentScene = newScene; this.currentScene = newScene;
this.currentScene.init(); this.currentScene.init();
} }
public update(elapsedMS) {
this.GameObjects.forEach((element) => {
element.update(elapsedMS);
});
}
} }

View File

@ -8,8 +8,6 @@ export type MissionDefinition = {
export type MapImageDefinition = { export type MapImageDefinition = {
url: string; url: string;
width: number;
height: number;
}; };
export type GameMapDefinition = { export type GameMapDefinition = {
@ -47,6 +45,8 @@ export type CreepResistancesDefinition = {
export type TowerDefinition = { export type TowerDefinition = {
name: string; name: string;
sprite: string;
description: string;
stats: TowerStatsDefinition; stats: TowerStatsDefinition;
}; };

View File

@ -11,6 +11,7 @@ export class Cell extends GameObject {
public column: number; public column: number;
public isPath: boolean = false; public isPath: boolean = false;
private g: PIXI.Graphics; private g: PIXI.Graphics;
private clickDetector: PIXI.Graphics;
constructor(type: TerrainType, row: number, column: number, isPath: boolean) { constructor(type: TerrainType, row: number, column: number, isPath: boolean) {
super(); super();
@ -25,6 +26,18 @@ export class Cell extends GameObject {
Globals.Grid.container.addChild(this.container); Globals.Grid.container.addChild(this.container);
this.container.x = this.bb.x; this.container.x = this.bb.x;
this.container.y = this.bb.y; 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; if (!GameAssets.DebuggingEnabled) return;
const text = new PIXI.Text({ const text = new PIXI.Text({
text: `${this.row}|${this.column}`, text: `${this.row}|${this.column}`,
@ -38,10 +51,12 @@ export class Cell extends GameObject {
text.anchor.set(0.5, 0.5); text.anchor.set(0.5, 0.5);
text.x = this.bb.width / 2; text.x = this.bb.width / 2;
text.y = this.bb.height / 2; text.y = this.bb.height / 2;
if (this.isPath) text.text += 'P'; if (isPath) text.text += 'p';
} }
public gDraw() { 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); this.g.rect(0, 0, this.bb.width, this.bb.height);
switch (this.type) { switch (this.type) {
case TerrainType.Restricted: case TerrainType.Restricted:
@ -51,7 +66,7 @@ export class Cell extends GameObject {
this.g.fill({ color: 0x222222, alpha: 0.5 }); this.g.fill({ color: 0x222222, alpha: 0.5 });
break; break;
case TerrainType.Buildable: case TerrainType.Buildable:
this.g.stroke({ color: 0x00ff00, alpha: 0.1 }); this.g.stroke({ color: 0x00ff00, alpha: 0.9 });
break; break;
} }
this.container.addChild(this.g); this.container.addChild(this.g);
@ -107,9 +122,8 @@ export class Grid extends GameObject {
} else { } else {
cell.gDraw(); cell.gDraw();
} }
// smort
this.gridShown = !this.gridShown;
}); });
this.gridShown = !this.gridShown;
} }
public addCreep(creep: Creep) { public addCreep(creep: Creep) {
console.log('ADD CREEP'); console.log('ADD CREEP');
@ -130,4 +144,12 @@ export class Grid extends GameObject {
creep.update(elapsedMS); 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) {}
} }

View File

@ -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<any>;
baseDamage: number;
damage: number;
cooldown: number;
ticksToFireAt: number;
slottedGems: Array<any>;
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.');
}
}
}

View File

@ -4,25 +4,28 @@ import GameAssets from '../Assets';
export default class GemTab extends GuiObject { export default class GemTab extends GuiObject {
private bounds: PIXI.Rectangle; private bounds: PIXI.Rectangle;
private towerTabSprite: PIXI.NineSliceSprite; private gemTabSprite: PIXI.NineSliceSprite;
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(false); super(false);
this.bounds = bounds; this.bounds = bounds;
this.container.x = this.bounds.x; this.container.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;
this.towerTabSprite = new PIXI.NineSliceSprite({ this.gemTabSprite = new PIXI.NineSliceSprite({
texture: GameAssets.FrameTowerTab, texture: GameAssets.FrameTowerTab,
leftWidth: 500, leftWidth: 1000,
topHeight: 500, topHeight: 1000,
rightWidth: 500, rightWidth: 1000,
bottomHeight: 500, bottomHeight: 1000,
}); });
this.towerTabSprite.x = 0; // this.towerTabSprite = new PIXI.Sprite({
this.towerTabSprite.y = 0; // texture: GameAssets.FrameBackground,
this.towerTabSprite.width = this.bounds.width; // });
this.towerTabSprite.height = this.bounds.height; 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);
} }
} }

View File

@ -1,14 +1,66 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject'; import GuiObject from '../GuiObject';
import GameAssets from '../Assets'; 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 { export default class TowerTab extends GuiObject {
private bounds: PIXI.Rectangle; private bounds: PIXI.Rectangle;
private towerTabSprite: PIXI.NineSliceSprite; private towerTabSprite: PIXI.NineSliceSprite;
private towerList: Array<any> = [];
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(false); super(false);
this.bounds = bounds; 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.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;
this.towerTabSprite = new PIXI.NineSliceSprite({ this.towerTabSprite = new PIXI.NineSliceSprite({
@ -19,11 +71,11 @@ export default class TowerTab extends GuiObject {
bottomHeight: 500, bottomHeight: 500,
roundPixels: true, roundPixels: true,
}); });
this.towerTabSprite.x = 0;
this.towerTabSprite.y = 0;
this.towerTabSprite.width = this.bounds.width; this.towerTabSprite.width = this.bounds.width;
this.towerTabSprite.height = this.bounds.height; this.towerTabSprite.height = this.bounds.height;
this.container.addChild(this.towerTabSprite); 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');
} }
} }

View File

@ -50,6 +50,7 @@ import { GameScene } from './scenes/Game';
resize(); resize();
await Assets.LoadAssets(); await Assets.LoadAssets();
new GameMaster(); new GameMaster();
globalThis.Globals = Globals;
Globals.GameMaster.changeScene(new MainScene()); Globals.GameMaster.changeScene(new MainScene());
let params = new URLSearchParams(location.href); let params = new URLSearchParams(location.href);
if (params.entries().next().value[1] == 'game') Globals.GameMaster.changeScene(new GameScene('Mission 1')); if (params.entries().next().value[1] == 'game') Globals.GameMaster.changeScene(new GameScene('Mission 1'));

View File

@ -9,6 +9,7 @@ import Button, { ButtonTexture } from '../classes/gui/Button';
import Scene from './Scene'; import Scene from './Scene';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import MissionStats from '../classes/game/MissionStats'; import MissionStats from '../classes/game/MissionStats';
import TowerManager, { TowerEvents } from '../classes/game/TowerManager';
enum RoundMode { enum RoundMode {
Purchase = 0, Purchase = 0,
@ -23,12 +24,11 @@ export class GameScene extends Scene {
public ticker: PIXI.Ticker; public ticker: PIXI.Ticker;
public changeRoundButton: Button; public changeRoundButton: Button;
public sidebar: Sidebar; public sidebar: Sidebar;
public hideSidebarButton: Button;
public sidebarHidden: boolean = false;
private currentRound: number = 0; private currentRound: number = 0;
constructor(name: string) { constructor(name: string) {
super(); super();
Globals.GameScene = this;
GameAssets.Missions.forEach((mission, index) => { GameAssets.Missions.forEach((mission, index) => {
if (mission.name == name) { if (mission.name == name) {
this.mission = mission; 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 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); const changeRoundButtonRect = new PIXI.Rectangle(50, Globals.app.canvas.height - 100, 310, 100);
new Grid(this.mission.gameMap, this.missionIndex); new Grid(this.mission.gameMap, this.missionIndex);
new TowerManager();
new WaveManager(this.mission.rounds, this.mission.gameMap.paths); 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.WaveManager.events.on(WaveManagerEvents.CreepSpawned, (creep: Creep) => {
Globals.Grid.addCreep(creep); Globals.Grid.addCreep(creep);
creep.events.on(CreepEvents.Escaped, () => { creep.events.on(CreepEvents.Escaped, () => {
@ -69,6 +75,7 @@ export class GameScene extends Scene {
Globals.WaveManager.update(elapsedMS); Globals.WaveManager.update(elapsedMS);
Globals.Grid.update(elapsedMS); Globals.Grid.update(elapsedMS);
} }
public onCreepEscaped(creep: Creep) { public onCreepEscaped(creep: Creep) {
this.MissionStats.takeDamage(creep.health); this.MissionStats.takeDamage(creep.health);
} }
@ -81,4 +88,6 @@ export class GameScene extends Scene {
Globals.WaveManager.end(); Globals.WaveManager.end();
} }
} }
public onTowerPlaced() {}
} }

View File

@ -1,7 +1,10 @@
import GuiObject from '../classes/GuiObject'; import GuiObject from '../classes/GuiObject';
import * as PIXI from 'pixi.js';
export default class Scene { export default class Scene {
public gui: GuiObject[] = []; public gui: GuiObject[] = [];
private _events: PIXI.EventEmitter = new PIXI.EventEmitter();
public destroy() { public destroy() {
this.gui.forEach((element) => { this.gui.forEach((element) => {
element.destroy(); element.destroy();
@ -13,6 +16,11 @@ export default class Scene {
public GetGuiObjectByName(name: string) { public GetGuiObjectByName(name: string) {
return this.gui.filter((obj) => obj.name == name); return this.gui.filter((obj) => obj.name == name);
} }
public get events(): PIXI.EventEmitter {
return this._events;
}
public init() { public init() {
// Definitions for scene elements. // Definitions for scene elements.
} }