Add Flak Cannon tower with AOE functionality, upgrade animations, and sound effects

This commit introduces a new Flak Cannon tower type with:
- Complete tower configuration including cost, damage stats, and range for all 3 levels
- Visual upgrades with rotating mid-sections for level 2/3
- Sound effects (flak.mp3) for firing and explosions
- AOE shot mechanics that fire multiple projectiles in a spread pattern
- Rotation handling to prevent conflicts with tower aiming logic

The flak cannon uses a unique firing pattern where it shoots multiple projectiles in different directions within its AOE radius, creating an explosion effect. The upgrade system adds visual elements like rotating mid-sections and changes sprite textures for each level.
This commit is contained in:
Brian Fertig 2025-09-07 10:51:39 -06:00
parent f843aee85f
commit 7976c35bfd
7 changed files with 115 additions and 12 deletions

BIN
assets/sounds/flak.mp3 Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

View File

@ -17,11 +17,11 @@ export class Level extends Phaser.Scene {
}
init(data) {
console.log('Selected level:', data.level);
console.log('Player name:', data.playerName);
if (data.level && data.playerName) {
this.level = data.level;
this.playerName = data.playerName;
}
}
preload() {
this.load.tilemapTiledJSON(`level${this.level}`, `assets/level${this.level}.json`);
@ -61,6 +61,7 @@ export class Level extends Phaser.Scene {
this.load.audio('portal', 'assets/sounds/portal.mp3');
this.load.audio('laser', 'assets/sounds/laser.mp3');
this.load.audio('upgrade', 'assets/sounds/upgrade.mp3');
this.load.audio('flak', 'assets/sounds/flak.mp3');
// Music
this.load.audio('level-bg1', 'assets/music/level-bg1.mp3');

View File

@ -254,8 +254,8 @@ export class InterfaceManager {
const tower = TOWERS_CONFIG[type];
this.gridAdd(x, y, tower.name, tower.cost, type);
x++;
if (x > 6) {
y = 2;
if (x > 4) {
y = 1;
x = 0;
}
}

View File

@ -175,5 +175,39 @@ export const TOWERS_CONFIG = {
'aoe': 225,
'anim': 'none'
}
},'flak': {
'name': 'Flak Cannon',
'cost': 700,
'spriteStart': 50,
'dmgType': 'aoe',
'level1': {
'dmgLow': 5,
'dmgHigh': 10,
'rate': 350,
'duration': 350,
'range': 300,
'aoe': 300,
'anim': 'none'
},
'level2': {
'cost': 1400,
'dmgLow': 6,
'dmgHigh': 11,
'rate': 300,
'duration': 300,
'range': 350,
'aoe': 350,
'anim': 'none'
},
'level3': {
'cost': 2800,
'dmgLow': 7,
'dmgHigh': 12,
'rate': 250,
'duration': 250,
'range': 375,
'aoe': 375,
'anim': 'none'
}
},
}

View File

@ -36,6 +36,15 @@ export class TowerManager {
}
}
if (type === 'flak') {
this.scene.tweens.add({
targets: tower,
angle: 360,
duration: 10000,
repeat: -1
});
}
tower.towerBase = towerBase;
// Generate unique ID for enemy
@ -189,6 +198,27 @@ export class TowerManager {
this.scene.sound.play('upgrade');
if (tower.props.type === 'icbm') {
tower.towerBase.setTexture('towers', 29+tower.props.level);
} else if (tower.props.type === 'flak') {
if (newLevel === 'level2') {
const towerMid = this.scene.add.sprite(tower.x, tower.y, 'towers', 51).setDepth(12);
this.scene.tweens.add({
targets: towerMid,
angle: -360,
duration: 12000,
repeat: -1
});
tower.towerMid = towerMid;
}
if (newLevel === 'level3') {
const towerMid = this.scene.add.sprite(tower.x, tower.y, 'towers', 52).setDepth(13);
this.scene.tweens.add({
targets: towerMid,
angle: 360,
duration: 6000,
repeat: -1
});
tower.towerMid = towerMid;
}
} else {
tower.setTexture('towers', TOWERS_CONFIG[tower.props.type][newLevel].sprite);
}
@ -209,7 +239,7 @@ export class TowerManager {
if (this.following.hasOwnProperty(tower.id)) {
this.scene.enemies.children.iterate((enemy) => {
if (this.following[tower.id] === enemy.props.id
&& type !== 'icbm')
&& type !== 'icbm' && type !=='flak')
{
// Rotate tower to face the enemy
const angle = Phaser.Math.Angle.Between(towerX, towerY, enemy.x, enemy.y);
@ -368,7 +398,9 @@ export class TowerManager {
const dmgHigh = config.dmgHigh;
const angle = Phaser.Math.Angle.Between(tower.x, tower.y, enemy.x, enemy.y);
if (type !== 'flak') {
tower.rotation = angle;
}
if (type === 'cannon') {
this.scene.sound.play('cannon');
@ -416,15 +448,51 @@ export class TowerManager {
}
if (type === 'flak') {
const shotAmt = Math.floor((400 - config.rate) / 50);
for (let i = 1; i <= shotAmt; i++) {
const shotAngle = Phaser.Math.Between(0, 360);
const shotDistance = Phaser.Math.Between(0, config.aoe);
const shotX = tower.x + Math.cos(shotAngle) * shotDistance;
const shotY = tower.y + Math.sin(shotAngle) * shotDistance;
const shotType = Phaser.Math.Between(53,55);
const shotDelay = i*100;
this.scene.time.delayedCall(shotDelay, () => {
const shot = this.scene.add.sprite(tower.x, tower.y, 'towers', shotType);
this.scene.tweens.add({
targets: shot,
x: shotX,
y: shotY,
angle: Phaser.Math.Between(0, 180),
duration: 2000,
onComplete: () => {
// Limit the times this sound plays to not overwhelm speakers
if (i === 1) {
this.scene.sound.play('flak');
}
shot.setTexture('towers', Phaser.Math.Between(56,57)).setScale(.4).setAlpha(.8);
this.scene.tweens.add({
targets: shot,
angle: Phaser.Math.Between(-90, 90),
alpha: .3,
scale: 1,
duration: 500,
onComplete: () => {
shot.destroy();
}
});
}
});
});
}
}
// Calculate damage (random between low and high)
const damage = Phaser.Math.Between(dmgLow, dmgHigh);
// Get AOE distance
const aoeDistance = config.aoe;
// Apply damage to all enemies in range
const enemiesInRange = this.scene.enemies.getChildren().filter(e => {
return Phaser.Math.Distance.Between(e.x, e.y, enemy.x, enemy.y) <= aoeDistance;
return Phaser.Math.Distance.Between(e.x, e.y, enemy.x, enemy.y) <= config.aoe;
});
enemiesInRange.forEach(targetEnemy => {