This commit is contained in:
Dalibor Čarapić 2024-09-28 19:47:03 +02:00
parent 7f2f1f514e
commit 7e9e178dc3
12 changed files with 288 additions and 138 deletions

View File

@ -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]
}
]
}
}

View File

@ -1,11 +1,29 @@
import * as PIXI from "pixi.js"; import * as PIXI from "pixi.js";
import { MissionDefinition } from "./Definitions";
export default class Assets { export default class Assets {
public static async LoadAssets() { public static async LoadAssets() {
console.log("Loading Texture Assets");
Assets.ButtonTexture = await PIXI.Assets.load({ Assets.ButtonTexture = await PIXI.Assets.load({
src: "/assets/gui/button_02.png", 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 ButtonTexture: PIXI.Texture;
public static Missions: MissionDefinition[];
} }

View File

@ -19,9 +19,9 @@ export default class Button extends GameObject {
constructor( constructor(
caption: string, caption: string,
bounds: PIXI.Rectangle,
color: PIXI.Color, color: PIXI.Color,
enabled: boolean = true enabled: boolean = true,
bounds?: PIXI.Rectangle
) { ) {
super(bounds); super(bounds);
this.caption = caption; this.caption = caption;
@ -33,9 +33,6 @@ export default class Button extends GameObject {
} }
protected draw() { protected draw() {
console.log(
`Drawing button ${this.caption} at ${JSON.stringify(this.bounds)}`
);
this.container.removeChildren(); this.container.removeChildren();
// const button = new PIXI.Graphics(); // const button = new PIXI.Graphics();
// button.rect(0, 0, this.bounds.width, this.bounds.height); // button.rect(0, 0, this.bounds.width, this.bounds.height);

43
src/base/Definitions.ts Normal file
View File

@ -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,
}

View File

@ -35,7 +35,7 @@ export default class Game extends GameObject {
const missionSelectScene = new MissionMenuSelect(this.bounds); const missionSelectScene = new MissionMenuSelect(this.bounds);
missionSelectScene.events.on("mission", (mission) => { missionSelectScene.events.on("mission", (mission) => {
console.log("Mission selected", mission); console.log("Mission selected", mission);
this.setScene(new GameScene(this.bounds)); this.setScene(new GameScene(mission, this.bounds));
}); });
missionSelectScene.events.on("back", () => { missionSelectScene.events.on("back", () => {
this.onMainMenu(); this.onMainMenu();

View File

@ -6,8 +6,19 @@ export default abstract class GameObject {
private _events: PIXI.EventEmitter = new PIXI.EventEmitter(); private _events: PIXI.EventEmitter = new PIXI.EventEmitter();
public setBounds(x: number, y: number, width: number, height: number) { public setBounds(bounds: PIXI.Rectangle): void;
this.bounds = new PIXI.Rectangle(x, y, width, height); 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. this.triggerBoundsChanged(); // GameObject implements this.
} }
@ -32,8 +43,8 @@ export default abstract class GameObject {
protected abstract draw(): void; protected abstract draw(): void;
constructor(bounds: PIXI.Rectangle) { constructor(bounds?: PIXI.Rectangle) {
this.bounds = bounds; this.bounds = bounds ?? new PIXI.Rectangle(0, 0, 0, 0);
this._container = new PIXI.Container(); this._container = new PIXI.Container();
} }
} }

View File

@ -1,18 +1,22 @@
import * as PIXI from "pixi.js"; import * as PIXI from "pixi.js";
import GameObject from "../base/GameObject"; import GameObject from "../base/GameObject";
import { GameMapDefinition, TerrainType } from "../base/Definitions";
export enum CellType {
Path,
NoBuild,
Build,
Undefined,
}
export class Cell extends GameObject { export class Cell extends GameObject {
public type: CellType; public type: TerrainType;
constructor(bounds: PIXI.Rectangle, type: CellType) { public row: number;
public column: number;
constructor(
type: TerrainType,
row: number,
column: number,
bounds?: PIXI.Rectangle
) {
super(bounds); super(bounds);
this.type = type; this.type = type;
this.row = row;
this.column = column;
this.draw(); this.draw();
} }
@ -21,17 +25,11 @@ export class Cell extends GameObject {
let g = new PIXI.Graphics(); let g = new PIXI.Graphics();
g.rect(0, 0, this.bounds.width, this.bounds.height); g.rect(0, 0, this.bounds.width, this.bounds.height);
switch (this.type) { switch (this.type) {
case CellType.Path: case TerrainType.Restricted:
g.fill(0x00ff00);
break;
case CellType.NoBuild:
g.fill(0xff0000); g.fill(0xff0000);
break; break;
case CellType.Build: case TerrainType.Buildable:
g.fill(0x0000ff); g.fill(0x00ff00);
break;
case CellType.Undefined:
g.fill(0x000000);
break; break;
} }
this.container.addChild(g); this.container.addChild(g);
@ -41,35 +39,45 @@ export class Cell extends GameObject {
} }
export class Grid extends GameObject { export class Grid extends GameObject {
public rows: number; private gameMap: GameMapDefinition;
public columns: number; private cells: Cell[] = [];
public cells: Array<Cell>;
constructor(bounds: PIXI.Rectangle, rows, columns) { constructor(map: GameMapDefinition, bounds?: PIXI.Rectangle) {
super(bounds); super(bounds);
this.rows = rows; this.gameMap = map;
this.columns = columns; for (let y = 0; y < this.gameMap.rows; y++) {
for (let y = 0; y < rows; y++) { for (let x = 0; x < this.gameMap.columns; x++) {
for (let x = 0; x < columns; x++) { let cell = new Cell(this.gameMap.cells[x][y], x, y);
let cell = new Cell(
new PIXI.Rectangle(
x,
y,
this.gridUnitsToPixels(1),
this.gridUnitsToPixels(1)
),
CellType.Undefined
);
this.cells.push(cell); 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() { private getPixelScalingFactor() {
const pixelScaleX = this.container.width / this.columns; const pixelScaleX = this.container.width / this.gameMap.columns;
const pixelScaleY = this.container.height / this.rows; const pixelScaleY = this.container.height / this.gameMap.rows;
return pixelScaleX < pixelScaleY ? pixelScaleX : pixelScaleY; return pixelScaleX < pixelScaleY ? pixelScaleX : pixelScaleY;
} }

View File

@ -2,10 +2,41 @@ import GameObject from "../base/GameObject";
import * as PIXI from "pixi.js"; import * as PIXI from "pixi.js";
export default class MissionStats extends GameObject { export default class MissionStats extends GameObject {
constructor(bounds: PIXI.Rectangle) { private hp: number = 100;
super(bounds); private gold: number = 0;
public setHP(hp: number) {
this.hp = hp;
this.draw(); 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;
}
} }

View File

@ -1,32 +1,62 @@
import Button from "../base/Button"; 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 SceneBase from "./SceneBase";
import * as PIXI from "pixi.js"; import * as PIXI from "pixi.js";
export default class GameScene extends SceneBase { 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); super(bounds);
this._ticker = new PIXI.Ticker(); this.ticker = new PIXI.Ticker();
this._ticker = new PIXI.Ticker(); this.ticker = new PIXI.Ticker();
this._ticker.maxFPS = 60; this.ticker.maxFPS = 60;
this._ticker.minFPS = 30; this.ticker.minFPS = 30;
this._ticker.add(this.update); this.ticker.add(this.update);
this._ticker.start(); this.ticker.start();
this.stats = new MissionStats(100, 200);
this.grid = new Grid(mission.gameMap);
this.draw(); 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() { public destroy() {
super.destroy(); super.destroy();
this._ticker.stop(); this.ticker.stop();
this._ticker.destroy(); this.ticker.destroy();
} }
public update() {} public update() {}
protected draw() { protected draw() {
console.log("Creating Game Scene ", this.bounds); console.log("Drawing Game Scene ", this.bounds);
this.container.removeChildren(); 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.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;
} }

View File

@ -6,8 +6,16 @@ export default class MainMenu extends SceneBase {
private _newGameButton: Button; private _newGameButton: Button;
private _settingsButton: Button; private _settingsButton: Button;
constructor(bounds: PIXI.Rectangle) { constructor(bounds?: PIXI.Rectangle) {
super(bounds); 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(); this.draw();
} }
@ -18,34 +26,19 @@ export default class MainMenu extends SceneBase {
g.rect(0, 0, this.bounds.width, this.bounds.height); g.rect(0, 0, this.bounds.width, this.bounds.height);
g.fill(0x000000); g.fill(0x000000);
this.container.addChild(g); this.container.addChild(g);
this._newGameButton = new Button( this._newGameButton.setBounds(
"New Game",
new PIXI.Rectangle(
this.bounds.width / 2 - 300 / 2, this.bounds.width / 2 - 300 / 2,
this.bounds.height / 2 - 80, this.bounds.height / 2 - 80,
300, 300,
60 60
),
new PIXI.Color("blue")
); );
this._newGameButton.events.on("click", () => { this._settingsButton.setBounds(
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.width / 2 - 300 / 2,
this.bounds.height / 2 + 20, this.bounds.height / 2 + 20,
300, 300,
60 60
),
new PIXI.Color("gray")
); );
this._settingsButton.events.on("click", () => { this.container.addChild(this._newGameButton.container);
this.events.emit("settings");
});
this.container.addChild(this._settingsButton.container); this.container.addChild(this._settingsButton.container);
this.container.x = this.bounds.x; this.container.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;

View File

@ -1,4 +1,6 @@
import Assets from "../base/Assets";
import Button from "../base/Button"; import Button from "../base/Button";
import { MissionDefinition } from "../base/Definitions";
import SceneBase from "./SceneBase"; import SceneBase from "./SceneBase";
import * as PIXI from "pixi.js"; import * as PIXI from "pixi.js";
@ -7,42 +9,40 @@ export default class MissionMenuSelect extends SceneBase {
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(bounds); super(bounds);
for (const mission of Assets.Missions) {
this.addMission(mission);
}
this.addButton("Back", () => {
this.events.emit("back");
});
this.draw(); this.draw();
} }
protected draw() { protected draw() {
this.container.removeChildren(); this.container.removeChildren();
this._buttons = [];
const g = new PIXI.Graphics(); const g = new PIXI.Graphics();
g.rect(0, 0, this.bounds.width, this.bounds.height); g.rect(0, 0, this.bounds.width, this.bounds.height);
g.fill(0x000000); g.fill(0x000000);
this.container.addChild(g); this.container.addChild(g);
this.addMission("Mission 1"); let y = 50;
this.addMission("Mission 2"); for (const button of this._buttons) {
this.addMission("Mission 3"); button.setBounds(this.bounds.width / 2 - 300 / 2, y, 300, 60);
this.addMission("Mission 4"); y += 80;
this.addButton("Back", () => { this.container.addChild(button.container);
this.events.emit("back"); }
});
this.container.x = this.bounds.x; this.container.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;
} }
private addMission(mission: string) { private addMission(mission: MissionDefinition) {
this.addButton(mission, () => { this.addButton(mission.name, () => {
this.events.emit("mission", mission); this.events.emit("mission", mission);
}); });
} }
private addButton(caption: string, onClick: () => void) { private addButton(caption: string, onClick: () => void) {
const yOffset = this._buttons.length * 80 + 100; const button = new Button(caption, new PIXI.Color("white"));
const button = new Button(
caption,
new PIXI.Rectangle(100, yOffset, this.bounds.width - 200, 60),
new PIXI.Color("white")
);
button.events.on("click", onClick); button.events.on("click", onClick);
this._buttons.push(button); this._buttons.push(button);
this.container.addChild(button.container);
} }
} }

View File

@ -3,9 +3,6 @@ import SceneBase from "./SceneBase";
import * as PIXI from "pixi.js"; import * as PIXI from "pixi.js";
export default class SettingsMenu extends SceneBase { export default class SettingsMenu extends SceneBase {
private _newGameButton: Button;
private _settingsButton: Button;
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(bounds); super(bounds);
this.draw(); this.draw();
@ -16,36 +13,7 @@ export default class SettingsMenu extends SceneBase {
const g = new PIXI.Graphics(); const g = new PIXI.Graphics();
g.rect(0, 0, this.bounds.width, this.bounds.height); g.rect(0, 0, this.bounds.width, this.bounds.height);
g.fill(0x000000); 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.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;
} }