feat(towers): 实现防御塔基类与4种塔及塔管理器
This commit is contained in:
145
game/towers/TowerBase.ts
Normal file
145
game/towers/TowerBase.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.drawSprite()
|
||||
this.updateStaminaBar()
|
||||
}
|
||||
|
||||
update(delta: number, enemies: EnemyBase[]): void {
|
||||
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 }
|
||||
}
|
||||
|
||||
abstract attack(target: EnemyBase): void
|
||||
abstract drawSprite(): void
|
||||
|
||||
destroy(): void {
|
||||
this.sprite?.destroy()
|
||||
this.staminaBar?.destroy()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user