Merge pull request #1 from koneko/egocentric

Egocentric
This commit is contained in:
Koneko 2024-10-18 14:43:29 +02:00 committed by GitHub
commit c8d6ab651d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 194 additions and 113 deletions

View File

@ -0,0 +1,14 @@
[
{
"health": 2,
"speed": 0.0005,
"special": null,
"resistance": {
"physical": 0,
"divine": 0,
"fire": 0,
"ice": 0,
"frostfire": 0
}
}
]

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -1,6 +1,11 @@
{ {
"name": "Mission 1", "name": "Mission 1",
"description": "This is the first mission", "description": "This is the first mission",
"mapImage": {
"url": "/assets/maps/mission_01.png",
"width": 1200,
"height": 900
},
"gameMap": { "gameMap": {
"rows": 15, "rows": 15,
"columns": 20, "columns": 20,
@ -19,29 +24,36 @@
[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, 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, 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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
], ],
"paths": [ "paths": [
[ [
[0, 3], [8, 0],
[1, 3], [8, 1],
[2, 3], [9, 2],
[3, 3], [10, 2],
[3, 2], [11, 2],
[4, 2], [12, 3],
[5, 2], [12, 4],
[6, 2], [12, 5],
[7, 2], [12, 6],
[7, 3], [12, 7],
[7, 4], [12, 8],
[8, 4], [12, 9],
[9, 4] [11, 10],
[10, 10],
[9, 10],
[8, 11],
[8, 12],
[8, 13],
[7, 14],
[6, 14],
[5, 14],
[4, 14],
[3, 14],
[2, 14],
[1, 14],
[0, 15]
] ]
] ]
}, },

View File

@ -1,5 +1,5 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { MissionDefinition } from './Definitions'; import { CreepStats, MissionDefinition } from './Definitions';
export default class Assets { export default class Assets {
public static async LoadAssets() { public static async LoadAssets() {
@ -12,6 +12,13 @@ export default class Assets {
}); });
console.log('Loading Missions'); console.log('Loading Missions');
await this.LoadMissions(); await this.LoadMissions();
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() { private static async LoadMissions() {
@ -21,11 +28,22 @@ export default class Assets {
private static async LoadMission(missionUrl: string) { private static async LoadMission(missionUrl: string) {
const res = await fetch(missionUrl); const res = await fetch(missionUrl);
const mission = await res.json(); const mission = await res.json();
await this.LoadBackground(mission.mapImage.url);
return mission; 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 ButtonTexture: PIXI.Texture; public static ButtonTexture: PIXI.Texture;
public static BasicCreepTexture: PIXI.Texture; public static BasicCreepTexture: PIXI.Texture;
public static MissionBackgrounds: PIXI.Texture[] = [];
public static Missions: MissionDefinition[]; public static Missions: MissionDefinition[];
public static CreepStats: CreepStats[];
} }

View File

@ -1,11 +1,17 @@
export type MissionDefinition = { export type MissionDefinition = {
name: string; name: string;
description: string; description: string;
mapImageUrl: string; mapImage: MapImageDefinition;
gameMap: GameMapDefinition; gameMap: GameMapDefinition;
rounds: MissionRoundDefinition[]; rounds: MissionRoundDefinition[];
}; };
export type MapImageDefinition = {
url: string;
width: number;
height: number;
};
export type GameMapDefinition = { export type GameMapDefinition = {
rows: number; rows: number;
columns: number; columns: number;
@ -24,6 +30,21 @@ export type WaveDefinition = {
creeps: CreepType[]; creeps: CreepType[];
}; };
export type CreepStats = {
health: number;
speed: number;
special: Function;
resistance: CreepResistances;
};
export type CreepResistances = {
physical: number;
divine: number;
fire: number;
ice: number;
frostfire: number;
};
export type PathDefinition = [[row: number, column: number]]; export type PathDefinition = [[row: number, column: number]];
export enum CreepType { export enum CreepType {
@ -34,6 +55,7 @@ export enum CreepType {
export enum TerrainType { export enum TerrainType {
Restricted = 0, Restricted = 0,
Buildable = 1, Buildable = 1,
Path = 9,
} }
export enum GemType { export enum GemType {

View File

@ -13,7 +13,7 @@ export default class Game extends GameObject {
super(bounds); super(bounds);
let params = new URLSearchParams(location.href); let params = new URLSearchParams(location.href);
if (params.entries().next().value[1] == 'game') { if (params.entries().next().value[1] == 'game') {
this.setScene(new GameScene(Assets.Missions[0], this.bounds)); this.setScene(new GameScene(Assets.Missions[0], 0, this.bounds));
} else this.onMainMenu(); } else this.onMainMenu();
} }
@ -39,7 +39,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(mission, this.bounds)); this.setScene(new GameScene(mission, Assets.Missions.indexOf(mission), this.bounds));
}); });
missionSelectScene.events.on('back', () => { missionSelectScene.events.on('back', () => {
this.onMainMenu(); this.onMainMenu();
@ -53,12 +53,7 @@ export default class Game extends GameObject {
protected triggerBoundsChanged(): void { protected triggerBoundsChanged(): void {
if (this._currentScene) { if (this._currentScene) {
this._currentScene.setBounds( this._currentScene.setBounds(0, 0, this.bounds.width, this.bounds.height);
0,
0,
this.bounds.width,
this.bounds.height
);
} }
} }

View File

@ -1,40 +1,8 @@
import Assets from '../base/Assets'; import Assets from '../base/Assets';
import { CreepType, PathDefinition } from '../base/Definitions'; import { CreepStats, CreepType, PathDefinition } from '../base/Definitions';
import GameObject from '../base/GameObject'; import GameObject from '../base/GameObject';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import GameScene from '../scenes/GameScene';
export function CreepStats(ctype: CreepType): object {
switch (ctype) {
case CreepType.Basic:
return {
health: 2,
speed: 0.45,
special: null,
resistance: {
physical: 0,
divine: 0,
fire: 0,
ice: 0,
frostfire: 0,
},
};
case CreepType.Fast:
throw new Error('Fast creep not defined.');
default:
return {
health: null,
speed: null,
special: null,
resistance: {
physical: null,
divine: null,
fire: null,
ice: null,
frostfire: null,
},
};
}
}
export enum CreepEvents { export enum CreepEvents {
Died = 'died', Died = 'died',
@ -46,19 +14,29 @@ export enum CreepEvents {
export default class Creep extends GameObject { export default class Creep extends GameObject {
public creepType: CreepType; public creepType: CreepType;
private path: PathDefinition; private path: PathDefinition;
private stats: CreepStats;
private pathIndex: number = 0; private pathIndex: number = 0;
private speed: number = 0.002; private speed: number;
public health: number = 2; private gameScene: GameScene;
public health: number;
public escaped: boolean = false; public escaped: boolean = false;
public died: boolean = false; public died: boolean = false;
public x: number; // X and Y are local to the grid, not canvas public x: number; // X and Y are local to the grid, not canvas
public y: number; public y: number;
constructor(creepType: CreepType, path: PathDefinition, bounds?: PIXI.Rectangle) { constructor(creepType: CreepType, path: PathDefinition, gameScene: GameScene, bounds?: PIXI.Rectangle) {
super(bounds); super(bounds);
this.gameScene = gameScene;
this.creepType = creepType; this.creepType = creepType;
this.stats = Assets.CreepStats[this.creepType];
this.speed = this.stats.speed;
this.health = this.stats.health;
this.path = path; this.path = path;
this.x = path[0][1] + 0.5; // centered this.x = path[0][1] + 0.5; // centered
this.y = path[0][0] + 0.5; this.y = path[0][0] + 0.5;
this.gameScene.grid.container.addChild(this.container);
}
private gridUnitsToPixels(gridUnits) {
return this.gameScene.grid.gridUnitsToPixels(gridUnits);
} }
public update(elapsedMS: number) { public update(elapsedMS: number) {
if (this.pathIndex + 1 == this.path.length) { if (this.pathIndex + 1 == this.path.length) {
@ -101,7 +79,14 @@ export default class Creep extends GameObject {
this.x += deltaX; this.x += deltaX;
this.y += deltaY; this.y += deltaY;
if (increaseIndex) this.pathIndex++; if (increaseIndex) this.pathIndex++;
this.events.emit(CreepEvents.Moved, this); this.setBounds(
new PIXI.Rectangle(
this.gridUnitsToPixels(this.x),
this.gridUnitsToPixels(this.y),
this.gridUnitsToPixels(0.5),
this.gridUnitsToPixels(0.6)
)
);
} }
public takeDamage(amount: number) { public takeDamage(amount: number) {
@ -112,6 +97,11 @@ export default class Creep extends GameObject {
} }
} }
public override destroy() {
super.destroy();
this.draw = null;
this.container.removeChildren();
}
protected draw() { protected draw() {
this.container.removeChildren(); this.container.removeChildren();
const sprite = new PIXI.Sprite(Assets.BasicCreepTexture); const sprite = new PIXI.Sprite(Assets.BasicCreepTexture);

View File

@ -2,19 +2,23 @@ import * as PIXI from 'pixi.js';
import GameObject from '../base/GameObject'; import GameObject from '../base/GameObject';
import { GameMapDefinition, TerrainType } from '../base/Definitions'; import { GameMapDefinition, TerrainType } from '../base/Definitions';
import Creep, { CreepEvents } from './Creep'; import Creep, { CreepEvents } from './Creep';
import GameScene from '../scenes/GameScene';
import Assets from '../base/Assets';
export class Cell extends GameObject { export class Cell extends GameObject {
public type: TerrainType; public type: TerrainType;
public row: number; public row: number;
public column: number; public column: number;
public isPath: boolean = false; public isPath: boolean = false;
private grid: Grid;
constructor(type: TerrainType, row: number, column: number, isPath: boolean, bounds?: PIXI.Rectangle) { constructor(type: TerrainType, row: number, column: number, isPath: boolean, grid: Grid, bounds?: PIXI.Rectangle) {
super(bounds); super(bounds);
this.type = type; this.type = type;
this.row = row; this.row = row;
this.column = column; this.column = column;
this.isPath = isPath; this.isPath = isPath;
this.grid = grid;
this.draw(); this.draw();
} }
@ -26,31 +30,55 @@ export class Cell extends GameObject {
case TerrainType.Restricted: case TerrainType.Restricted:
g.fill(0xff0000); g.fill(0xff0000);
break; break;
case TerrainType.Path:
g.fill(0xff00ff);
break;
case TerrainType.Buildable: case TerrainType.Buildable:
g.fill(0x00ff00); g.stroke(0x00ff00);
break; break;
} }
this.container.addChild(g); this.container.addChild(g);
this.container.x = this.bounds.x; this.container.x = this.bounds.x;
this.container.y = this.bounds.y; this.container.y = this.bounds.y;
return; // comment to enable debugging
const text = new PIXI.Text({
text: `${this.row}|${this.column}`,
style: new PIXI.TextStyle({
fill: 0xffffff,
dropShadow: true,
fontSize: 16,
}),
});
this.container.addChild(text);
text.anchor.set(0.5, 0.5);
text.x = this.bounds.width / 2;
text.y = this.bounds.height / 2;
if (this.isPath) text.text += 'P';
} }
} }
export class Grid extends GameObject { export class Grid extends GameObject {
private gameMap: GameMapDefinition; private gameMap: GameMapDefinition;
private cells: Cell[] = []; private cells: Cell[] = [];
private creeps: Creep[] = []; private gameScene: GameScene;
public creeps: Creep[] = [];
constructor(map: GameMapDefinition, bounds?: PIXI.Rectangle) { constructor(map: GameMapDefinition, gameScene: GameScene, bounds?: PIXI.Rectangle) {
super(bounds); super(bounds);
this.gameMap = map; this.gameMap = map;
this.gameScene = gameScene;
console.log(this.gameMap.paths); console.log(this.gameMap.paths);
for (let y = 0; y < this.gameMap.rows; y++) { for (let y = 0; y < this.gameMap.columns; y++) {
for (let x = 0; x < this.gameMap.columns; x++) { for (let x = 0; x < this.gameMap.rows; x++) {
let type = this.gameMap.cells[x][y]; let type;
try {
type = this.gameMap.cells[x][y];
} catch (e) {
type = 1;
}
const isPath = this.gameMap.paths.some((path) => path.some((p) => p[0] === x && p[1] === y)); const isPath = this.gameMap.paths.some((path) => path.some((p) => p[0] === x && p[1] === y));
if (isPath) type = TerrainType.Restricted; if (isPath) type = TerrainType.Path;
let cell = new Cell(type, x, y, isPath); let cell = new Cell(type, x, y, isPath, this);
this.cells.push(cell); this.cells.push(cell);
} }
} }
@ -59,38 +87,30 @@ export class Grid extends GameObject {
} }
public addCreep(creep: Creep) { public addCreep(creep: Creep) {
this.creeps.push(creep); this.creeps.push(creep);
creep.events.on(CreepEvents.Moved, (movedCreep) => {
this.onCreepMoved(movedCreep);
});
creep.events.on(CreepEvents.Died, (diedCreep) => { creep.events.on(CreepEvents.Died, (diedCreep) => {
this.onCreepDiedOrEscaped(diedCreep); this.onCreepDiedOrEscaped(diedCreep);
}); });
creep.events.on(CreepEvents.Escaped, (escapedCreep) => { creep.events.on(CreepEvents.Escaped, (escapedCreep) => {
this.onCreepDiedOrEscaped(escapedCreep); this.onCreepDiedOrEscaped(escapedCreep);
}); });
this.draw();
}
private onCreepMoved(movedCreep: Creep) {
movedCreep.setBounds(
new PIXI.Rectangle(
this.gridUnitsToPixels(movedCreep.x),
this.gridUnitsToPixels(movedCreep.y),
this.gridUnitsToPixels(0.5),
this.gridUnitsToPixels(0.6)
)
);
} }
private onCreepDiedOrEscaped(creep: Creep) { private onCreepDiedOrEscaped(creep: Creep) {
this.creeps.splice(this.creeps.indexOf(creep), 1); this.creeps.splice(this.creeps.indexOf(creep), 1);
this.draw(); creep.destroy();
} }
protected draw() { protected draw() {
console.log('Drawing Grid', this.bounds); console.log('Drawing Grid', this.bounds);
this.container.removeChildren(); this.container.removeChildren();
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 + 100);
g.fill(0x00aa00); // g.fill(0xffffff);
this.container.addChild(g); // this.container.addChild(g);
let background = new PIXI.Sprite(Assets.MissionBackgrounds[this.gameScene.missionIndex]);
background.x = 0;
background.y = 0;
background.width = this.bounds.width;
background.height = this.bounds.height;
this.container.addChild(background);
for (let cell of this.cells) { for (let cell of this.cells) {
cell.setBounds( cell.setBounds(
this.gridUnitsToPixels(cell.column), this.gridUnitsToPixels(cell.column),
@ -100,16 +120,6 @@ export class Grid extends GameObject {
); );
this.container.addChild(cell.container); this.container.addChild(cell.container);
} }
for (const creep of this.creeps) {
creep.setBounds(
this.gridUnitsToPixels(creep.x),
this.gridUnitsToPixels(creep.y),
this.gridUnitsToPixels(0.5),
this.gridUnitsToPixels(0.6)
);
// console.log(creep.getBounds());
this.container.addChild(creep.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;
} }

0
src/components/Tower.ts Normal file
View File

View File

@ -1,6 +1,7 @@
import { CreepType, MissionRoundDefinition, PathDefinition } from '../base/Definitions'; import { CreepType, MissionRoundDefinition, PathDefinition } from '../base/Definitions';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import Creep, { CreepEvents } from './Creep'; import Creep, { CreepEvents } from './Creep';
import GameScene from '../scenes/GameScene';
export enum WaveManagerEvents { export enum WaveManagerEvents {
CreepSpawned = 'creepSpawned', CreepSpawned = 'creepSpawned',
@ -21,11 +22,13 @@ export default class WaveManager {
private paths: PathDefinition[]; private paths: PathDefinition[];
private ticks: number = 0; private ticks: number = 0;
private started: boolean = false; private started: boolean = false;
private gameScene: GameScene;
public finished: boolean = false; public finished: boolean = false;
public events = new PIXI.EventEmitter(); public events = new PIXI.EventEmitter();
constructor(rounds: MissionRoundDefinition[], paths: PathDefinition[]) { constructor(rounds: MissionRoundDefinition[], paths: PathDefinition[], gameScene) {
this.rounds = rounds; this.rounds = rounds;
this.paths = paths; this.paths = paths;
this.gameScene = gameScene;
} }
public start(roundIndex) { public start(roundIndex) {
this.started = true; this.started = true;
@ -36,7 +39,7 @@ export default class WaveManager {
this.rounds[roundIndex].waves.forEach((wave) => { this.rounds[roundIndex].waves.forEach((wave) => {
tickToSpawnAt += wave.firstCreepSpawnTick; tickToSpawnAt += wave.firstCreepSpawnTick;
wave.creeps.forEach((creep) => { wave.creeps.forEach((creep) => {
const creepObj = new Creep(creep, this.paths[0]); const creepObj = new Creep(creep, this.paths[0], this.gameScene);
const creepInstance = { const creepInstance = {
creep: creepObj, creep: creepObj,
tickToSpawnAt, tickToSpawnAt,

View File

@ -13,17 +13,21 @@ enum RoundMode {
} }
export default class GameScene extends SceneBase { export default class GameScene extends SceneBase {
public grid: Grid;
private ticker: PIXI.Ticker; private ticker: PIXI.Ticker;
private stats: MissionStats; private stats: MissionStats;
private grid: Grid;
private waveManager: WaveManager; private waveManager: WaveManager;
private roundMode = RoundMode.Purchase; private roundMode = RoundMode.Purchase;
private changeRoundButton: Button; private changeRoundButton: Button;
private currentRound: number = 0; private currentRound: number = 0;
public missionIndex: number;
constructor(mission: MissionDefinition, bounds: PIXI.Rectangle) { private gridWidth: number;
private gridHeight: number;
constructor(mission: MissionDefinition, missionIndex: number, bounds: PIXI.Rectangle) {
super(bounds); super(bounds);
this.waveManager = new WaveManager(mission.rounds, mission.gameMap.paths); this.waveManager = new WaveManager(mission.rounds, mission.gameMap.paths, this);
this.waveManager.events.on(WaveManagerEvents.CreepSpawned, (creep: Creep) => { this.waveManager.events.on(WaveManagerEvents.CreepSpawned, (creep: Creep) => {
this.grid.addCreep(creep); this.grid.addCreep(creep);
creep.events.on(CreepEvents.Escaped, () => { creep.events.on(CreepEvents.Escaped, () => {
@ -31,12 +35,15 @@ export default class GameScene extends SceneBase {
}); });
}); });
this.stats = new MissionStats(100, 200); this.stats = new MissionStats(100, 200);
this.grid = new Grid(mission.gameMap); this.grid = new Grid(mission.gameMap, this);
this.gridWidth = mission.mapImage.width;
this.gridHeight = mission.mapImage.height;
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.elapsedMS)); // bruh this.ticker.add(() => this.update(this.ticker.elapsedMS)); // bruh
this.ticker.start(); this.ticker.start();
this.missionIndex = missionIndex;
this.changeRoundButton = new Button('Start', new PIXI.Color('white'), true); this.changeRoundButton = new Button('Start', new PIXI.Color('white'), true);
this.changeRoundButton.events.on('click', () => { this.changeRoundButton.events.on('click', () => {
console.log('clicked'); console.log('clicked');
@ -56,6 +63,7 @@ export default class GameScene extends SceneBase {
public update(elapsedMS: number) { public update(elapsedMS: number) {
if (this.checkGameOver()) return; if (this.checkGameOver()) return;
this.waveManager.update(elapsedMS); this.waveManager.update(elapsedMS);
this.grid.creeps.forEach((creep) => creep.update(elapsedMS));
this.checkToEndCombat(); this.checkToEndCombat();
} }
@ -115,10 +123,19 @@ export default class GameScene extends SceneBase {
private getGridBounds(): PIXI.Rectangle { private getGridBounds(): PIXI.Rectangle {
// Center / Center // Center / Center
return new PIXI.Rectangle(this.bounds.width / 2 - 600 / 2, this.bounds.height / 2 - 600 / 2, 600, 600); let width = 600;
let height = 600;
return new PIXI.Rectangle(
this.bounds.width / 2 - this.gridWidth / 2,
this.bounds.height / 2 - this.gridHeight / 2,
this.gridWidth,
this.gridHeight
);
} }
private getChangeRoundButtonBounds(): PIXI.Rectangle { private getChangeRoundButtonBounds(): PIXI.Rectangle {
// Center / Center // Center / Center
return new PIXI.Rectangle(this.bounds.width - 300, this.bounds.height - 150, 300, 150); let width = 300;
let height = 150;
return new PIXI.Rectangle(this.bounds.width - width, this.bounds.height - height, width, height);
} }
} }