feat(towers): Implement interactive upgrade system with visual range indicators and cost validation

- Replace static camera bounds with dynamic level dimensions
- Add comprehensive tower upgrade menu with animated range circle
- Implement gold cost validation and visual feedback for upgrades
- Create interactive upgrade/cancel buttons with tween animations
- Add upgrade cost tracking and enable/disable functionality
- Update UI scene to include new upgrade icon asset
This commit is contained in:
Brian Fertig 2025-09-03 20:13:28 -06:00
parent 0dc8fba5db
commit 2c37e407cd
4 changed files with 101 additions and 32 deletions

BIN
assets/intUpgrade.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View File

@ -37,7 +37,7 @@ export class Level extends Phaser.Scene {
this.platformsLayer = this.levelMap.createLayer('platforms', terrainTiles); this.platformsLayer = this.levelMap.createLayer('platforms', terrainTiles);
// Add camera zoom functionality // Add camera zoom functionality
this.cameras.main.setBounds(0, 0, 2000, 2000); this.cameras.main.setBounds(0, 0, this.mainLayer.width, this.mainLayer.height);
this.addControls(); this.addControls();
this.waveManager = new WaveManager(this, 1, 1); this.waveManager = new WaveManager(this, 1, 1);

View File

@ -8,6 +8,7 @@ export class TowerManager {
this.lastFired = {}; // Track last fire time for each tower this.lastFired = {}; // Track last fire time for each tower
this.following = {}; this.following = {};
this.selectedTower = null; this.selectedTower = null;
this.upgradeCost = null;
this.createAnims(); this.createAnims();
@ -38,6 +39,7 @@ export class TowerManager {
tower.setInteractive(); tower.setInteractive();
tower.on('pointerdown', () => { tower.on('pointerdown', () => {
if (!this.selectedTower) { if (!this.selectedTower) {
console.log('CLICK');
this.upgradeMenu(tower); this.upgradeMenu(tower);
} }
}); });
@ -52,63 +54,125 @@ export class TowerManager {
upgradeMenu(tower) { upgradeMenu(tower) {
this.selectedTower = tower; this.selectedTower = tower;
const camera = this.scene.cameras.main; const currentLevel = `level${tower.props.level}`;
const towerLocX = tower.x - camera.scrollX; const nextLevel = currentLevel === 'level3' ? 'level3' : `level${tower.props.level + 1}`;
const towerLocY = tower.y - camera.scrollY;
this.rangeCircle = this.scene.add.circle(tower.x, tower.y, TOWERS_CONFIG[tower.props.type].level1.range, 0xc009900, 0.2) this.upgradeDetails = this.scene.add.container();
const rangeCircle = this.scene.add.circle(tower.x, tower.y, TOWERS_CONFIG[tower.props.type][currentLevel].range, 0xc009900, 0.2)
.setOrigin(0.5) .setOrigin(0.5)
.setDepth(5); .setDepth(5);
// Add a line from center to edge of range circle this.upgradeDetails.add(rangeCircle);
this.centerToEdgeLine = this.scene.add.line(tower.x, tower.y, 0, 0, TOWERS_CONFIG[tower.props.type].level1.range, 0, 0x00ff00)
const centerToEdgeLine = this.scene.add.line(tower.x, tower.y, 0, 0, TOWERS_CONFIG[tower.props.type][currentLevel].range, 0, 0x00ff00)
.setOrigin(0) .setOrigin(0)
.setDepth(6); .setDepth(6);
this.upgradeDetails.add(centerToEdgeLine);
this.scene.tweens.add({ this.scene.tweens.add({
targets: this.centerToEdgeLine, targets: centerToEdgeLine,
angle: 360, angle: 360,
duration: 8000, duration: 8000,
repeat: -1 repeat: -1
}); });
console.log('here');
// TODO: update this asap. const intUpgrade = this.scene.add.image(tower.x + 100, tower.y, 'intUpgrade')
const nextLevel = `level${tower.props.level+1}`; .setDepth(12)
if (this.scene.UIScene.interfaceManager.gold >= TOWERS_CONFIG[tower.props.type][nextLevel].cost){ .setTint(0xAAAAAA)
this.upgradeTower(tower) .setOrigin(0, 0.5)
.setScale(0)
.setAlpha(0);
this.upgradeDetails.add(intUpgrade);
this.upgradeCost = TOWERS_CONFIG[tower.props.type][nextLevel].cost;
this.upgradeText = this.scene.add.text(tower.x +175, tower.y - 50, `Upgrade: ${this.upgradeCost}`, {
fontFamily: 'neuropol, arial',
fontSize: '32px',
fill: '#ffd900ff',
stroke: '#c48f00ff',
strokeThickness: 2,
shadow: {
offsetX: 5,
offsetY: 5,
color: '#000000',
blur: 5,
stroke: false,
fill: true
} }
}).setDepth(13).setAlpha(0).setInteractive();
this.upgradeDetails.add(this.upgradeText);
if (currentLevel === 'level3') upgradeText.setText('Max Upgrade').disableInteractive();
const upgradeCancelText = this.scene.add.text(tower.x +175, tower.y + 25, `Cancel`, {
fontFamily: 'neuropol, arial',
fontSize: '32px',
fill: '#ff0000ff',
stroke: '#c40000ff',
strokeThickness: 2,
shadow: {
offsetX: 5,
offsetY: 5,
color: '#000000',
blur: 5,
stroke: false,
fill: true
}
}).setDepth(13).setAlpha(0).setInteractive();
this.upgradeDetails.add(upgradeCancelText);
upgradeCancelText.on('pointerdown', () => {
this.closeUpgradeMenu();
});
this.scene.tweens.add({
targets:[intUpgrade, this.upgradeText, upgradeCancelText],
scale: 1,
alpha: 1,
duration: 500
});
this.upgradeText.on('pointerdown', () => {
this.upgradeTower(tower);
});
if (this.scene.UIScene.interfaceManager.gold < TOWERS_CONFIG[tower.props.type][nextLevel].cost) {
this.disableUpgrade(this.upgradeText);
}
}
enableUpgrade(text) {
text.setInteractive().setTint(0xFFFFFF);
}
disableUpgrade(text) {
text.disableInteractive().setTint(0xAAAAAA);
} }
isPointerOverTower(pointer) { isPointerOverTower(pointer) {
// Check if pointer is over any tower
let isOverTower = false; let isOverTower = false;
this.scene.towers.children.iterate((tower) => { if (this.upgradeDetails.getBounds().contains(pointer.x, pointer.y)) {
if (tower.getBounds().contains(pointer.x, pointer.y)) {
isOverTower = true; isOverTower = true;
return false; // Break iteration
} }
});
return isOverTower; return isOverTower;
} }
closeUpgradeMenu() { closeUpgradeMenu() {
if (this.rangeCircle) { if (this.upgradeDetails) {
this.rangeCircle.destroy(); this.upgradeDetails.destroy();
this.rangeCircle = null; this.upgradeDetails = null;
} }
if (this.centerToEdgeLine) {
this.centerToEdgeLine.destroy();
this.centerToEdgeLine = null;
}
this.selectedTower = null; this.selectedTower = null;
} }
upgradeTower(tower) { upgradeTower(tower) {
this.closeUpgradeMenu();
if (tower.props.level === 3) return; if (tower.props.level === 3) return;
tower.props.level++; tower.props.level++;
const newLevel = `level${tower.props.level}` const newLevel = `level${tower.props.level}`
tower.setTexture('towers', TOWERS_CONFIG[tower.props.type][newLevel].sprite); tower.setTexture('towers', TOWERS_CONFIG[tower.props.type][newLevel].sprite);
this.scene.UIScene.removeGold(TOWERS_CONFIG[tower.props.type][newLevel].cost);
} }
update(time, delta) { update(time, delta) {
@ -175,6 +239,10 @@ export class TowerManager {
} }
} }
}); });
if (this.upgradeDetails && this.upgradeCost <= this.scene.UIScene.interfaceManager.gold) {
this.enableUpgrade(this.upgradeText);
}
} }
attackTarget(tower, enemy) { attackTarget(tower, enemy) {

View File

@ -16,6 +16,7 @@ export class UIScene extends Phaser.Scene {
this.load.image('redArrow', 'assets/redArrow.png'); this.load.image('redArrow', 'assets/redArrow.png');
this.load.image('intTop', 'assets/intTop.png'); this.load.image('intTop', 'assets/intTop.png');
this.load.image('gold', 'assets/gold.png'); this.load.image('gold', 'assets/gold.png');
this.load.image('intUpgrade', 'assets/intUpgrade.png');
this.load.spritesheet('nextWave', 'assets/nextWave.png', { this.load.spritesheet('nextWave', 'assets/nextWave.png', {
frameHeight: 150, frameHeight: 150,
frameWidth: 150 frameWidth: 150