From 3fa2e3b596e89467201260be71dcb18e3a2a66b1 Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Sun, 31 Aug 2025 12:53:42 -0600 Subject: [PATCH] feat(towers): Implement tower system with targeting, firing animations, and health bars - Added tower spritesheet loading and tower creation functionality - Implemented tower targeting logic to follow enemies based on distance traveled - Added firing animations for towers with proper rotation towards targets - Enhanced enemy management with unique IDs, health bars, and death animations - Updated enemy configurations with reduced health values - Modified wave spawn rates to increase difficulty progression - Improved tower attack logic to handle multiple enemies in range properly This commit introduces the core tower defense mechanics including tower placement, targeting, firing, and enemy management systems. --- assets/towers.png | Bin 0 -> 14492 bytes assets/towers.psd | Bin 0 -> 104627 bytes src/levels/level1.js | 6 +- src/support/enemies.js | 51 +++++++----- src/support/enemiesConfig.js | 8 +- src/support/towerConfig.js | 1 + src/support/towerManager.js | 150 ++++++++++++++++++++++++----------- src/support/waveConfig.js | 2 +- 8 files changed, 142 insertions(+), 76 deletions(-) create mode 100644 assets/towers.png create mode 100644 assets/towers.psd diff --git a/assets/towers.png b/assets/towers.png new file mode 100644 index 0000000000000000000000000000000000000000..33bf99c32051d100119a00c147401952cac9cb5f GIT binary patch literal 14492 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

