import type Phaser from 'phaser' import { MAP_COLS, MAP_ROWS, GAME_WIDTH, GAME_HEIGHT, 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 { const tiles = new Set() 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 rawW = Math.floor(GAME_WIDTH / MAP_COLS) // 80 const rawH = Math.floor((GAME_HEIGHT - HUD_HEIGHT) / MAP_ROWS) // 55 const cell = Math.min(rawW, rawH) // 55 → 正方形 return { cellW: cell, cellH: cell } } /** * 绘制所有地图格子 */ 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) }