import { _decorator, Color, Component, Graphics, Layers, Node, UITransform, Vec3, view, } from 'cc'; import { CELL_PIXEL, CAMERA_ORTHO_HALF, DESIGN_HEIGHT } from '../core/GridConstants'; import { cellToWorldCenter, getHalfCellSize } from '../core/GridCoords'; import { EventManager, EventType } from '../core/EventManager'; import { GameManager } from '../manager/GameManager'; import { GridSnapHelper } from '../level/GridSnapHelper'; const { ccclass } = _decorator; const UI_LAYER = Layers.Enum.UI_2D; /** 对齐 Unity LineGridRenderer:导航按钮切换的满屏等距辅助网格 */ @ccclass('LineGridRenderer') export class LineGridRenderer extends Component { static instance: LineGridRenderer | null = null; /** 以中心向外的格子半径(菱形个数 ≈ (2r+1)²) */ radius = 24; cellSize = CELL_PIXEL; private graphics: Graphics | null = null; private visible = false; static ensure(parent: Node): LineGridRenderer { LineGridRenderer.purgeDuplicates(parent); let comp = LineGridRenderer.instance?.isValid ? LineGridRenderer.instance : null; if (!comp) { const found = LineGridRenderer.findUnder(parent); comp = found?.getComponent(LineGridRenderer) ?? null; } if (!comp) { const node = new Node('LineGrid'); node.parent = parent; comp = node.addComponent(LineGridRenderer); } else if (comp.node.parent !== parent) { comp.node.parent = parent; } comp.setupRuntime(); comp.sendToBack(); return comp; } /** 移除误挂在关卡内的重复 LineGrid(会导致叠线无法关闭) */ private static purgeDuplicates(entrance: Node) { const keep = LineGridRenderer.instance?.isValid ? LineGridRenderer.instance.node : LineGridRenderer.findUnder(entrance); const walk = (node: Node) => { for (let i = node.children.length - 1; i >= 0; i--) { const ch = node.children[i]; if (ch.name === 'LineGrid' && ch !== keep) { ch.destroy(); continue; } walk(ch); } }; walk(entrance); } private static findUnder(root: Node): Node | null { if (root.name === 'LineGrid') return root; for (const ch of root.children) { const hit = LineGridRenderer.findUnder(ch); if (hit) return hit; } return null; } onLoad() { if (LineGridRenderer.instance && LineGridRenderer.instance !== this) { this.node.destroy(); return; } LineGridRenderer.instance = this; this.setupRuntime(); EventManager.register(EventType.LevelInit, this.onLevelInit); } onDestroy() { EventManager.remove(EventType.LevelInit, this.onLevelInit); if (LineGridRenderer.instance === this) LineGridRenderer.instance = null; } isGridVisible(): boolean { return this.visible; } private setupRuntime() { this.node.layer = UI_LAYER; if (!this.node.getComponent(UITransform)) { this.node.addComponent(UITransform).setContentSize(1, 1); } const g = this.getComponent(Graphics) ?? this.addComponent(Graphics); this.graphics = g; g.lineWidth = 1.4; g.strokeColor = new Color(0, 0, 0, 255); if (!this.visible) { g.clear(); this.node.active = false; } } private onLevelInit = () => { this.purgeEditorGrid(); this.syncLevelOffset(); this.updateGridRadius(); if (this.visible) { this.rebuild(); this.node.active = true; this.sendToBack(); } else { this.graphics?.clear(); this.node.active = false; } }; toggleGridVisibility() { this.setupRuntime(); this.visible = !this.visible; if (this.visible) { this.purgeEditorGrid(); this.syncLevelOffset(); this.updateGridRadius(); this.rebuild(); this.node.active = true; this.sendToBack(); } else { this.graphics?.clear(); this.node.active = false; } } /** 移除编辑器 GridSnapHelper 遗留的蓝色/灰色参考格 */ private purgeEditorGrid() { const entrance = this.node.parent; if (entrance?.isValid) { GridSnapHelper.purgeRuntimeGrids(entrance); } const scene = this.node.scene; if (scene?.isValid) { GridSnapHelper.purgeRuntimeGrids(scene); } } /** 保持在 MainLevelEntrance 最底层,不遮挡关卡内砖块/角色 */ private sendToBack() { const parent = this.node.parent; if (parent?.isValid) { this.node.setSiblingIndex(0); } } /** 与关卡根节点对齐,网格覆盖当前可见砖块区域 */ private syncLevelOffset() { const parent = this.node.parent; if (!parent) return; const levelRoot = parent.children.find( (c) => c?.isValid && c !== this.node && (c.name ?? '').startsWith('Level'), ); if (levelRoot?.isValid) { const p = levelRoot.position; this.node.setPosition(p.x, p.y, 0); } else { this.node.setPosition(0, 0, 0); } } private updateGridRadius() { const b = GameManager.instance?.getCurLevel()?.boundary; const vs = view.getVisibleSize(); const halfH = CAMERA_ORTHO_HALF; const halfW = halfH * (vs.width / Math.max(vs.height, DESIGN_HEIGHT)); const cover = Math.ceil(Math.max(halfW, halfH) / (CELL_PIXEL * 0.35)) + 6; if (b) { this.radius = Math.max(cover, b.x + 8, b.y + 8); } else { this.radius = cover; } } private rebuild() { const g = this.graphics; if (!g) return; g.clear(); const { halfW, halfH } = getHalfCellSize(); const r = this.radius; for (let x = -r; x <= r; x++) { for (let y = -r; y <= r; y++) { const c = cellToWorldCenter(new Vec3(x, y, 0)); g.moveTo(c.x, c.y + halfH); g.lineTo(c.x + halfW, c.y); g.lineTo(c.x, c.y - halfH); g.lineTo(c.x - halfW, c.y); g.close(); } } g.stroke(); } }