#$f0k({pQGmuCC5+r_>A%afw6;`;Q!X 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 h-r=n?HwIS1V7AxFO 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 literal 0 HcmV?d00001 diff --git a/src/levels/level1.js b/src/levels/level1.js index 07dced4..c3315c7 100644 --- a/src/levels/level1.js +++ b/src/levels/level1.js @@ -13,11 +13,14 @@ export class Level1 extends Phaser.Scene { preload() { this.load.tilemapTiledJSON('level1', 'assets/level1.json'); this.load.image('terrain', 'assets/terrain.png'); - this.load.image('josh', 'assets/josh-life.png'); this.load.spritesheet('basic-enemies', 'assets/basic-enemies.png', { frameWidth: 50, frameHeight: 50 }); + this.load.spritesheet('towers', 'assets/towers.png', { + frameHeight: 100, + frameWidth: 100 + }); } create() { @@ -33,6 +36,7 @@ export class Level1 extends Phaser.Scene { this.enemies = this.physics.add.group(); this.towers = this.physics.add.group(); + this.towerManager.createTower('gun', 2, 1); this.towerManager.createTower('gun', 4, 2); this.physics.add.collider(this.enemies, this.mainLayer); diff --git a/src/support/enemies.js b/src/support/enemies.js index d4fa255..2d13917 100644 --- a/src/support/enemies.js +++ b/src/support/enemies.js @@ -22,30 +22,37 @@ export class Enemies { const randSpeed = Phaser.Math.Between(this.speedLow, this.speedHigh); const spawnX = (this.x * 200) + 100 + randX; const spawnY = (this.y * 200) + 100 + randY; - - // Create enemy and store reference - const enemy = this.scene.add.sprite(spawnX, spawnY, ENEMIES_CONFIG[this.type].spriteSheet, ENEMIES_CONFIG[this.type].spriteStart); - - // Create Animations - this.createAnim('side', ENEMIES_CONFIG[this.type].spriteStart, ENEMIES_CONFIG[this.type].spriteStart+2); - this.createAnim('up', ENEMIES_CONFIG[this.type].spriteStart+6, ENEMIES_CONFIG[this.type].spriteStart+7); - this.createAnim('down', ENEMIES_CONFIG[this.type].spriteStart+3, ENEMIES_CONFIG[this.type].spriteStart+5); - this.createAnim('die', ENEMIES_CONFIG[this.type].spriteStart+8, ENEMIES_CONFIG[this.type].spriteStart+9, 0); - - enemy.props = { - 'offsetX': randX, - 'offsetY': randY, - 'path': this.path, - 'pathPhase': 0, - 'speed': randSpeed, - 'health': ENEMIES_CONFIG[this.type].health, - 'type': this.type, - 'distanceTraveled': 0 - }; - this.scene.enemies.add(enemy); + // Randomize Spawn Time a bit + this.scene.time.delayedCall(Phaser.Math.Between(0,2000), () => { + // Create enemy and store reference + const enemy = this.scene.add.sprite(spawnX, spawnY, ENEMIES_CONFIG[this.type].spriteSheet, ENEMIES_CONFIG[this.type].spriteStart); + + // Create Animations + this.createAnim('side', ENEMIES_CONFIG[this.type].spriteStart, ENEMIES_CONFIG[this.type].spriteStart+2); + this.createAnim('up', ENEMIES_CONFIG[this.type].spriteStart+6, ENEMIES_CONFIG[this.type].spriteStart+7); + this.createAnim('down', ENEMIES_CONFIG[this.type].spriteStart+3, ENEMIES_CONFIG[this.type].spriteStart+5); + this.createAnim('die', ENEMIES_CONFIG[this.type].spriteStart+8, ENEMIES_CONFIG[this.type].spriteStart+9, 0); + + // Generate unique ID for enemy + const uniqueId = Phaser.Math.Between(100000, 999999); - enemy.play(`${this.type}-side`); + enemy.props = { + 'offsetX': randX, + 'offsetY': randY, + 'path': this.path, + 'pathPhase': 0, + 'speed': randSpeed, + 'health': ENEMIES_CONFIG[this.type].health, + 'type': this.type, + 'distanceTraveled': 0, + 'id': uniqueId + }; + + this.scene.enemies.add(enemy); + + enemy.play(`${this.type}-side`); + }); } createAnim(type, start, end, repeat = -1) { diff --git a/src/support/enemiesConfig.js b/src/support/enemiesConfig.js index 05cbc57..a5675f2 100644 --- a/src/support/enemiesConfig.js +++ b/src/support/enemiesConfig.js @@ -1,8 +1,8 @@ export const ENEMIES_CONFIG = { 'basic1': { 'spread': 25, - 'health': 100, - 'fullHealth': 100, + 'health': 25, + 'fullHealth': 25, 'speedLow': 25, 'speedHigh': 35, 'spriteStart': 0, @@ -12,8 +12,8 @@ export const ENEMIES_CONFIG = { }, 'basic2': { 'spread': 0, - 'health': 300, - 'fullHealth': 100, + 'health': 50, + 'fullHealth': 50, 'speedLow': 45, 'speedHigh': 55, 'spriteStart': 0, diff --git a/src/support/towerConfig.js b/src/support/towerConfig.js index a6ce4c6..48e7ec0 100644 --- a/src/support/towerConfig.js +++ b/src/support/towerConfig.js @@ -1,5 +1,6 @@ export const TOWERS_CONFIG = { 'gun': { + 'spriteStart': 0, 'level1': { 'dmgLow': 10, 'dmgHigh': 30, diff --git a/src/support/towerManager.js b/src/support/towerManager.js index 26e04ae..d003e1e 100644 --- a/src/support/towerManager.js +++ b/src/support/towerManager.js @@ -1,3 +1,4 @@ +import { ENEMIES_CONFIG } from './enemiesConfig.js'; import { TOWERS_CONFIG } from './towerConfig.js'; export class TowerManager { @@ -5,27 +6,50 @@ export class TowerManager { constructor(scene) { this.scene = scene; this.lastFired = {}; // Track last fire time for each tower + this.following = {}; + + this.createAnims(); } createTower(type, x, y) { const posX = this.scene.gridToLocation(x); const posY = this.scene.gridToLocation(y); - const tower = this.scene.add.image(posX, posY, 'josh'); + const tower = this.scene.add.sprite(posX, posY, 'towers', TOWERS_CONFIG[type].spriteStart); tower.props = { 'type': type, 'level': 1 } + + // Generate unique ID for enemy + const uniqueId = Phaser.Math.Between(100000, 999999); + tower.id = uniqueId; + + tower.setScale(1.5); this.scene.towers.add(tower); // Draw range circle const config = TOWERS_CONFIG[type].level1; if (config) { const range = config.range; - const circle = this.scene.add.circle(posX, posY, range, 0x00ff00, 0.2); + tower.showRange = this.scene.add.circle(posX, posY, range, 0x00ff00, 0); } } + createAnims() { + this.scene.anims.create({ + key: 'gun-level1-fire', + frames: this.scene.anims.generateFrameNumbers('towers', { + start: 0, + end: 1, + }), + frameRate: 15, + duration: 500, + repeat: 1, + yoyo: true + }); + } + update(time, delta) { // Iterate through all towers this.scene.towers.children.iterate((tower) => { @@ -36,6 +60,16 @@ export class TowerManager { const type = tower.props.type; const level = 'level'+tower.props.level; const config = TOWERS_CONFIG[type][level]; + + if (this.following.hasOwnProperty(tower.id)) { + this.scene.enemies.children.iterate((enemy) => { + if (this.following[tower.id] === enemy.props.id) { + // Rotate tower to face the enemy + const angle = Phaser.Math.Angle.Between(towerX, towerY, enemy.x, enemy.y); + tower.rotation = angle; + } + }); + } if (!config) return; @@ -47,7 +81,7 @@ export class TowerManager { time - this.lastFired[tower.id] >= rate) { // Check for enemies in range - let inRange = false; + let enemiesInRange = []; this.scene.enemies.children.iterate((enemy) => { const distanceTraveled = enemy.props.distanceTraveled; const distance = Phaser.Math.Distance.Between( @@ -55,22 +89,32 @@ export class TowerManager { enemy.x, enemy.y ); - if (distance <= range) { - inRange = true; - this.attackTarget(tower, enemy); - return false; // Stop iterating once we find one enemy + if (distance <= range && enemy.props.health > 0) { + enemiesInRange.push(enemy); } }); // Fire if enemies are in range - if (inRange) { - console.log('fire'); + if (enemiesInRange.length > 0) { + + // Find enemy with greatest distance traveled + const furthestEnemy = enemiesInRange.reduce((max, current) => { + return current.props.distanceTraveled > max.props.distanceTraveled ? current : max; + }); + + this.followTarget(tower, furthestEnemy); + this.attackTarget(tower, furthestEnemy); + this.lastFired[tower.id] = time; } } }); } + followTarget(tower, enemy) { + this.following[tower.id] = enemy.props.id; + } + attackTarget(tower, enemy) { // Tower Properties const type = tower.props.type; @@ -82,27 +126,18 @@ export class TowerManager { const dmgLow = config.dmgLow; const dmgHigh = config.dmgHigh; - // Enemy Information - const fullHealth = enemy.props.fullHealth; - const currentHealth = enemy.props.health; + tower.play(`${type}-${level}-fire`, true); // Calculate damage (random between low and high) const damage = Phaser.Math.Between(dmgLow, dmgHigh); // Apply damage to enemy enemy.props.health -= damage; - - // Create or update health bar - if (!enemy.healthBar) { + + if (enemy.props.health > 0) { this.createHealthBar(enemy); - } - - // Update health bar display - this.updateHealthBar(enemy); - - // Check if enemy should be destroyed - if (enemy.props.health <= 0) { - this.destroyEnemy(enemy); + } else { + this.destroyEnemy(enemy, tower); } } @@ -111,9 +146,11 @@ export class TowerManager { const barHeight = 5; const barX = enemy.x - barWidth/2; const barY = enemy.y - enemy.displayHeight/2 - 10; + const health = Math.max(enemy.props.health, 0); + const fullHealth = ENEMIES_CONFIG[enemy.props.type].fullHealth; // Create health bar container - enemy.healthBar = this.scene.add.container(enemy.x, enemy.y - enemy.displayHeight/2 - 10); + const healthBar = this.scene.add.container(enemy.x, barY); // Background bar (gray) const background = this.scene.add.rectangle(0, 0, barWidth, barHeight, 0x808080); @@ -122,33 +159,50 @@ export class TowerManager { const healthFill = this.scene.add.rectangle(0, 0, barWidth, barHeight, 0x00ff00); // Position the fill relative to background - healthFill.x = -barWidth/2 + (barWidth * (enemy.props.health / enemy.props.fullHealth)) / 2; - - enemy.healthBar.add([background, healthFill]); - } - - updateHealthBar(enemy) { - if (!enemy.healthBar) return; - - const barWidth = 30; - const healthPercentage = enemy.props.health / enemy.props.fullHealth; - - // Update the fill width based on current health - enemy.healthBar.list[1].width = barWidth * healthPercentage; - - // Position the container correctly - enemy.healthBar.x = enemy.x; - enemy.healthBar.y = enemy.y - enemy.displayHeight/2 - 10; - } - - destroyEnemy(enemy) { - // Remove health bar if exists - if (enemy.healthBar) { - enemy.healthBar.destroy(); + if (health === 0) { + healthFill.width = 0; + } else { + healthFill.width = (health / fullHealth) * barWidth; } + + healthBar.add([background, healthFill]); + this.scene.physics.world.enable(healthBar); + healthBar.body.setVelocity(enemy.body.velocity.x, enemy.body.velocity.y); + + this.scene.tweens.add({ + targets: healthBar, + delay: 1000, + alpha: 0, + onComplete: () => { + healthBar.destroy(); + } + }); + } + + destroyEnemy(enemy, tower) { + const dieX = enemy.x; + const dieY = enemy.y; + const type = enemy.props.type; + const sprite = enemy.texture.key; + const firstFrame = enemy.texture.firstFrame; + const id = enemy.props.id; - // Destroy the enemy sprite enemy.destroy(); + + const deadEnemy = this.scene.add.sprite(dieX, dieY, sprite, firstFrame); + deadEnemy.play(`${type}-die`); + + this.scene.tweens.add({ + targets: deadEnemy, + delay: 5000, + duration: 2000, + alpha: 0, + onComplete: () => { + deadEnemy.destroy(); + } + }); + + this.following[tower.id] = null; } } \ No newline at end of file diff --git a/src/support/waveConfig.js b/src/support/waveConfig.js index 3c6a72d..3307d72 100644 --- a/src/support/waveConfig.js +++ b/src/support/waveConfig.js @@ -15,7 +15,7 @@ export const WAVE_CONFIG = { }, 2: { begin: 15, - basic1: 1 + basic1: 20 }, 3: { begin: 30,