Massive progress, id say from 50% to 65% done!

this is great, only gems, their properties and tower interactions and game is effectively done! im very happy!
This commit is contained in:
Koneko 2025-01-17 00:32:22 +01:00 committed by GitHub
commit b372ab8233
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 174 additions and 53 deletions

View File

@ -6,9 +6,8 @@ List of things to implement following the "release" of the minimum viable produc
- [ ] Elemental resistances/attunement
- [x] Proper animation via PNG sequence
- [ ] More variety in Creeps
- [ ] Health bar + statistics on hover over
- [ ] +/- on x or y axis to give creeps more variance when walking along path (maybe)
- [x] More variety in Creeps
- [x] Health bar
## Towers
@ -16,8 +15,8 @@ List of things to implement following the "release" of the minimum viable produc
- [ ] Make tower react with slotted gems
- [ ] Alter damage based on attunement from slotted gems
- [ ] Tower info on click
- [ ] Animate projectiles
- [ ] Better mouseover tracking when placing tower and showing radius
- [x] Animate projectiles
- [x] Better mouseover tracking when placing tower and showing radius
## Gems

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -4,8 +4,42 @@
"textures": [],
"textureArrayLength": 12,
"stats": {
"health": 2,
"speed": 0.04,
"health": 5,
"speed": 2.4,
"special": null,
"resistance": {
"physical": 0,
"divine": 0,
"fire": 0,
"ice": 0,
"frostfire": 0
}
}
},
{
"name": "quick",
"textures": [],
"textureArrayLength": 12,
"stats": {
"health": 7,
"speed": 2.4,
"special": null,
"resistance": {
"physical": 0,
"divine": 0,
"fire": 0,
"ice": 0,
"frostfire": 0
}
}
},
{
"name": "tank",
"textures": [],
"textureArrayLength": 12,
"stats": {
"health": 7,
"speed": 2.4,
"special": null,
"resistance": {
"physical": 0,

View File

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

View File

@ -109,7 +109,7 @@
{
"firstCreepSpawnTick": 500,
"spawnIntervalTicks": 1000,
"creeps": [0]
"creeps": [1, 1, 1, 1, 1]
}
],
"offeredGems": [0, 1, 2, 3]
@ -119,7 +119,7 @@
{
"firstCreepSpawnTick": 500,
"spawnIntervalTicks": 1000,
"creeps": [0, 0]
"creeps": [2, 2, 2, 2, 2]
}
],
"offeredGems": [0, 1, 2, 3]

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 { CreepDefinition, CreepStatsDefinition, MissionDefinition, TowerDefinition } from './Definitions';
import { CreepDefinition, MissionDefinition, TowerDefinition } from './Definitions';
import { Engine } from './Bastion';
export default class GameAssets {
public static BasicTowerTexture: PIXI.Texture;
public static BasicProjectileTexture: PIXI.Texture;
public static Frame01Texture: PIXI.Texture;
public static Frame02Texture: PIXI.Texture;
public static Frame03Texture: PIXI.Texture;
public static FrameBackground: PIXI.Texture;
public static FrameTowerTab: PIXI.Texture;
public static VioletBackground: PIXI.Texture;
@ -29,6 +26,7 @@ export default class GameAssets {
private static text;
private static async Load(src) {
this.text.text = 'Loading asset: ' + src;
console.log('LOADING ' + src);
return await PIXI.Assets.load({
src: src,
});
@ -63,11 +61,12 @@ export default class GameAssets {
this.text.y = Engine.app.canvas.height / 2 + 50;
this.text.anchor.set(0.5, 0.5);
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.Button02Texture = await this.Load('/assets/gui/button_02.png');
this.Frame01Texture = await this.Load('/assets/gui/frame_01.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.FrameTowerTab = await this.Load('/assets/gui/background_02.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.GoldTexture = await this.Load('/assets/gui/money.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.LoadTowers();
await this.LoadCreeps();
@ -108,7 +105,17 @@ export default class GameAssets {
private static async LoadTowers() {
const res = await fetch('/assets/json/Towers.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) => {
let index = this.TowerSprites.length - 1;
if (index == -1) index = 0;

View File

@ -57,6 +57,8 @@ export type TowerDefinition = {
sprite: string;
description: string;
texture: PIXI.Texture;
projectileTextures: PIXI.Texture[];
projectileTexturesArrayLength: number;
stats: TowerStatsDefinition;
};
@ -72,7 +74,8 @@ export type PathDefinition = [[row: number, column: number]];
export enum CreepType {
Basic = 0,
Fast = 1,
Quick = 1,
Tank = 2,
}
export enum TerrainType {
@ -89,6 +92,6 @@ export enum GemType {
}
export enum TowerType {
Shooting = 0,
Basic = 0,
Circle = 1,
}

View File

@ -21,6 +21,8 @@ export default class Creep extends GameObject {
private pathIndex: number = 0;
private speed: number;
private direction: number = 1;
private healthBarGraphics: PIXI.Graphics = new PIXI.Graphics();
private healthBarWidth = 50;
public health: number;
public maxHealth: number;
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.
this.sprite.scale.x *= -1;
this.sprite.anchor.set(0.5, 0.5);
this.sprite.animationSpeed = 0.3;
this.sprite.play();
this.id = id;
// Explanation: WaveManager spawns all creeps instantly, and since I don't want
@ -46,7 +49,8 @@ export default class Creep extends GameObject {
this.container.y = -50;
this.sprite.width = Engine.GridCellSize;
this.sprite.height = Engine.GridCellSize;
this.speed = this.stats.speed;
this.bb.width = this.sprite.width;
this.speed = this.stats.speed / 60;
this.health = this.stats.health;
this.maxHealth = this.stats.health;
this.path = path;
@ -56,9 +60,24 @@ export default class Creep extends GameObject {
Engine.GameScene.events.on(CreepEvents.TakenDamage, (creepID, damage) => {
if (creepID != this.id) return;
this.health -= damage;
this.UpdateHealthbar();
});
Engine.Grid.container.addChild(this.container);
this.container.addChild(this.healthBarGraphics);
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) {
if (this.dead) return;

View File

@ -37,20 +37,6 @@ export class Cell extends GameObject {
this.clickDetector.onpointerdown = (e) => {
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() {
this.g = new PIXI.Graphics({
@ -143,5 +129,6 @@ export class Grid extends GameObject {
public getCellByRowAndCol(row, column) {
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) {}
}

View File

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

View File

@ -57,17 +57,21 @@ export class Tower extends GameObject {
this.container.addChild(this.sprite);
parent.container.addChild(this.container);
parent.clickDetector.onmouseenter = (e) => {
this.graphics.circle(
this.column * Engine.GridCellSize + Engine.GridCellSize / 2,
this.row * Engine.GridCellSize + Engine.GridCellSize / 2,
this.definition.stats.range * Engine.GridCellSize
);
this.graphics.fill({ color: 0xff0000, alpha: 0.5 });
this.showRangeDisplay();
};
parent.clickDetector.onmouseleave = (e) => {
this.graphics.clear();
};
Engine.GameMaster.currentScene.stage.addChild(this.graphics);
this.showRangeDisplay();
}
public showRangeDisplay() {
this.graphics.circle(
this.column * Engine.GridCellSize + Engine.GridCellSize / 2,
this.row * Engine.GridCellSize + Engine.GridCellSize / 2,
this.definition.stats.range * Engine.GridCellSize
);
this.graphics.fill({ color: 0xff0000, alpha: 0.5 });
}
public GetCreepsInRange() {
let creeps = Engine.Grid.creeps;
@ -86,7 +90,7 @@ export class Tower extends GameObject {
let y = this.row * Engine.GridCellSize + Engine.GridCellSize / 2;
let angle = calculateAngleToPoint(x, y, creep.x, creep.y);
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 {

View File

@ -2,8 +2,8 @@ import * as PIXI from 'pixi.js';
import { Engine } from '../Bastion';
import { TerrainType, TowerDefinition } from '../Definitions';
import GameAssets from '../Assets';
import GameObject from '../GameObject';
import { Tower, TowerEvents } from './Tower';
import { Cell } from './Grid';
export enum TowerBehaviours {
BasicTowerBehaviour = 'BasicTowerBehaviour',
@ -34,8 +34,13 @@ export default class TowerManager {
public PlayerClickOnGrid(row, column) {
if (!this.canPlaceTowers) return;
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.');
}
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);
this.towers.push(tower);
this.ToggleChoosingTowerLocation('RESET');
console.log('SHOULDVE PLACED TOWER');
console.log(this.selectedTower);
this.selectedTower = null;
Engine.GameScene.events.emit(TowerEvents.TowerPlacedEvent, definition.name);
} 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.');
}
}

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);
}