From b7d084439fd0a26080f2fecdfda56861acd1bae3 Mon Sep 17 00:00:00 2001 From: koneko <67551503+koneko@users.noreply.github.com> Date: Thu, 28 Nov 2024 00:38:21 +0100 Subject: [PATCH] programing is a hard job, i have nothing to say --- src/classes/Assets.ts | 90 +++++++++++++++++++ src/classes/Bastion.ts | 57 +++++++----- src/classes/Definitions.ts | 84 +++++++++++++++++ .../{DynamicObject.ts => GameObject.ts} | 11 +-- src/classes/GuiObject.ts | 19 ++-- src/classes/gui/Button.ts | 60 +++++++++++++ src/main.ts | 21 +++-- src/scenes/Main.ts | 9 ++ src/scenes/Scene.ts | 16 ++++ 9 files changed, 325 insertions(+), 42 deletions(-) create mode 100644 src/classes/Assets.ts create mode 100644 src/classes/Definitions.ts rename src/classes/{DynamicObject.ts => GameObject.ts} (65%) create mode 100644 src/classes/gui/Button.ts create mode 100644 src/scenes/Scene.ts diff --git a/src/classes/Assets.ts b/src/classes/Assets.ts new file mode 100644 index 0000000..7ef42f1 --- /dev/null +++ b/src/classes/Assets.ts @@ -0,0 +1,90 @@ +import * as PIXI from 'pixi.js'; +import { CreepStatsDefinition, MissionDefinition, TowerDefinition } from './Definitions'; + +export default class Assets { + public static async LoadAssets() { + console.log('Loading Texture Assets'); + Assets.Button01Texture = await PIXI.Assets.load({ + src: '/assets/gui/button_01.png', + }); + Assets.Button02Texture = await PIXI.Assets.load({ + src: '/assets/gui/button_02.png', + }); + Assets.Frame01Texture = await PIXI.Assets.load({ + src: '/assets/gui/frame_01.png', + }); + Assets.Frame02Texture = await PIXI.Assets.load({ + src: '/assets/gui/frame_02.png', + }); + Assets.FrameGreenTexture = await PIXI.Assets.load({ + src: '/assets/gui/frame_green.png', + }); + Assets.FrameRedTexture = await PIXI.Assets.load({ + src: '/assets/gui/frame_red.png', + }); + Assets.FrameVioletTexture = await PIXI.Assets.load({ + src: '/assets/gui/frame_violet.png', + }); + Assets.HealthTexture = await PIXI.Assets.load({ + src: '/assets/gui/heart.png', + }); + Assets.GoldTexture = await PIXI.Assets.load({ + src: '/assets/gui/star.png', + }); + Assets.BasicCreepTexture = await PIXI.Assets.load({ + src: '/assets/creeps/basic.jpg', + }); + await this.LoadMissions(); + await this.LoadTowers(); + await this.LoadCreepStats(); + } + + public static async LoadCreepStats() { + const res = await fetch('/assets/CreepStats.json'); + const stats = await res.json(); + this.CreepStats = stats; + } + + private static async LoadMissions() { + Assets.Missions = [await this.LoadMission('/assets/missions/mission_01.json')]; + } + + private static async LoadTowers() { + const res = await fetch('/assets/Towers.json'); + const towers = await res.json(); + Assets.Towers = towers; + } + + private static async LoadMission(missionUrl: string) { + const res = await fetch(missionUrl); + const mission = await res.json(); + await this.LoadBackground(mission.mapImage.url); + return mission; + } + + private static async LoadBackground(backgroundUrl: string) { + let index = this.MissionBackgrounds.length - 1; + if (index == -1) index = 0; + this.MissionBackgrounds[index] = await PIXI.Assets.load({ + src: backgroundUrl, + }); + } + + public static BasicCreepTexture: PIXI.Texture; + + public static Frame01Texture: PIXI.Texture; + public static Frame02Texture: PIXI.Texture; + public static FrameGreenTexture: PIXI.Texture; + public static FrameRedTexture: PIXI.Texture; + public static FrameVioletTexture: 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 Missions: MissionDefinition[]; + public static Towers: TowerDefinition[]; + public static CreepStats: CreepStatsDefinition[]; + public static DebuggingEnabled: boolean = true; +} diff --git a/src/classes/Bastion.ts b/src/classes/Bastion.ts index 96c0fbc..d7caf2b 100644 --- a/src/classes/Bastion.ts +++ b/src/classes/Bastion.ts @@ -1,47 +1,62 @@ import * as PIXI from 'pixi.js'; -import DynamicObject from './DynamicObject'; +import GameObject from './GameObject'; +import GuiObject from './GuiObject'; +import Scene from '../scenes/Scene'; -export class Foundation { - public static _PIXIApp: PIXI.Application; - public static Master: Master; +export class environment { + public static app: PIXI.Application; + public static GameMaster: GameMaster; public static WindowHeight: number; public static WindowWidth: number; public static AspectRatio: number = 4 / 3; } -export default class Master { - private DynamicObjects: DynamicObject[]; +export default class GameMaster { + public currentScene: Scene; + private GameObjects: GameObject[]; private ticker: PIXI.Ticker; - public stage: PIXI.Container = new PIXI.Container({ - width: Foundation.WindowWidth, - height: Foundation.WindowHeight, - }); constructor() { - Foundation.Master = this; + environment.GameMaster = this; this.ticker = new PIXI.Ticker(); this.ticker.maxFPS = 60; this.ticker.minFPS = 30; this.ticker.add(() => this.update(this.ticker.elapsedMS)); } - public _CreateDynamicObject(object: DynamicObject) { - this.DynamicObjects.push(object); + public _CreateGameObject(object: GameObject) { + this.GameObjects.push(object); + environment.app.stage.addChild(object.container); } - public _RemoveDynamicObject(object: DynamicObject) { - this.DynamicObjects.splice(this.DynamicObjects.indexOf(object), 1); + public _RemoveGameObject(object: GameObject) { + this.GameObjects.splice(this.GameObjects.indexOf(object), 1); + environment.app.stage.removeChild(object.container); } - public RefreshStage() { - Foundation._PIXIApp.stage.removeChildren(); - this.stage.width = Foundation.WindowWidth; - this.stage.height = Foundation.WindowHeight; - Foundation._PIXIApp.stage.addChild(this.stage); + public _CreateGuiObject(object: GuiObject) { + this.currentScene.gui.push(object); + environment.app.stage.addChild(object.container); + } + + public _RemoveGuiObject(object: GuiObject) { + this.currentScene.gui.splice(this.currentScene.gui.indexOf(object), 1); + environment.app.stage.removeChild(object.container); + } + + public changeScene(newScene: Scene) { + if (this.currentScene) { + this.currentScene.destroy(); + } + this.GameObjects.forEach((element) => { + element.destroy(); + }); + this.currentScene = newScene; + this.currentScene.init(); } public update(elapsedMS) { - this.DynamicObjects.forEach((element) => { + this.GameObjects.forEach((element) => { element.update(elapsedMS); }); } diff --git a/src/classes/Definitions.ts b/src/classes/Definitions.ts new file mode 100644 index 0000000..4f8bcaa --- /dev/null +++ b/src/classes/Definitions.ts @@ -0,0 +1,84 @@ +export type MissionDefinition = { + name: string; + description: string; + mapImage: MapImageDefinition; + gameMap: GameMapDefinition; + rounds: MissionRoundDefinition[]; +}; + +export type MapImageDefinition = { + url: string; + width: number; + height: number; +}; + +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 CreepStatsDefinition = { + health: number; + speed: number; + special: Function; + resistance: CreepResistancesDefinition; +}; + +export type CreepResistancesDefinition = { + physical: number; + divine: number; + fire: number; + ice: number; + frostfire: number; +}; + +export type TowerDefinition = { + name: string; + stats: TowerStatsDefinition; +}; + +export type TowerStatsDefinition = { + damage: number; + cooldown: number; + gemSlotsAmount: number; + cost: number; + range: number; +}; + +export type PathDefinition = [[row: number, column: number]]; + +export enum CreepType { + Basic = 0, + Fast = 1, +} + +export enum TerrainType { + Restricted = 0, + Buildable = 1, + Path = 9, +} + +export enum GemType { + Fire = 0, + Yeti = 1, + Titalium = 2, + Soulforge = 3, +} + +export enum TowerType { + Shooting = 0, + Circle = 1, +} diff --git a/src/classes/DynamicObject.ts b/src/classes/GameObject.ts similarity index 65% rename from src/classes/DynamicObject.ts rename to src/classes/GameObject.ts index a8ea86b..69b5fc6 100644 --- a/src/classes/DynamicObject.ts +++ b/src/classes/GameObject.ts @@ -1,7 +1,7 @@ import * as PIXI from 'pixi.js'; -import { Foundation } from './Bastion'; +import { environment } from './Bastion'; -export default abstract class DynamicObject { +export default abstract class GameObject { public readonly name: string = this.constructor.name; protected _container: PIXI.Container = new PIXI.Container(); @@ -9,10 +9,10 @@ export default abstract class DynamicObject { protected _events: PIXI.EventEmitter = new PIXI.EventEmitter(); public destroy() { + environment.GameMaster._RemoveGameObject(this); this._events.removeAllListeners(); if (this._container.parent) this._container.parent.removeChild(this._container); this._container.destroy(); - Foundation.Master._RemoveDynamicObject(this); } public get container(): PIXI.Container { @@ -23,9 +23,10 @@ export default abstract class DynamicObject { return this._events; } - public update(elapsedMS) {} + public abstract update(elapsedMS): void; constructor() { - Foundation.Master._CreateDynamicObject(this); + // Define stuff that goes into this.container (visual elements), then call super(). + environment.GameMaster._CreateGameObject(this); } } diff --git a/src/classes/GuiObject.ts b/src/classes/GuiObject.ts index 5767253..3fac59f 100644 --- a/src/classes/GuiObject.ts +++ b/src/classes/GuiObject.ts @@ -1,12 +1,15 @@ import * as PIXI from 'pixi.js'; +import { environment } from './Bastion'; export default abstract class GuiObject { public readonly name: string = this.constructor.name; - protected _container: PIXI.Container; + protected _container: PIXI.Container = new PIXI.Container(); protected _events: PIXI.EventEmitter = new PIXI.EventEmitter(); + protected enabled: boolean; + public destroy() { this._events.removeAllListeners(); if (this._container.parent) this._container.parent.removeChild(this._container); @@ -24,18 +27,24 @@ export default abstract class GuiObject { public onClick(e: PIXI.FederatedPointerEvent) { console.warn(`[${this.name} does not implement GuiObject.onClick()]`); } + public onWheel(e: PIXI.FederatedWheelEvent) { console.warn(`[${this.name} does not implement GuiObject.onWheel()]`); } - constructor() { - this._container = new PIXI.Container(); + public setEnabled(enabled: boolean) { + this.enabled = enabled; + } + + constructor(interactive?: boolean) { + environment.GameMaster._CreateGuiObject(this); + if (!interactive) return; this._container.interactive = true; this._container.onwheel = (e) => { - this.onWheel(e); + if (this.enabled) this.onWheel(e); }; this._container.onclick = (e) => { - this.onClick(e); + if (this.enabled) this.onClick(e); }; } } diff --git a/src/classes/gui/Button.ts b/src/classes/gui/Button.ts new file mode 100644 index 0000000..bf2ce64 --- /dev/null +++ b/src/classes/gui/Button.ts @@ -0,0 +1,60 @@ +import * as PIXI from 'pixi.js'; +import GuiObject from '../GuiObject'; +import Assets from '../Assets'; + +export enum ButtonTexture { + Button01 = 0, + Button02 = 1, +} + +export default class Button extends GuiObject { + private caption: string; + private bounds: PIXI.Rectangle; + private buttonTexture: PIXI.Texture; + + private buttonSprite: PIXI.NineSliceSprite; + private buttonText: PIXI.Text; + + setCaption(caption: string) { + this.caption = caption; + this.buttonText.text = caption; + } + + constructor(bounds: PIXI.Rectangle, caption: string, buttonTexture: ButtonTexture, enabled: boolean = true) { + super(true); + if (buttonTexture == ButtonTexture.Button01) this.buttonTexture = Assets.Button01Texture; + if (buttonTexture == ButtonTexture.Button02) this.buttonTexture = Assets.Button02Texture; + this.caption = caption; + this.enabled = enabled; + this.bounds = bounds; + this.container.x = this.bounds.x; + this.container.y = this.bounds.y; + this.container.width = this.bounds.width; + this.container.height = this.bounds.height; + this.buttonSprite = new PIXI.NineSliceSprite({ + texture: this.buttonTexture, + leftWidth: 100, + topHeight: 100, + rightWidth: 100, + bottomHeight: 100, + }); + this.buttonSprite.x = 0; + this.buttonSprite.y = 0; + this.buttonSprite.width = this.bounds.width; + this.buttonSprite.height = this.bounds.height; + this.container.addChild(this.buttonSprite); + this.buttonText = new PIXI.Text({ + text: this.caption, + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 24, + }), + }); + this.container.addChild(this.buttonText); + this.buttonText.anchor.set(0.5, 0.5); + this.buttonText.x = this.bounds.width / 2; + this.buttonText.y = this.bounds.height / 2; + this.container.x = this.bounds.x; + this.container.y = this.bounds.y; + } +} diff --git a/src/main.ts b/src/main.ts index 399b6ac..0ac6560 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,17 @@ import * as PIXI from 'pixi.js'; -import Master, { Foundation } from './classes/Bastion'; +import GameMaster, { environment } from './classes/Bastion'; +import Assets from './classes/Assets'; (async () => { const app = new PIXI.Application(); - const aspectRatio = Foundation.AspectRatio; + const aspectRatio = environment.AspectRatio; const maxWidth = window.innerWidth; const maxHeight = window.innerHeight; const width = Math.min(maxWidth * 0.75, maxHeight * aspectRatio); const height = width / aspectRatio; - Foundation.WindowWidth = width; - Foundation.WindowHeight = height; - Foundation._PIXIApp = app; + environment.WindowWidth = width; + environment.WindowHeight = height; + environment.app = app; await app.init({ width: width, @@ -19,16 +20,14 @@ import Master, { Foundation } from './classes/Bastion'; sharedTicker: true, preference: 'webgl', }); - document.body.appendChild(app.canvas); - let master = new Master(); - master.RefreshStage(); + await Assets.LoadAssets(); + new GameMaster(); window.addEventListener('resize', () => { const newWidth = Math.min(window.innerWidth * 0.75, window.innerHeight * aspectRatio); const newHeight = newWidth / aspectRatio; - Foundation.WindowWidth = newWidth; - Foundation.WindowHeight = newHeight; + environment.WindowWidth = newWidth; + environment.WindowHeight = newHeight; app.renderer.resize(newWidth, newHeight); - master.RefreshStage(); }); })(); diff --git a/src/scenes/Main.ts b/src/scenes/Main.ts index e69de29..ec658fe 100644 --- a/src/scenes/Main.ts +++ b/src/scenes/Main.ts @@ -0,0 +1,9 @@ +import Button from '../classes/gui/Button'; +import Scene from './Scene'; +import * as PIXI from 'pixi.js'; + +export class MainScene extends Scene { + public init() { + new Button(new PIXI.Bounds(0, 0, 200, 200)); + } +} diff --git a/src/scenes/Scene.ts b/src/scenes/Scene.ts new file mode 100644 index 0000000..d495f8a --- /dev/null +++ b/src/scenes/Scene.ts @@ -0,0 +1,16 @@ +import GuiObject from '../classes/GuiObject'; + +export default class Scene { + public gui: GuiObject[]; + public destroy() { + this.gui.forEach((element) => { + element.destroy(); + }); + } + public GetGuiObject(object: GuiObject) { + return this.gui.find((obj) => obj == object); + } + public init() { + // Definitions for scene elements. + } +}