diff --git a/docs/todos.md b/docs/todos.md index a824a87..5db8075 100644 --- a/docs/todos.md +++ b/docs/todos.md @@ -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 diff --git a/public/assets/creeps/quick/0.png b/public/assets/creeps/quick/0.png new file mode 100644 index 0000000..21bdcc7 Binary files /dev/null and b/public/assets/creeps/quick/0.png differ diff --git a/public/assets/creeps/quick/1.png b/public/assets/creeps/quick/1.png new file mode 100644 index 0000000..f507451 Binary files /dev/null and b/public/assets/creeps/quick/1.png differ diff --git a/public/assets/creeps/quick/10.png b/public/assets/creeps/quick/10.png new file mode 100644 index 0000000..9f0492c Binary files /dev/null and b/public/assets/creeps/quick/10.png differ diff --git a/public/assets/creeps/quick/11.png b/public/assets/creeps/quick/11.png new file mode 100644 index 0000000..f507451 Binary files /dev/null and b/public/assets/creeps/quick/11.png differ diff --git a/public/assets/creeps/quick/2.png b/public/assets/creeps/quick/2.png new file mode 100644 index 0000000..9f0492c Binary files /dev/null and b/public/assets/creeps/quick/2.png differ diff --git a/public/assets/creeps/quick/3.png b/public/assets/creeps/quick/3.png new file mode 100644 index 0000000..c81ad49 Binary files /dev/null and b/public/assets/creeps/quick/3.png differ diff --git a/public/assets/creeps/quick/4.png b/public/assets/creeps/quick/4.png new file mode 100644 index 0000000..21b252f Binary files /dev/null and b/public/assets/creeps/quick/4.png differ diff --git a/public/assets/creeps/quick/5.png b/public/assets/creeps/quick/5.png new file mode 100644 index 0000000..ac2b7a4 Binary files /dev/null and b/public/assets/creeps/quick/5.png differ diff --git a/public/assets/creeps/quick/6.png b/public/assets/creeps/quick/6.png new file mode 100644 index 0000000..6a9ea52 Binary files /dev/null and b/public/assets/creeps/quick/6.png differ diff --git a/public/assets/creeps/quick/7.png b/public/assets/creeps/quick/7.png new file mode 100644 index 0000000..ac2b7a4 Binary files /dev/null and b/public/assets/creeps/quick/7.png differ diff --git a/public/assets/creeps/quick/8.png b/public/assets/creeps/quick/8.png new file mode 100644 index 0000000..21b252f Binary files /dev/null and b/public/assets/creeps/quick/8.png differ diff --git a/public/assets/creeps/quick/9.png b/public/assets/creeps/quick/9.png new file mode 100644 index 0000000..c81ad49 Binary files /dev/null and b/public/assets/creeps/quick/9.png differ diff --git a/public/assets/creeps/tank/0.png b/public/assets/creeps/tank/0.png new file mode 100644 index 0000000..b72254f Binary files /dev/null and b/public/assets/creeps/tank/0.png differ diff --git a/public/assets/creeps/tank/1.png b/public/assets/creeps/tank/1.png new file mode 100644 index 0000000..c59c1c9 Binary files /dev/null and b/public/assets/creeps/tank/1.png differ diff --git a/public/assets/creeps/tank/10.png b/public/assets/creeps/tank/10.png new file mode 100644 index 0000000..6742bac Binary files /dev/null and b/public/assets/creeps/tank/10.png differ diff --git a/public/assets/creeps/tank/11.png b/public/assets/creeps/tank/11.png new file mode 100644 index 0000000..c59c1c9 Binary files /dev/null and b/public/assets/creeps/tank/11.png differ diff --git a/public/assets/creeps/tank/2.png b/public/assets/creeps/tank/2.png new file mode 100644 index 0000000..6742bac Binary files /dev/null and b/public/assets/creeps/tank/2.png differ diff --git a/public/assets/creeps/tank/3.png b/public/assets/creeps/tank/3.png new file mode 100644 index 0000000..cc73e6c Binary files /dev/null and b/public/assets/creeps/tank/3.png differ diff --git a/public/assets/creeps/tank/4.png b/public/assets/creeps/tank/4.png new file mode 100644 index 0000000..4a76473 Binary files /dev/null and b/public/assets/creeps/tank/4.png differ diff --git a/public/assets/creeps/tank/5.png b/public/assets/creeps/tank/5.png new file mode 100644 index 0000000..49b469a Binary files /dev/null and b/public/assets/creeps/tank/5.png differ diff --git a/public/assets/creeps/tank/6.png b/public/assets/creeps/tank/6.png new file mode 100644 index 0000000..aea5c89 Binary files /dev/null and b/public/assets/creeps/tank/6.png differ diff --git a/public/assets/creeps/tank/7.png b/public/assets/creeps/tank/7.png new file mode 100644 index 0000000..49b469a Binary files /dev/null and b/public/assets/creeps/tank/7.png differ diff --git a/public/assets/creeps/tank/8.png b/public/assets/creeps/tank/8.png new file mode 100644 index 0000000..4a76473 Binary files /dev/null and b/public/assets/creeps/tank/8.png differ diff --git a/public/assets/creeps/tank/9.png b/public/assets/creeps/tank/9.png new file mode 100644 index 0000000..cc73e6c Binary files /dev/null and b/public/assets/creeps/tank/9.png differ diff --git a/public/assets/gui/frame_c3_01.png b/public/assets/gui/frame_03.png similarity index 100% rename from public/assets/gui/frame_c3_01.png rename to public/assets/gui/frame_03.png diff --git a/public/assets/json/Creeps.json b/public/assets/json/Creeps.json index 2f2eaa8..2a8eff8 100644 --- a/public/assets/json/Creeps.json +++ b/public/assets/json/Creeps.json @@ -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, diff --git a/public/assets/json/Towers.json b/public/assets/json/Towers.json index eac4067..f60e715 100644 --- a/public/assets/json/Towers.json +++ b/public/assets/json/Towers.json @@ -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, diff --git a/public/assets/missions/mission_01.json b/public/assets/missions/mission_01.json index 9b95751..496c2b2 100644 --- a/public/assets/missions/mission_01.json +++ b/public/assets/missions/mission_01.json @@ -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] diff --git a/public/assets/projectiles/basic_tower.png b/public/assets/projectiles/basic_tower.png deleted file mode 100644 index 272ad51..0000000 Binary files a/public/assets/projectiles/basic_tower.png and /dev/null differ diff --git a/public/assets/projectiles/basic_tower/0.png b/public/assets/projectiles/basic_tower/0.png new file mode 100644 index 0000000..5bcdebd Binary files /dev/null and b/public/assets/projectiles/basic_tower/0.png differ diff --git a/public/assets/projectiles/basic_tower/1.png b/public/assets/projectiles/basic_tower/1.png new file mode 100644 index 0000000..40ff7ae Binary files /dev/null and b/public/assets/projectiles/basic_tower/1.png differ diff --git a/public/assets/projectiles/basic_tower/2.png b/public/assets/projectiles/basic_tower/2.png new file mode 100644 index 0000000..1fe70f0 Binary files /dev/null and b/public/assets/projectiles/basic_tower/2.png differ diff --git a/public/assets/projectiles/basic_tower/3.png b/public/assets/projectiles/basic_tower/3.png new file mode 100644 index 0000000..b5258c0 Binary files /dev/null and b/public/assets/projectiles/basic_tower/3.png differ diff --git a/public/assets/projectiles/basic_tower/4.png b/public/assets/projectiles/basic_tower/4.png new file mode 100644 index 0000000..5bcdebd Binary files /dev/null and b/public/assets/projectiles/basic_tower/4.png differ diff --git a/src/classes/Assets.ts b/src/classes/Assets.ts index 86a1189..a9a016a 100644 --- a/src/classes/Assets.ts +++ b/src/classes/Assets.ts @@ -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; diff --git a/src/classes/Definitions.ts b/src/classes/Definitions.ts index 5153276..da4701d 100644 --- a/src/classes/Definitions.ts +++ b/src/classes/Definitions.ts @@ -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, } diff --git a/src/classes/game/Creep.ts b/src/classes/game/Creep.ts index d317946..2b6e1cc 100644 --- a/src/classes/game/Creep.ts +++ b/src/classes/game/Creep.ts @@ -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; diff --git a/src/classes/game/Grid.ts b/src/classes/game/Grid.ts index 7959cf6..1209044 100644 --- a/src/classes/game/Grid.ts +++ b/src/classes/game/Grid.ts @@ -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) {} } diff --git a/src/classes/game/Projectile.ts b/src/classes/game/Projectile.ts index d36bb38..e4811ac 100644 --- a/src/classes/game/Projectile.ts +++ b/src/classes/game/Projectile.ts @@ -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); diff --git a/src/classes/game/Tower.ts b/src/classes/game/Tower.ts index 971d2a5..d2506d8 100644 --- a/src/classes/game/Tower.ts +++ b/src/classes/game/Tower.ts @@ -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 { diff --git a/src/classes/game/TowerManager.ts b/src/classes/game/TowerManager.ts index afc8d28..e4fcd73 100644 --- a/src/classes/game/TowerManager.ts +++ b/src/classes/game/TowerManager.ts @@ -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.'); } } diff --git a/src/classes/gui/SelectedTowerPanel.ts b/src/classes/gui/SelectedTowerPanel.ts new file mode 100644 index 0000000..322f407 --- /dev/null +++ b/src/classes/gui/SelectedTowerPanel.ts @@ -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); + } +} diff --git a/src/classes/gui/Tooltip.ts b/src/classes/gui/Tooltip.ts new file mode 100644 index 0000000..28cdb15 --- /dev/null +++ b/src/classes/gui/Tooltip.ts @@ -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); + } +} diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index c604173..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function log(msg: any) { - console.log(msg); -}