diff --git a/assets/towers.png b/assets/towers.png new file mode 100644 index 0000000..33bf99c Binary files /dev/null and b/assets/towers.png differ diff --git a/assets/towers.psd b/assets/towers.psd new file mode 100644 index 0000000..7bfbda0 Binary files /dev/null and b/assets/towers.psd differ 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,