import { Container, Graphics, Text, TextStyle } from 'pixi.js' import { GridMap, ISO_HALF_W, ISO_HALF_H, type TileType } from '@/game/map/GridMap' import { getLeyLineSegments } from '@/game/map/LeyLines' const TILE_TOP: Record = { buildable: 0x2d5a1e, path: 0x6b4226, blocked: 0x1a1a28, ley_line: 0x1a3a5c, nexus: 0x2a1060, spawn: 0x601010, } const TILE_SIDE_L: Record = { buildable: 0x1e3e14, path: 0x4a2e18, blocked: 0x111120, ley_line: 0x102840, nexus: 0x1a0840, spawn: 0x400808, } const TILE_SIDE_R: Record = { buildable: 0x163010, path: 0x3a2010, blocked: 0x0c0c18, ley_line: 0x0a1e30, nexus: 0x140630, spawn: 0x300606, } const TILE_EDGE: Record = { buildable: 0x3a7a28, path: 0x8a5a30, blocked: 0x2a2a38, ley_line: 0x2a5a8a, nexus: 0x3020a0, spawn: 0x801818, } const SIDE_DEPTH = 8 // px depth of 3D side faces export class MapRenderer { readonly container = new Container() private leyGfx = new Graphics() private debugContainer = new Container() private map: GridMap | null = null private _debugPath: { x: number; y: number }[] = [] constructor() { this.container.addChild(this.leyGfx) this.container.addChild(this.debugContainer) } load(map: GridMap): void { this.map = map this._drawTiles() this._drawLeyLines() } showPath(path: { x: number; y: number }[]): void { this._debugPath = path this._drawDebug() } private _drawTiles(): void { if (!this.map) return const hw = ISO_HALF_W, hh = ISO_HALF_H, sd = SIDE_DEPTH const sideLGfx = new Graphics() const sideRGfx = new Graphics() const topGfx = new Graphics() const decoGfx = new Graphics() // Draw in back-to-front order: sort tiles by (tx + ty) ascending const sorted = [...this.map.allTiles()].sort((a, b) => (a.x + a.y) - (b.x + b.y)) for (const tile of sorted) { const { x: cx, y: cy } = this.map.tileCenter(tile.x, tile.y) // Left side face (south-west) sideLGfx.poly([cx - hw, cy, cx, cy + hh, cx, cy + hh + sd, cx - hw, cy + sd]) sideLGfx.fill({ color: TILE_SIDE_L[tile.type] }) // Right side face (south-east) sideRGfx.poly([cx, cy + hh, cx + hw, cy, cx + hw, cy + sd, cx, cy + hh + sd]) sideRGfx.fill({ color: TILE_SIDE_R[tile.type] }) // Top diamond face topGfx.poly([cx, cy - hh, cx + hw, cy, cx, cy + hh, cx - hw, cy]) topGfx.fill({ color: TILE_TOP[tile.type] }) topGfx.poly([cx, cy - hh, cx + hw, cy, cx, cy + hh, cx - hw, cy]) topGfx.stroke({ color: TILE_EDGE[tile.type], width: 1, alpha: 0.5 }) // Decorations if (tile.type === 'spawn') { decoGfx.circle(cx, cy, 10) decoGfx.fill({ color: 0xff4444, alpha: 0.7 }) decoGfx.circle(cx, cy, 10) decoGfx.stroke({ color: 0xff8888, width: 1.5 }) } if (tile.type === 'nexus') { decoGfx.circle(cx, cy, 14) decoGfx.fill({ color: 0x8844ff, alpha: 0.8 }) decoGfx.circle(cx, cy, 14) decoGfx.stroke({ color: 0xbb88ff, width: 2 }) // Nexus gem decoGfx.poly([cx, cy - 10, cx + 8, cy, cx, cy + 10, cx - 8, cy]) decoGfx.fill({ color: 0xcc88ff, alpha: 0.9 }) } } this.container.addChildAt(sideLGfx, 0) this.container.addChildAt(sideRGfx, 1) this.container.addChildAt(topGfx, 2) this.container.addChildAt(decoGfx, 3) } private _drawLeyLines(): void { if (!this.map) return this.leyGfx.clear() const segments = getLeyLineSegments(this.map) for (const { x1, y1, x2, y2 } of segments) { const { x: sx1, y: sy1 } = this.map.tileCenter(x1, y1) const { x: sx2, y: sy2 } = this.map.tileCenter(x2, y2) this.leyGfx.moveTo(sx1, sy1) this.leyGfx.lineTo(sx2, sy2) this.leyGfx.stroke({ color: 0x6ecbd5, width: 8, alpha: 0.12 }) this.leyGfx.moveTo(sx1, sy1) this.leyGfx.lineTo(sx2, sy2) this.leyGfx.stroke({ color: 0x6ecbd5, width: 3, alpha: 0.6 }) } for (const tile of this.map.allTiles()) { if (tile.type !== 'ley_line') continue const { x: cx, y: cy } = this.map.tileCenter(tile.x, tile.y) this.leyGfx.circle(cx, cy, 5) this.leyGfx.fill({ color: 0x6ecbd5, alpha: 0.9 }) } } private _drawDebug(): void { this.debugContainer.removeChildren() if (this._debugPath.length === 0 || !this.map) return const gfx = new Graphics() const pts = this._debugPath for (let i = 1; i < pts.length; i++) { const { x: x1, y: y1 } = this.map.tileCenter(pts[i - 1].x, pts[i - 1].y) const { x: x2, y: y2 } = this.map.tileCenter(pts[i].x, pts[i].y) gfx.moveTo(x1, y1) gfx.lineTo(x2, y2) gfx.stroke({ color: 0xffdd44, width: 2, alpha: 0.7 }) } for (let i = 0; i < pts.length; i++) { const { x, y } = this.map.tileCenter(pts[i].x, pts[i].y) const isEnd = i === 0 || i === pts.length - 1 gfx.circle(x, y, isEnd ? 8 : 4) gfx.fill({ color: i === 0 ? 0xff4444 : i === pts.length - 1 ? 0x44ff88 : 0xffdd44, alpha: 0.9, }) } for (let i = 0; i < pts.length; i += 4) { const { x, y } = this.map.tileCenter(pts[i].x, pts[i].y) const label = new Text({ text: String(i), style: new TextStyle({ fill: 0xffffff, fontSize: 9, fontFamily: 'monospace' }), }) label.x = x + 6 label.y = y - 6 this.debugContainer.addChild(label) } this.debugContainer.addChild(gfx) } destroy(): void { this.container.destroy({ children: true }) } }