Files
test1/game/towers/TowerBase.ts

176 lines
4.6 KiB
TypeScript

import type Phaser from 'phaser'
import { GameManager } from '../GameManager'
import { TILE_SIZE, HUD_HEIGHT, COFFEE_COST, STAMINA_MAX, STAMINA_REGEN } from '../constants'
import type { EnemyBase } from '../enemies/EnemyBase'
export abstract class TowerBase {
protected scene: Phaser.Scene
public gridX: number
public gridY: number
protected sprite!: Phaser.GameObjects.Graphics
protected staminaBar!: Phaser.GameObjects.Graphics
private frozenOverlay!: Phaser.GameObjects.Graphics
public readonly cost: number
public readonly attackRange: number
public readonly attackDamage: number
public readonly attackSpeed: number
public readonly maxStamina: number = STAMINA_MAX
public stamina: number = STAMINA_MAX
public isFrozen: boolean = false
protected attackCooldown: number = 0
protected staminaRegen: number = STAMINA_REGEN
protected isActive: boolean = true
// Pixel center position
protected px: number
protected py: number
constructor(
scene: Phaser.Scene,
gridX: number,
gridY: number,
cost: number,
attackRange: number,
attackDamage: number,
attackSpeed: number
) {
this.scene = scene
this.gridX = gridX
this.gridY = gridY
this.cost = cost
this.attackRange = attackRange
this.attackDamage = attackDamage
this.attackSpeed = attackSpeed
this.px = gridX * TILE_SIZE + TILE_SIZE / 2
this.py = gridY * TILE_SIZE + TILE_SIZE / 2 + HUD_HEIGHT
this.sprite = scene.add.graphics()
this.staminaBar = scene.add.graphics()
this.frozenOverlay = scene.add.graphics()
this.drawSprite()
this.updateStaminaBar()
}
update(delta: number, enemies: EnemyBase[]): void {
// 禁锢状态:跳过攻击,显示灰色覆盖
if (this.isFrozen) {
this.drawFrozenOverlay()
return
}
// 解除禁锢时清除覆盖层
this.clearFrozenOverlay()
this.attackCooldown -= delta
if (this.stamina <= 0) {
this.isActive = false
}
if (!this.isActive) {
this.stamina = Math.min(
this.maxStamina,
this.stamina + (this.staminaRegen * delta) / 1000
)
if (this.stamina > 20) this.isActive = true
this.updateStaminaBar()
return
}
const target = this.findTarget(enemies)
if (target && this.attackCooldown <= 0) {
this.attack(target)
this.stamina -= 5
this.attackCooldown = 1000 / this.attackSpeed
this.updateStaminaBar()
} else if (!target) {
this.stamina = Math.min(
this.maxStamina,
this.stamina + (this.staminaRegen * delta) / 1000
)
this.updateStaminaBar()
}
}
protected findTarget(enemies: EnemyBase[]): EnemyBase | null {
const rangePx = this.attackRange * TILE_SIZE
let best: EnemyBase | null = null
let bestProgress = -1
for (const e of enemies) {
if (e.isDead) continue
const dx = e.sprite.x - this.px
const dy = e.sprite.y - this.py
const dist = Math.sqrt(dx * dx + dy * dy)
if (dist <= rangePx) {
// 优先选取路径进度最深的(模拟血量最多/威胁最大)
const progress = e.pathProgress
if (progress > bestProgress) {
bestProgress = progress
best = e
}
}
}
return best
}
protected updateStaminaBar(): void {
this.staminaBar.clear()
const bw = 40
const bh = 4
const bx = this.px - bw / 2
const by = this.py + TILE_SIZE / 2 - 8
this.staminaBar.fillStyle(0x374151, 1)
this.staminaBar.fillRect(bx, by, bw, bh)
this.staminaBar.fillStyle(0xf59e0b, 1)
this.staminaBar.fillRect(bx, by, bw * (this.stamina / this.maxStamina), bh)
this.staminaBar.setDepth(11)
}
buyCoffee(): boolean {
const manager = GameManager.getInstance()
if (manager.spendHC(COFFEE_COST)) {
this.stamina = this.maxStamina
this.isActive = true
this.updateStaminaBar()
return true
}
return false
}
getPixelCenter(): { x: number; y: number } {
return { x: this.px, y: this.py }
}
/** 绘制禁锢状态灰色覆盖层 */
protected drawFrozenOverlay(): void {
this.frozenOverlay.clear()
const half = TILE_SIZE / 2
this.frozenOverlay.fillStyle(0x6b7280, 0.6)
this.frozenOverlay.fillRect(
this.px - half,
this.py - half,
TILE_SIZE,
TILE_SIZE
)
this.frozenOverlay.setDepth(13)
}
/** 清除禁锢覆盖层 */
protected clearFrozenOverlay(): void {
this.frozenOverlay.clear()
}
abstract attack(target: EnemyBase): void
abstract drawSprite(): void
destroy(): void {
this.sprite?.destroy()
this.staminaBar?.destroy()
this.frozenOverlay?.destroy()
}
}