248 lines
8.2 KiB
TypeScript
248 lines
8.2 KiB
TypeScript
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<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
|
||
}
|
||
|
||
/**
|
||
* 向后兼容导出:用地图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<string>,
|
||
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<string>
|
||
): 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)
|
||
}
|