import type Phaser from 'phaser' import { MAP_COLS, MAP_ROWS, GAME_WIDTH, GAME_HEIGHT, HUD_HEIGHT, INITIAL_KPI, } from './constants' import type { MapConfig } from './data/mapConfigs' import { ALL_MAPS } from './data/mapConfigs' // ─── PATH TILES ──────────────────────────────────────────────────────────────── /** 将折线关键坐标点展开为完整路径格子集合 */ 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 } /** * 向后兼容导出:用地图1配置初始化(GameScene 内部用局部变量覆盖) */ export const PATH_TILES = buildPathTiles(ALL_MAPS[0].waypoints) // ─── CELL SIZE ──────────────────────────────────────────────────────────────── /** 计算格子尺寸(正方形格子,取宽高中较小值保证正方形) */ export function getCellSize() { const rawW = Math.floor(GAME_WIDTH / MAP_COLS) const rawH = Math.floor((GAME_HEIGHT - HUD_HEIGHT) / MAP_ROWS) const cell = Math.min(rawW, rawH) return { cellW: cell, cellH: cell } } // ─── TILE RENDERING ─────────────────────────────────────────────────────────── /** * 绘制所有地图格子 * @param pathTiles 当前地图的路径格集合(若不传则用模块级默认值) * @param mapConfig 当前地图配置,用于读取颜色(可选,默认用地图1颜色) */ export function drawAllTiles( g: Phaser.GameObjects.Graphics, hovered: { col: number; row: number } | null, pathTiles?: Set, mapConfig?: MapConfig ): void { const { cellW, cellH } = getCellSize() const tiles = pathTiles ?? PATH_TILES const pathColor = mapConfig?.pathColor ?? 0x3d2b1f const hoverColor = 0x7c3aed g.clear() for (let row = 0; row < MAP_ROWS; row++) { for (let col = 0; col < MAP_COLS; col++) { const key = `${col},${row}` const isPath = tiles.has(key) const isHovered = !isPath && hovered?.col === col && hovered?.row === row const x = col * cellW const y = HUD_HEIGHT + row * cellH if (isPath) { // 路径格:半透明深色叠加,让背景隐约可见 g.fillStyle(pathColor, 0.78) g.fillRect(x, y, cellW, cellH) // 路径边框:稍亮一点 g.lineStyle(1, 0x6b4226, 0.9) g.strokeRect(x, y, cellW, cellH) } else if (isHovered) { // 悬停格:紫色高亮半透明 g.fillStyle(hoverColor, 0.35) g.fillRect(x, y, cellW, cellH) g.lineStyle(2, 0xa78bfa, 0.9) g.strokeRect(x, y, cellW, cellH) } else { // 可建格:只画细边框,背景图完全透出 g.lineStyle(1, 0x334155, 0.35) g.strokeRect(x, y, cellW, cellH) } } } } // ─── MAP LABELS ─────────────────────────────────────────────────────────────── /** * 渲染地图区域标签,返回创建的文字对象(便于切图时销毁) */ export function renderMapLabels( scene: Phaser.Scene, labels?: { col: number; row: number; text: string }[] ): Phaser.GameObjects.Text[] { const { cellW, cellH } = getCellSize() const list = labels ?? ALL_MAPS[0].labels return list.map(label => { const x = label.col * cellW + cellW / 2 const y = HUD_HEIGHT + label.row * cellH + cellH / 2 return scene.add .text(x, y, label.text, { fontFamily: 'VT323, monospace', fontSize: '13px', color: '#e2e8f0', backgroundColor: 'rgba(0,0,0,0.55)', padding: { x: 5, y: 2 }, }) .setOrigin(0.5, 0.5) .setDepth(2) .setAlpha(0.85) }) } // ─── BACKGROUND ─────────────────────────────────────────────────────────────── /** * 渲染地图背景图(铺满地图区域,HUD 下方) * 返回创建的 Image 对象,切图时需 destroy */ export function renderMapBackground( scene: Phaser.Scene, bgKey: string ): Phaser.GameObjects.Image | null { if (!scene.textures.exists(bgKey)) return null const { cellW, cellH } = getCellSize() const mapW = MAP_COLS * cellW const mapH = MAP_ROWS * cellH return scene.add .image(0, HUD_HEIGHT, bgKey) .setDisplaySize(mapW, mapH) .setDepth(-1) .setOrigin(0, 0) } // ─── DECORATIONS ───────────────────────────────────────────────────────────── /** * 渲染地图区域标签文字(替代图片装饰物,不破坏背景图视觉) * 返回对象数组(切图时销毁) */ export function renderDecorations( scene: Phaser.Scene, decorations: MapConfig['decorations'], pathTiles: Set ): Phaser.GameObjects.Image[] { // 装饰物改为纯文字标注,不再叠加图片,直接返回空数组 // 视觉信息已由地图标签(renderMapLabels)和背景图承载 void scene; void decorations; void pathTiles return [] } // ─── HUD ────────────────────────────────────────────────────────────────────── /** 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 } { 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) 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) const kpiBar = scene.add.graphics() updateKPIBar(kpiBar, INITIAL_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) 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) }