180 lines
4.6 KiB
TypeScript
180 lines
4.6 KiB
TypeScript
import type Phaser from 'phaser'
|
|
import {
|
|
MAP_COLS,
|
|
MAP_ROWS,
|
|
GAME_WIDTH,
|
|
HUD_HEIGHT,
|
|
PATH_WAYPOINTS,
|
|
COLOR_PATH,
|
|
COLOR_BUILDABLE,
|
|
COLOR_HOVER,
|
|
COLOR_BORDER,
|
|
MAP_LABELS,
|
|
INITIAL_KPI,
|
|
} from './constants'
|
|
|
|
/** 将折线关键坐标点展开为完整路径格子集合 */
|
|
export function buildPathTiles(
|
|
waypoints: readonly { x: number; y: number }[]
|
|
): Set<string> {
|
|
const tiles = new Set<string>()
|
|
for (let i = 0; i < waypoints.length - 1; i++) {
|
|
const from = waypoints[i]
|
|
const to = waypoints[i + 1]
|
|
if (from.x === to.x) {
|
|
const minY = Math.min(from.y, to.y)
|
|
const maxY = Math.max(from.y, to.y)
|
|
for (let y = minY; y <= maxY; y++) tiles.add(`${from.x},${y}`)
|
|
} else {
|
|
const minX = Math.min(from.x, to.x)
|
|
const maxX = Math.max(from.x, to.x)
|
|
for (let x = minX; x <= maxX; x++) tiles.add(`${x},${from.y}`)
|
|
}
|
|
}
|
|
return tiles
|
|
}
|
|
|
|
export const PATH_TILES = buildPathTiles(PATH_WAYPOINTS)
|
|
|
|
/** 计算格子尺寸 */
|
|
export function getCellSize() {
|
|
const cellW = Math.floor(GAME_WIDTH / MAP_COLS)
|
|
const cellH = Math.floor((720 - HUD_HEIGHT) / MAP_ROWS)
|
|
return { cellW, cellH }
|
|
}
|
|
|
|
/**
|
|
* 绘制所有地图格子
|
|
*/
|
|
export function drawAllTiles(
|
|
g: Phaser.GameObjects.Graphics,
|
|
hovered: { col: number; row: number } | null
|
|
): void {
|
|
const { cellW, cellH } = getCellSize()
|
|
g.clear()
|
|
|
|
for (let row = 0; row < MAP_ROWS; row++) {
|
|
for (let col = 0; col < MAP_COLS; col++) {
|
|
const isPath = PATH_TILES.has(`${col},${row}`)
|
|
const isHovered =
|
|
!isPath && hovered !== null &&
|
|
hovered.col === col && hovered.row === row
|
|
|
|
const fillColor = isPath
|
|
? COLOR_PATH
|
|
: isHovered ? COLOR_HOVER : COLOR_BUILDABLE
|
|
|
|
const x = col * cellW
|
|
const y = HUD_HEIGHT + row * cellH
|
|
|
|
g.fillStyle(fillColor, 1)
|
|
g.fillRect(x + 1, y + 1, cellW - 2, cellH - 2)
|
|
g.lineStyle(1, COLOR_BORDER, 0.6)
|
|
g.strokeRect(x, y, cellW, cellH)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 渲染地图装饰性标签
|
|
*/
|
|
export function renderMapLabels(scene: Phaser.Scene): void {
|
|
const { cellW, cellH } = getCellSize()
|
|
for (const label of MAP_LABELS) {
|
|
const x = label.col * cellW + cellW / 2
|
|
const y = HUD_HEIGHT + label.row * cellH + cellH / 2
|
|
scene.add
|
|
.text(x, y, label.text, {
|
|
fontFamily: 'VT323, monospace',
|
|
fontSize: '14px',
|
|
color: '#A78BFA',
|
|
})
|
|
.setOrigin(0.5, 0.5)
|
|
.setAlpha(0.5)
|
|
}
|
|
}
|
|
|
|
/** HUD 进度条参数 */
|
|
export const BAR_W = 300
|
|
export const BAR_H = 16
|
|
export const BAR_X = (GAME_WIDTH - BAR_W) / 2
|
|
export const BAR_Y = (HUD_HEIGHT - BAR_H) / 2
|
|
|
|
/**
|
|
* 渲染顶部 HUD 区域,返回可更新的 Graphics 和 Text 对象
|
|
*/
|
|
export function renderHUD(scene: Phaser.Scene): {
|
|
kpiBar: Phaser.GameObjects.Graphics
|
|
kpiText: Phaser.GameObjects.Text
|
|
hcText: Phaser.GameObjects.Text
|
|
} {
|
|
// HUD 背景
|
|
const hudBg = scene.add.graphics()
|
|
hudBg.fillStyle(0x0a1628, 0.92)
|
|
hudBg.fillRect(0, 0, GAME_WIDTH, HUD_HEIGHT)
|
|
hudBg.lineStyle(1, 0x1e3a5f, 1)
|
|
hudBg.strokeRect(0, 0, GAME_WIDTH, HUD_HEIGHT)
|
|
|
|
// 左侧标题
|
|
scene.add.text(16, HUD_HEIGHT / 2, '大厂保卫战', {
|
|
fontFamily: "'Press Start 2P', monospace",
|
|
fontSize: '10px',
|
|
color: '#A78BFA',
|
|
}).setOrigin(0, 0.5)
|
|
|
|
// KPI 标签
|
|
scene.add.text(BAR_X - 8, HUD_HEIGHT / 2, 'KPI', {
|
|
fontFamily: "'Press Start 2P', monospace",
|
|
fontSize: '8px',
|
|
color: '#E2E8F0',
|
|
}).setOrigin(1, 0.5)
|
|
|
|
// 进度条背景轨道
|
|
const trackBar = scene.add.graphics()
|
|
trackBar.fillStyle(0x1a1a2e, 1)
|
|
trackBar.fillRect(BAR_X, BAR_Y, BAR_W, BAR_H)
|
|
trackBar.lineStyle(1, 0x7c3aed, 0.6)
|
|
trackBar.strokeRect(BAR_X, BAR_Y, BAR_W, BAR_H)
|
|
|
|
// KPI 进度前景
|
|
const kpiBar = scene.add.graphics()
|
|
updateKPIBar(kpiBar, INITIAL_KPI)
|
|
|
|
// KPI 百分比文字
|
|
const kpiText = scene.add.text(
|
|
BAR_X + BAR_W / 2, HUD_HEIGHT / 2,
|
|
`${INITIAL_KPI}%`,
|
|
{
|
|
fontFamily: "'Press Start 2P', monospace",
|
|
fontSize: '8px',
|
|
color: '#E2E8F0',
|
|
}
|
|
).setOrigin(0.5, 0.5).setDepth(1)
|
|
|
|
// 右侧 HC 数值
|
|
const hcText = scene.add.text(
|
|
GAME_WIDTH - 16, HUD_HEIGHT / 2,
|
|
`HC: 200`,
|
|
{
|
|
fontFamily: "'Press Start 2P', monospace",
|
|
fontSize: '10px',
|
|
color: '#A78BFA',
|
|
}
|
|
).setOrigin(1, 0.5)
|
|
|
|
return { kpiBar, kpiText, hcText }
|
|
}
|
|
|
|
/**
|
|
* 更新 KPI 进度条(颜色随数值变化:绿→黄→红)
|
|
*/
|
|
export function updateKPIBar(
|
|
kpiBar: Phaser.GameObjects.Graphics,
|
|
kpi: number
|
|
): void {
|
|
kpiBar.clear()
|
|
const color = kpi > 60 ? 0x22c55e : kpi > 30 ? 0xf59e0b : 0xef4444
|
|
kpiBar.fillStyle(color, 1)
|
|
kpiBar.fillRect(BAR_X + 1, BAR_Y + 1, (BAR_W - 2) * (kpi / 100), BAR_H - 2)
|
|
}
|