Files
test1/game/mapRenderer.ts

253 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 buildColor = mapConfig?.buildColor ?? 0x1e3a5f
const hoverColor = 0x2d5a8e
const borderColor = 0x0a1628
g.clear()
for (let row = 0; row < MAP_ROWS; row++) {
for (let col = 0; col < MAP_COLS; col++) {
const isPath = tiles.has(`${col},${row}`)
const isHovered =
!isPath && hovered !== null &&
hovered.col === col && hovered.row === row
const fillColor = isPath ? pathColor : isHovered ? hoverColor : buildColor
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, borderColor, 0.6)
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: '14px',
color: '#A78BFA',
})
.setOrigin(0.5, 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 进度条参数 */
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)
}