add proper asset loading and use for towers + projectiles (also projectile animation) + add health bars

This commit is contained in:
koneko 2025-01-17 00:29:51 +01:00
parent c8bf247104
commit 64795b1ebf
18 changed files with 129 additions and 42 deletions

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -4,6 +4,8 @@
"behaviour": "BasicTowerBehaviour", "behaviour": "BasicTowerBehaviour",
"sprite": "basic_tower", "sprite": "basic_tower",
"texture": null, "texture": null,
"projectileTextures": [],
"projectileTexturesArrayLength": 5,
"description": "The building block of society, nothing more basic exists.", "description": "The building block of society, nothing more basic exists.",
"stats": { "stats": {
"damage": 2, "damage": 2,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,14 +1,11 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { CreepDefinition, CreepStatsDefinition, MissionDefinition, TowerDefinition } from './Definitions'; import { CreepDefinition, MissionDefinition, TowerDefinition } from './Definitions';
import { Engine } from './Bastion'; import { Engine } from './Bastion';
export default class GameAssets { export default class GameAssets {
public static BasicTowerTexture: PIXI.Texture;
public static BasicProjectileTexture: PIXI.Texture;
public static Frame01Texture: PIXI.Texture; public static Frame01Texture: PIXI.Texture;
public static Frame02Texture: PIXI.Texture; public static Frame02Texture: PIXI.Texture;
public static Frame03Texture: PIXI.Texture;
public static FrameBackground: PIXI.Texture; public static FrameBackground: PIXI.Texture;
public static FrameTowerTab: PIXI.Texture; public static FrameTowerTab: PIXI.Texture;
public static VioletBackground: PIXI.Texture; public static VioletBackground: PIXI.Texture;
@ -29,6 +26,7 @@ export default class GameAssets {
private static text; private static text;
private static async Load(src) { private static async Load(src) {
this.text.text = 'Loading asset: ' + src; this.text.text = 'Loading asset: ' + src;
console.log('LOADING ' + src);
return await PIXI.Assets.load({ return await PIXI.Assets.load({
src: src, src: src,
}); });
@ -63,11 +61,12 @@ export default class GameAssets {
this.text.y = Engine.app.canvas.height / 2 + 50; this.text.y = Engine.app.canvas.height / 2 + 50;
this.text.anchor.set(0.5, 0.5); this.text.anchor.set(0.5, 0.5);
Engine.app.stage.addChild(this.text); Engine.app.stage.addChild(this.text);
this.Load('/aclonica.woff2'); await this.Load('/aclonica.woff2');
this.Button01Texture = await this.Load('/assets/gui/button_01.png'); this.Button01Texture = await this.Load('/assets/gui/button_01.png');
this.Button02Texture = await this.Load('/assets/gui/button_02.png'); this.Button02Texture = await this.Load('/assets/gui/button_02.png');
this.Frame01Texture = await this.Load('/assets/gui/frame_01.png'); this.Frame01Texture = await this.Load('/assets/gui/frame_01.png');
this.Frame02Texture = await this.Load('/assets/gui/frame_02.png'); this.Frame02Texture = await this.Load('/assets/gui/frame_02.png');
this.Frame03Texture = await this.Load('/assets/gui/frame_03.png');
this.FrameBackground = await this.Load('/assets/gui/background_01.png'); this.FrameBackground = await this.Load('/assets/gui/background_01.png');
this.FrameTowerTab = await this.Load('/assets/gui/background_02.png'); this.FrameTowerTab = await this.Load('/assets/gui/background_02.png');
this.VioletBackground = await this.Load('/assets/gui/frame_violet.png'); this.VioletBackground = await this.Load('/assets/gui/frame_violet.png');
@ -76,8 +75,6 @@ export default class GameAssets {
this.HealthTexture = await this.Load('/assets/gui/heart.png'); this.HealthTexture = await this.Load('/assets/gui/heart.png');
this.GoldTexture = await this.Load('/assets/gui/money.png'); this.GoldTexture = await this.Load('/assets/gui/money.png');
this.WaveTexture = await this.Load('/assets/gui/wave.png'); this.WaveTexture = await this.Load('/assets/gui/wave.png');
this.BasicTowerTexture = await this.Load('/assets/towers/basic_tower.png');
this.BasicProjectileTexture = await this.Load('/assets/projectiles/basic_tower.png');
await this.LoadMissions(); await this.LoadMissions();
await this.LoadTowers(); await this.LoadTowers();
await this.LoadCreeps(); await this.LoadCreeps();
@ -108,7 +105,17 @@ export default class GameAssets {
private static async LoadTowers() { private static async LoadTowers() {
const res = await fetch('/assets/json/Towers.json'); const res = await fetch('/assets/json/Towers.json');
const towers = await res.json(); const towers = await res.json();
GameAssets.Towers = towers; this.Towers = towers;
console.log(this.Towers);
for (let idx = 0; idx < this.Towers.length; idx++) {
const tower = this.Towers[idx];
for (let i = 0; i < tower.projectileTexturesArrayLength; i++) {
console.log(`WANT TO LOAD /assets/projectiles/${tower.sprite}/${i}.png`);
const texture = await this.Load(`/assets/projectiles/${tower.sprite}/${i}.png`);
tower.projectileTextures[i] = texture;
console.log(tower.projectileTextures);
}
}
towers.forEach(async (tower) => { towers.forEach(async (tower) => {
let index = this.TowerSprites.length - 1; let index = this.TowerSprites.length - 1;
if (index == -1) index = 0; if (index == -1) index = 0;

View File

@ -57,6 +57,8 @@ export type TowerDefinition = {
sprite: string; sprite: string;
description: string; description: string;
texture: PIXI.Texture; texture: PIXI.Texture;
projectileTextures: PIXI.Texture[];
projectileTexturesArrayLength: number;
stats: TowerStatsDefinition; stats: TowerStatsDefinition;
}; };
@ -90,6 +92,6 @@ export enum GemType {
} }
export enum TowerType { export enum TowerType {
Shooting = 0, Basic = 0,
Circle = 1, Circle = 1,
} }

View File

@ -21,6 +21,8 @@ export default class Creep extends GameObject {
private pathIndex: number = 0; private pathIndex: number = 0;
private speed: number; private speed: number;
private direction: number = 1; private direction: number = 1;
private healthBarGraphics: PIXI.Graphics = new PIXI.Graphics();
private healthBarWidth = 50;
public health: number; public health: number;
public maxHealth: number; public maxHealth: number;
public escaped: boolean = false; public escaped: boolean = false;
@ -37,6 +39,7 @@ export default class Creep extends GameObject {
// Initially flip sprite to the right, since the asset is facing left. // Initially flip sprite to the right, since the asset is facing left.
this.sprite.scale.x *= -1; this.sprite.scale.x *= -1;
this.sprite.anchor.set(0.5, 0.5); this.sprite.anchor.set(0.5, 0.5);
this.sprite.animationSpeed = 0.3;
this.sprite.play(); this.sprite.play();
this.id = id; this.id = id;
// Explanation: WaveManager spawns all creeps instantly, and since I don't want // Explanation: WaveManager spawns all creeps instantly, and since I don't want
@ -46,6 +49,7 @@ export default class Creep extends GameObject {
this.container.y = -50; this.container.y = -50;
this.sprite.width = Engine.GridCellSize; this.sprite.width = Engine.GridCellSize;
this.sprite.height = Engine.GridCellSize; this.sprite.height = Engine.GridCellSize;
this.bb.width = this.sprite.width;
this.speed = this.stats.speed / 60; this.speed = this.stats.speed / 60;
this.health = this.stats.health; this.health = this.stats.health;
this.maxHealth = this.stats.health; this.maxHealth = this.stats.health;
@ -56,9 +60,24 @@ export default class Creep extends GameObject {
Engine.GameScene.events.on(CreepEvents.TakenDamage, (creepID, damage) => { Engine.GameScene.events.on(CreepEvents.TakenDamage, (creepID, damage) => {
if (creepID != this.id) return; if (creepID != this.id) return;
this.health -= damage; this.health -= damage;
this.UpdateHealthbar();
}); });
Engine.Grid.container.addChild(this.container); Engine.Grid.container.addChild(this.container);
this.container.addChild(this.healthBarGraphics);
this.container.addChild(this.sprite); this.container.addChild(this.sprite);
this.UpdateHealthbar();
}
private UpdateHealthbar() {
this.healthBarGraphics.clear();
const hp = this.health;
const maxHp = this.maxHealth;
const percent = hp / maxHp;
const width = this.healthBarWidth * percent;
// ! TODO: MAKE THIS BETTER! It works like this now, but I don't like how its implemented.
this.healthBarGraphics.rect(-this.healthBarWidth / 2 + 5, -30, this.healthBarWidth, 10);
this.healthBarGraphics.fill({ color: 0x00ff00 });
this.healthBarGraphics.rect(-this.healthBarWidth / 2 + 5, -30, width, 10);
this.healthBarGraphics.fill({ color: 0xff0000 });
} }
public update(elapsedMS: number) { public update(elapsedMS: number) {
if (this.dead) return; if (this.dead) return;

View File

@ -37,20 +37,6 @@ export class Cell extends GameObject {
this.clickDetector.onpointerdown = (e) => { this.clickDetector.onpointerdown = (e) => {
Engine.Grid.onGridCellClicked(row, column); Engine.Grid.onGridCellClicked(row, column);
}; };
// 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.bb.width / 2;
// text.y = this.bb.height / 2;
// if (isPath) text.text += 'p';
} }
public gDraw() { public gDraw() {
this.g = new PIXI.Graphics({ this.g = new PIXI.Graphics({
@ -143,5 +129,6 @@ export class Grid extends GameObject {
public getCellByRowAndCol(row, column) { public getCellByRowAndCol(row, column) {
return this.cells.filter((item) => item.row == row && item.column == column)[0]; return this.cells.filter((item) => item.row == row && item.column == column)[0];
} }
// Not defined here, rather GameScene defines it. This is just for TS.
public onGridCellClicked(row, column) {} public onGridCellClicked(row, column) {}
} }

View File

@ -11,21 +11,21 @@ export function calculateAngleToPoint(x, y, targetX, targetY) {
export default class Projectile extends GameObject { export default class Projectile extends GameObject {
public deleteMe: boolean = false; public deleteMe: boolean = false;
public sprite: PIXI.Sprite; public sprite: PIXI.AnimatedSprite;
public x: number; public x: number;
public y: number; public y: number;
public angle: number; public angle: number;
public speed: number; public speed: number;
public damage: number; public damage: number;
public timeToLive: number = 1; public timeToLive: number = 1;
constructor(x, y, spriteTexture, angle, damage) { constructor(x, y, textures, angle, damage) {
super(); super();
this.x = x; this.x = x;
this.y = y; this.y = y;
this.damage = damage; this.damage = damage;
this.sprite = new PIXI.AnimatedSprite({ textures: textures, scale: 0.25, rotation: angle });
this.sprite = new PIXI.Sprite({ texture: spriteTexture, scale: 0.5, rotation: angle });
this.sprite.anchor.set(0.5); this.sprite.anchor.set(0.5);
this.sprite.play();
this.container.x = this.x; this.container.x = this.x;
this.container.y = this.y; this.container.y = this.y;
this.container.addChild(this.sprite); this.container.addChild(this.sprite);

View File

@ -57,17 +57,21 @@ export class Tower extends GameObject {
this.container.addChild(this.sprite); this.container.addChild(this.sprite);
parent.container.addChild(this.container); parent.container.addChild(this.container);
parent.clickDetector.onmouseenter = (e) => { parent.clickDetector.onmouseenter = (e) => {
this.showRangeDisplay();
};
parent.clickDetector.onmouseleave = (e) => {
this.graphics.clear();
};
Engine.GameMaster.currentScene.stage.addChild(this.graphics);
this.showRangeDisplay();
}
public showRangeDisplay() {
this.graphics.circle( this.graphics.circle(
this.column * Engine.GridCellSize + Engine.GridCellSize / 2, this.column * Engine.GridCellSize + Engine.GridCellSize / 2,
this.row * Engine.GridCellSize + Engine.GridCellSize / 2, this.row * Engine.GridCellSize + Engine.GridCellSize / 2,
this.definition.stats.range * Engine.GridCellSize this.definition.stats.range * Engine.GridCellSize
); );
this.graphics.fill({ color: 0xff0000, alpha: 0.5 }); this.graphics.fill({ color: 0xff0000, alpha: 0.5 });
};
parent.clickDetector.onmouseleave = (e) => {
this.graphics.clear();
};
Engine.GameMaster.currentScene.stage.addChild(this.graphics);
} }
public GetCreepsInRange() { public GetCreepsInRange() {
let creeps = Engine.Grid.creeps; let creeps = Engine.Grid.creeps;
@ -86,7 +90,7 @@ export class Tower extends GameObject {
let y = this.row * Engine.GridCellSize + Engine.GridCellSize / 2; let y = this.row * Engine.GridCellSize + Engine.GridCellSize / 2;
let angle = calculateAngleToPoint(x, y, creep.x, creep.y); let angle = calculateAngleToPoint(x, y, creep.x, creep.y);
this.projectiles.push( this.projectiles.push(
new Projectile(x, y, GameAssets.BasicProjectileTexture, angle, this.definition.stats.damage) new Projectile(x, y, this.definition.projectileTextures, angle, this.definition.stats.damage)
); );
} }
public update(elapsedMS: any): void { public update(elapsedMS: any): void {

View File

@ -2,8 +2,8 @@ import * as PIXI from 'pixi.js';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import { TerrainType, TowerDefinition } from '../Definitions'; import { TerrainType, TowerDefinition } from '../Definitions';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import GameObject from '../GameObject';
import { Tower, TowerEvents } from './Tower'; import { Tower, TowerEvents } from './Tower';
import { Cell } from './Grid';
export enum TowerBehaviours { export enum TowerBehaviours {
BasicTowerBehaviour = 'BasicTowerBehaviour', BasicTowerBehaviour = 'BasicTowerBehaviour',
@ -34,8 +34,13 @@ export default class TowerManager {
public PlayerClickOnGrid(row, column) { public PlayerClickOnGrid(row, column) {
if (!this.canPlaceTowers) return; if (!this.canPlaceTowers) return;
if (this.isPlacingTower) { if (this.isPlacingTower) {
if (!this.selectedTower) if (!this.selectedTower) {
Engine.NotificationManager.Notify(
'TowerManager.selectedTower is null when trying to place tower.',
'danger'
);
throw console.warn('TowerManager.selectedTower is null when trying to place tower.'); throw console.warn('TowerManager.selectedTower is null when trying to place tower.');
}
this.PlaceTower(this.selectedTower, row, column, this.selectedTower.behaviour); this.PlaceTower(this.selectedTower, row, column, this.selectedTower.behaviour);
} }
} }
@ -65,11 +70,13 @@ export default class TowerManager {
let tower = new Tower(row, column, sprite, definition, behaviour); let tower = new Tower(row, column, sprite, definition, behaviour);
this.towers.push(tower); this.towers.push(tower);
this.ToggleChoosingTowerLocation('RESET'); this.ToggleChoosingTowerLocation('RESET');
console.log('SHOULDVE PLACED TOWER');
console.log(this.selectedTower);
this.selectedTower = null; this.selectedTower = null;
Engine.GameScene.events.emit(TowerEvents.TowerPlacedEvent, definition.name); Engine.GameScene.events.emit(TowerEvents.TowerPlacedEvent, definition.name);
} else { } else {
Engine.NotificationManager.Notify(
'Can not place tower on path or other tower, choose another spot.',
'warn'
);
console.warn('Can not place tower on occupied spot or path. Try again.'); console.warn('Can not place tower on occupied spot or path. Try again.');
} }
} }

View File

@ -0,0 +1,32 @@
import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
import GameAssets from '../Assets';
import { Engine } from '../Bastion';
// ! TODO NEXT!
export default class SelectedTowerPanel extends GuiObject {
private bounds: PIXI.Rectangle;
private towerPanel: PIXI.NineSliceSprite;
constructor(bounds: PIXI.Rectangle) {
super(false);
this.bounds = bounds;
this.container.x = this.bounds.x;
this.container.y = this.bounds.y;
this.towerPanel = new PIXI.NineSliceSprite({
texture: GameAssets.FrameTowerTab,
leftWidth: 1000,
topHeight: 1000,
rightWidth: 1000,
bottomHeight: 1000,
});
this.towerPanel.x = -300;
this.towerPanel.y = -300;
this.towerPanel.width = this.bounds.width;
this.towerPanel.height = this.bounds.height;
this.container.addChild(this.towerPanel);
Engine.GameMaster.currentScene.stage.addChild(this.container);
}
}

View File

@ -0,0 +1,30 @@
import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
import GameAssets from '../Assets';
// ! TODO NEXT!
export default class Tooltip extends GuiObject {
private bounds: PIXI.Rectangle;
private tooltipSprite: PIXI.NineSliceSprite;
constructor(bounds: PIXI.Rectangle) {
super(false);
this.bounds = bounds;
this.container.x = this.bounds.x;
this.container.y = this.bounds.y;
this.tooltipSprite = new PIXI.NineSliceSprite({
texture: GameAssets.FrameTowerTab,
leftWidth: 1000,
topHeight: 1000,
rightWidth: 1000,
bottomHeight: 1000,
});
this.tooltipSprite.x = 0;
this.tooltipSprite.y = 0;
this.tooltipSprite.width = this.bounds.width;
this.tooltipSprite.height = this.bounds.height;
this.container.addChild(this.tooltipSprite);
}
}

View File

@ -1,3 +0,0 @@
export function log(msg: any) {
console.log(msg);
}