refactor(game/mapRenderer): 支持动态路径、地图背景图渲染和装饰物绘制

This commit is contained in:
Cloud Bot
2026-03-21 09:45:12 +00:00
parent 59df63504b
commit bcfd001c11

View File

@@ -5,14 +5,12 @@ import {
GAME_WIDTH, GAME_WIDTH,
GAME_HEIGHT, GAME_HEIGHT,
HUD_HEIGHT, HUD_HEIGHT,
PATH_WAYPOINTS,
COLOR_PATH,
COLOR_BUILDABLE,
COLOR_HOVER,
COLOR_BORDER,
MAP_LABELS,
INITIAL_KPI, INITIAL_KPI,
} from './constants' } from './constants'
import type { MapConfig } from './data/mapConfigs'
import { ALL_MAPS } from './data/mapConfigs'
// ─── PATH TILES ────────────────────────────────────────────────────────────────
/** 将折线关键坐标点展开为完整路径格子集合 */ /** 将折线关键坐标点展开为完整路径格子集合 */
export function buildPathTiles( export function buildPathTiles(
@@ -35,57 +33,78 @@ export function buildPathTiles(
return tiles return tiles
} }
export const PATH_TILES = buildPathTiles(PATH_WAYPOINTS) /**
* 向后兼容导出用地图1配置初始化GameScene 内部用局部变量覆盖)
*/
export const PATH_TILES = buildPathTiles(ALL_MAPS[0].waypoints)
// ─── CELL SIZE ────────────────────────────────────────────────────────────────
/** 计算格子尺寸(正方形格子,取宽高中较小值保证正方形) */ /** 计算格子尺寸(正方形格子,取宽高中较小值保证正方形) */
export function getCellSize() { export function getCellSize() {
const rawW = Math.floor(GAME_WIDTH / MAP_COLS) // 80 const rawW = Math.floor(GAME_WIDTH / MAP_COLS)
const rawH = Math.floor((GAME_HEIGHT - HUD_HEIGHT) / MAP_ROWS) // 55 const rawH = Math.floor((GAME_HEIGHT - HUD_HEIGHT) / MAP_ROWS)
const cell = Math.min(rawW, rawH) // 55 → 正方形 const cell = Math.min(rawW, rawH)
return { cellW: cell, cellH: cell } return { cellW: cell, cellH: cell }
} }
// ─── TILE RENDERING ───────────────────────────────────────────────────────────
/** /**
* 绘制所有地图格子 * 绘制所有地图格子
* @param pathTiles 当前地图的路径格集合(若不传则用模块级默认值)
* @param mapConfig 当前地图配置用于读取颜色可选默认用地图1颜色
*/ */
export function drawAllTiles( export function drawAllTiles(
g: Phaser.GameObjects.Graphics, g: Phaser.GameObjects.Graphics,
hovered: { col: number; row: number } | null hovered: { col: number; row: number } | null,
pathTiles?: Set<string>,
mapConfig?: MapConfig
): void { ): void {
const { cellW, cellH } = getCellSize() const { cellW, cellH } = getCellSize()
const tiles = pathTiles ?? PATH_TILES
const pathColor = mapConfig?.pathColor ?? 0x3d2b1f
const buildColor = mapConfig?.buildColor ?? 0x1e3a5f
const hoverColor = 0x2d5a8e
const borderColor = 0x0a1628
g.clear() g.clear()
for (let row = 0; row < MAP_ROWS; row++) { for (let row = 0; row < MAP_ROWS; row++) {
for (let col = 0; col < MAP_COLS; col++) { for (let col = 0; col < MAP_COLS; col++) {
const isPath = PATH_TILES.has(`${col},${row}`) const isPath = tiles.has(`${col},${row}`)
const isHovered = const isHovered =
!isPath && hovered !== null && !isPath && hovered !== null &&
hovered.col === col && hovered.row === row hovered.col === col && hovered.row === row
const fillColor = isPath const fillColor = isPath ? pathColor : isHovered ? hoverColor : buildColor
? COLOR_PATH
: isHovered ? COLOR_HOVER : COLOR_BUILDABLE
const x = col * cellW const x = col * cellW
const y = HUD_HEIGHT + row * cellH const y = HUD_HEIGHT + row * cellH
g.fillStyle(fillColor, 1) g.fillStyle(fillColor, 1)
g.fillRect(x + 1, y + 1, cellW - 2, cellH - 2) g.fillRect(x + 1, y + 1, cellW - 2, cellH - 2)
g.lineStyle(1, COLOR_BORDER, 0.6) g.lineStyle(1, borderColor, 0.6)
g.strokeRect(x, y, cellW, cellH) g.strokeRect(x, y, cellW, cellH)
} }
} }
} }
// ─── MAP LABELS ───────────────────────────────────────────────────────────────
/** /**
* 渲染地图装饰性标签 * 渲染地图装饰性标签,返回创建的文字对象(便于切图时销毁)
*/ */
export function renderMapLabels(scene: Phaser.Scene): void { export function renderMapLabels(
scene: Phaser.Scene,
labels?: { col: number; row: number; text: string }[]
): Phaser.GameObjects.Text[] {
const { cellW, cellH } = getCellSize() const { cellW, cellH } = getCellSize()
for (const label of MAP_LABELS) { const list = labels ?? ALL_MAPS[0].labels
return list.map(label => {
const x = label.col * cellW + cellW / 2 const x = label.col * cellW + cellW / 2
const y = HUD_HEIGHT + label.row * cellH + cellH / 2 const y = HUD_HEIGHT + label.row * cellH + cellH / 2
scene.add return scene.add
.text(x, y, label.text, { .text(x, y, label.text, {
fontFamily: 'VT323, monospace', fontFamily: 'VT323, monospace',
fontSize: '14px', fontSize: '14px',
@@ -93,9 +112,68 @@ export function renderMapLabels(scene: Phaser.Scene): void {
}) })
.setOrigin(0.5, 0.5) .setOrigin(0.5, 0.5)
.setAlpha(0.5) .setAlpha(0.5)
} })
} }
// ─── 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[] {
const { cellW, cellH } = getCellSize()
const result: Phaser.GameObjects.Image[] = []
for (const deco of decorations) {
const key = `${deco.col},${deco.row}`
// 跳过路径格
if (pathTiles.has(key)) continue
// 检查贴图是否已加载
if (!scene.textures.exists(deco.key)) continue
const x = deco.col * cellW + cellW / 2
const y = HUD_HEIGHT + deco.row * cellH + cellH / 2
const img = scene.add
.image(x, y, deco.key)
.setDisplaySize(cellW * 0.7, cellH * 0.7)
.setDepth(1)
.setAlpha(0.6)
result.push(img)
}
return result
}
// ─── HUD ──────────────────────────────────────────────────────────────────────
/** HUD 进度条参数 */ /** HUD 进度条参数 */
export const BAR_W = 300 export const BAR_W = 300
export const BAR_H = 16 export const BAR_H = 16
@@ -110,39 +188,33 @@ export function renderHUD(scene: Phaser.Scene): {
kpiText: Phaser.GameObjects.Text kpiText: Phaser.GameObjects.Text
hcText: Phaser.GameObjects.Text hcText: Phaser.GameObjects.Text
} { } {
// HUD 背景
const hudBg = scene.add.graphics() const hudBg = scene.add.graphics()
hudBg.fillStyle(0x0a1628, 0.92) hudBg.fillStyle(0x0a1628, 0.92)
hudBg.fillRect(0, 0, GAME_WIDTH, HUD_HEIGHT) hudBg.fillRect(0, 0, GAME_WIDTH, HUD_HEIGHT)
hudBg.lineStyle(1, 0x1e3a5f, 1) hudBg.lineStyle(1, 0x1e3a5f, 1)
hudBg.strokeRect(0, 0, GAME_WIDTH, HUD_HEIGHT) hudBg.strokeRect(0, 0, GAME_WIDTH, HUD_HEIGHT)
// 左侧标题
scene.add.text(16, HUD_HEIGHT / 2, '大厂保卫战', { scene.add.text(16, HUD_HEIGHT / 2, '大厂保卫战', {
fontFamily: "'Press Start 2P', monospace", fontFamily: "'Press Start 2P', monospace",
fontSize: '10px', fontSize: '10px',
color: '#A78BFA', color: '#A78BFA',
}).setOrigin(0, 0.5) }).setOrigin(0, 0.5)
// KPI 标签
scene.add.text(BAR_X - 8, HUD_HEIGHT / 2, 'KPI', { scene.add.text(BAR_X - 8, HUD_HEIGHT / 2, 'KPI', {
fontFamily: "'Press Start 2P', monospace", fontFamily: "'Press Start 2P', monospace",
fontSize: '8px', fontSize: '8px',
color: '#E2E8F0', color: '#E2E8F0',
}).setOrigin(1, 0.5) }).setOrigin(1, 0.5)
// 进度条背景轨道
const trackBar = scene.add.graphics() const trackBar = scene.add.graphics()
trackBar.fillStyle(0x1a1a2e, 1) trackBar.fillStyle(0x1a1a2e, 1)
trackBar.fillRect(BAR_X, BAR_Y, BAR_W, BAR_H) trackBar.fillRect(BAR_X, BAR_Y, BAR_W, BAR_H)
trackBar.lineStyle(1, 0x7c3aed, 0.6) trackBar.lineStyle(1, 0x7c3aed, 0.6)
trackBar.strokeRect(BAR_X, BAR_Y, BAR_W, BAR_H) trackBar.strokeRect(BAR_X, BAR_Y, BAR_W, BAR_H)
// KPI 进度前景
const kpiBar = scene.add.graphics() const kpiBar = scene.add.graphics()
updateKPIBar(kpiBar, INITIAL_KPI) updateKPIBar(kpiBar, INITIAL_KPI)
// KPI 百分比文字
const kpiText = scene.add.text( const kpiText = scene.add.text(
BAR_X + BAR_W / 2, HUD_HEIGHT / 2, BAR_X + BAR_W / 2, HUD_HEIGHT / 2,
`${INITIAL_KPI}%`, `${INITIAL_KPI}%`,
@@ -153,7 +225,6 @@ export function renderHUD(scene: Phaser.Scene): {
} }
).setOrigin(0.5, 0.5).setDepth(1) ).setOrigin(0.5, 0.5).setDepth(1)
// 右侧 HC 数值
const hcText = scene.add.text( const hcText = scene.add.text(
GAME_WIDTH - 16, HUD_HEIGHT / 2, GAME_WIDTH - 16, HUD_HEIGHT / 2,
`HC: 200`, `HC: 200`,