Files
test1/game/enemies/EnemyBase.ts

254 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type Phaser from 'phaser'
import { GameManager } from '../GameManager'
import { HUD_HEIGHT } from '../constants'
import { getCellSize } from '../mapRenderer'
import { ALL_MAPS } from '../data/mapConfigs'
export interface PathPoint { x: number; y: number }
function gridToPixel(gx: number, gy: number, cellW: number, cellH: number): PathPoint {
return {
x: gx * cellW + cellW / 2,
y: gy * cellH + cellH / 2 + HUD_HEIGHT,
}
}
/**
* 将地图路径折线坐标转换为像素路径点序列
* @param waypoints 折线关键坐标点默认使用地图1
*/
export function buildFullPath(
waypoints?: readonly { x: number; y: number }[]
): PathPoint[] {
const { cellW, cellH } = getCellSize()
const pts = waypoints ?? ALL_MAPS[0].waypoints
const points: PathPoint[] = []
for (let i = 0; i < pts.length - 1; i++) {
const from = gridToPixel(pts[i].x, pts[i].y, cellW, cellH)
const to = gridToPixel(pts[i + 1].x, pts[i + 1].y, cellW, cellH)
points.push(from)
points.push(to)
}
return points.filter(
(p, i, arr) => i === 0 || p.x !== arr[i - 1].x || p.y !== arr[i - 1].y
)
}
export interface DotEffect { damage: number; duration: number; timer: number }
export abstract class EnemyBase {
protected scene: Phaser.Scene
protected imageSprite!: Phaser.GameObjects.Image
public x: number = 0
public y: number = 0
protected healthBar!: Phaser.GameObjects.Graphics
protected quoteText!: Phaser.GameObjects.Text
public maxHp: number
public hp: number
public speed: number
public readonly kpiDamage: number
public readonly hcReward: number
protected spriteKey: string
protected pathPoints: PathPoint[]
protected currentPathIndex: number = 0
public isDead: boolean = false
public isActive: boolean = true
get pathProgress(): number { return this.currentPathIndex }
public dotEffects: DotEffect[] = []
public slowEffect: number = 0
public slowTimer: number = 0
public shieldCount: number = 0
protected cellW: number
protected cellH: number
constructor(
scene: Phaser.Scene,
pathPoints: PathPoint[],
maxHp: number,
speed: number,
kpiDamage: number,
hcReward: number,
spriteKey: string,
speedMultiplier: number = 1.0,
hpMultiplier: number = 1.0
) {
this.scene = scene
this.pathPoints = pathPoints
this.maxHp = Math.ceil(maxHp * hpMultiplier)
this.hp = this.maxHp
this.speed = speed * speedMultiplier
this.kpiDamage = kpiDamage
this.hcReward = hcReward
this.spriteKey = spriteKey
const { cellW, cellH } = getCellSize()
this.cellW = cellW
this.cellH = cellH
if (pathPoints.length > 0) {
this.x = pathPoints[0].x
this.y = pathPoints[0].y
}
const enemySize = cellW * 0.75
this.imageSprite = scene.add.image(this.x, this.y, spriteKey)
this.imageSprite.setDisplaySize(enemySize, enemySize)
this.imageSprite.setDepth(10)
this.healthBar = scene.add.graphics()
this.quoteText = scene.add
.text(this.x, this.y - 30, this.getQuote(), {
fontFamily: 'VT323, monospace',
fontSize: '13px',
color: '#FFFFFF',
backgroundColor: 'rgba(0,0,0,0.7)',
padding: { x: 4, y: 2 },
})
.setOrigin(0.5, 1)
.setDepth(15)
.setAlpha(0)
this.drawHealthBar()
scene.time.delayedCall(500, () => { if (!this.isDead) this.showQuote() })
}
update(delta: number): void {
if (this.isDead || !this.isActive) return
this.processDOT(delta)
if (this.slowTimer > 0) {
this.slowTimer -= delta
if (this.slowTimer <= 0) this.slowEffect = 0
}
this.moveAlongPath(delta)
this.imageSprite.setPosition(this.x, this.y)
this.drawHealthBar()
if (this.quoteText?.alpha > 0) {
this.quoteText.setPosition(this.x, this.y - (this.cellH * 0.45))
}
}
protected processDOT(delta: number): void {
for (let i = this.dotEffects.length - 1; i >= 0; i--) {
const dot = this.dotEffects[i]
dot.timer -= delta
this.hp -= (dot.damage / 1000) * delta
if (this.hp <= 0) { this.die(); return }
if (dot.timer <= 0) this.dotEffects.splice(i, 1)
}
}
protected moveAlongPath(delta: number): void {
if (this.currentPathIndex >= this.pathPoints.length - 1) {
this.reachEnd(); return
}
const target = this.pathPoints[this.currentPathIndex + 1]
const currentSpeed = this.speed * (1 - this.slowEffect)
const distance = (currentSpeed * delta) / 1000
const dx = target.x - this.x
const dy = target.y - this.y
const dist = Math.sqrt(dx * dx + dy * dy)
if (dist <= distance) {
this.x = target.x; this.y = target.y
this.currentPathIndex++
} else {
this.x += (dx / dist) * distance
this.y += (dy / dist) * distance
}
if (this.slowEffect > 0) {
this.imageSprite.setTint(0x93c5fd)
} else {
this.imageSprite.clearTint()
}
}
protected drawHealthBar(): void {
this.healthBar.clear()
const bw = this.cellW * 0.5
const bh = 5
const bx = this.x - bw / 2
const by = this.y - this.cellH * 0.45
const ratio = Math.max(0, this.hp / this.maxHp)
const color = ratio > 0.5 ? 0x22c55e : ratio > 0.25 ? 0xf59e0b : 0xef4444
this.healthBar.fillStyle(0x1f2937, 1)
this.healthBar.fillRect(bx, by, bw, bh)
this.healthBar.fillStyle(color, 1)
this.healthBar.fillRect(bx, by, bw * ratio, bh)
this.healthBar.setDepth(14)
}
takeDamage(damage: number): void {
if (this.isDead) return
if (this.shieldCount > 0) {
this.shieldCount--
this.showShieldBlock()
return
}
this.hp -= damage
this.drawHealthBar()
if (this.hp <= 0) this.die()
}
private showShieldBlock(): void {
const txt = this.scene.add
.text(this.x, this.y - 35, '护盾!', {
fontFamily: 'VT323, monospace', fontSize: '14px', color: '#93C5FD',
}).setOrigin(0.5, 1).setDepth(20)
this.scene.tweens.add({
targets: txt, y: this.y - 55, alpha: 0, duration: 800,
onComplete: () => txt.destroy(),
})
}
addDOT(damage: number, duration: number): void {
this.dotEffects.push({ damage, duration, timer: duration })
}
addSlow(percent: number, duration: number): void {
this.slowEffect = Math.max(this.slowEffect, percent)
this.slowTimer = Math.max(this.slowTimer, duration)
}
protected die(): void {
if (this.isDead) return
this.isDead = true
GameManager.getInstance().addHC(this.hcReward)
this.onDeath()
this.destroy()
}
protected reachEnd(): void {
if (this.isDead) return
GameManager.getInstance().reduceKPI(this.kpiDamage)
this.isDead = true
this.destroy()
}
protected onDeath(): void {}
showQuote(): void {
if (this.isDead || !this.quoteText) return
this.quoteText.setText(this.getQuote())
this.quoteText.setPosition(this.x, this.y - this.cellH * 0.45)
this.quoteText.setAlpha(1)
this.scene.tweens.add({
targets: this.quoteText, alpha: 0,
duration: 800, delay: 2200, ease: 'Linear',
})
}
abstract getQuote(): string
destroy(): void {
this.imageSprite?.destroy()
this.healthBar?.destroy()
this.quoteText?.destroy()
}
}