import { _decorator, CCString, Color, Component, Graphics, Label, Node, UITransform, v3 } from 'cc'; import { MapGrid } from './MapGrid'; const { ccclass, property, executeInEditMode } = _decorator; @ccclass('MapGridStatusDebug') @executeInEditMode export class MapGridStatusDebug extends Component { @property({ type: [Color], displayName: '状态颜色数组(按状态字段顺序)' }) colorArr: Color[] = [ new Color(0x20, 0xEC, 0x1B, 0xFF), // isCanWalk 通行 new Color(0xFF, 0xFF, 0xFF, 0xFF), // isCanPutText 放文字 new Color(0x9B, 0x59, 0xB6, 0xC8), // isCanPutGrid 摆格子 new Color(0x8B, 0x5A, 0x2B, 0xC8), // isCanBeDig 可开挖 new Color(0xF3, 0x41, 0x29, 0xC8), // isCanRemove 可铲除 new Color(0x2B, 0xC8, 0xFC, 0xC8), // isCanBeThunder 可雷击 new Color(0xFF, 0x99, 0x00, 0xC8), // isBestPlace 最佳点 new Color(0xF3, 0xD4, 0x58, 0xC8), // isFreePlace 免费推荐点 ]; /** 与 MapGrid 可配置布尔字段一一对应(不含 CampAreaID,阵营走铺色/数字) */ @property({ type: [CCString], displayName: '状态字段数组(可扩展)' }) statusKeys: string[] = [ 'isCanWalk', 'isCanPutText', 'isCanPutGrid', 'isCanBeDig', 'isCanRemove', 'isCanBeThunder', 'isBestPlace', 'isFreePlace', ]; @property({ displayName: '显示阵营区域' }) showCampArea = true; @property({ displayName: '显示阵营数字' }) showCampIdLabel = true; /** 下标 = CampAreaID;0 预留;1 己方蓝 / 2 敌方红,其余可扩展 */ @property({ type: [Color], displayName: '阵营颜色(下标=阵营ID)' }) campAreaColors: Color[] = [ new Color(0, 0, 0, 0), new Color(0x2B, 0x8C, 0xFF, 0x55), // 1 己方 new Color(0xFF, 0x4D, 0x4D, 0x55), // 2 敌方 new Color(0xA8, 0x55, 0xF7, 0x55), // 3 new Color(0x22, 0xC5, 0x5E, 0x55), // 4 new Color(0xFB, 0x92, 0x3C, 0x55), // 5 new Color(0xEC, 0x48, 0x99, 0x55), // 6 new Color(0x14, 0xB8, 0xA6, 0x55), // 7 new Color(0xEA, 0xB3, 0x08, 0x55), // 8 ]; @property({ displayName: '阵营描边宽度' }) campStrokeWidth = 2; @property({ displayName: '阵营数字字号' }) campIdFontSize = 22; @property({ displayName: '单个色块尺寸' }) blockSize: number = 8; @property({ displayName: '色块间距' }) blockGap: number = 1; @property({ displayName: '格子内边距X' }) insetX: number = 2; @property({ displayName: '格子内边距Y' }) insetY: number = 2; private _graphics: Graphics = null; private _selfTf: UITransform = null; private _gridRoot: Node = null; private _grids: MapGrid[] = []; private _gridTfs: UITransform[] = []; private _lastMasks: number[] = []; private _lastCampIds: number[] = []; private _lastWorldX: number[] = []; private _lastWorldY: number[] = []; private _lastGridW: number[] = []; private _lastGridH: number[] = []; private _lastConfigSignature = ''; private _tmpWorld = v3(); private _tmpLocal = v3(); private _campLabelRoot: Node = null; private _campLabels: Label[] = []; private _strokeColor = new Color(); onLoad(): void { this._graphics = this.node.getComponent(Graphics); this._selfTf = this.node.getComponent(UITransform); if (!this._graphics) { this._graphics = this.node.addComponent(Graphics); } if (!this._selfTf) { this._selfTf = this.node.addComponent(UITransform); } this.ensureCampLabelRoot(); this.rebuildGridCache(); this.updateStatus(true); } protected onDisable(): void { this.hideCampLabels(); if (this._graphics) { this._graphics.clear(); } } protected update(dt: number): void { this.updateStatus(); } private ensureCampLabelRoot() { this._campLabelRoot = this.node.getChildByName('CampIdLabels'); if (!this._campLabelRoot) { this._campLabelRoot = new Node('CampIdLabels'); this._campLabelRoot.parent = this.node; } } private rebuildGridCache() { this._gridRoot = this.node.parent?.getChildByName("格子") || null; this._grids = this._gridRoot ? this._gridRoot.getComponentsInChildren(MapGrid) : []; this._gridTfs = new Array(this._grids.length); this._lastMasks = new Array(this._grids.length); this._lastCampIds = new Array(this._grids.length); this._lastWorldX = new Array(this._grids.length); this._lastWorldY = new Array(this._grids.length); this._lastGridW = new Array(this._grids.length); this._lastGridH = new Array(this._grids.length); for (let i = 0; i < this._grids.length; i++) { this._gridTfs[i] = this._grids[i]?.node?.getComponent(UITransform) || null; this._lastMasks[i] = -1; this._lastCampIds[i] = Number.NaN; this._lastWorldX[i] = NaN; this._lastWorldY[i] = NaN; this._lastGridW[i] = NaN; this._lastGridH[i] = NaN; } this.rebuildCampLabels(); } private rebuildCampLabels() { this.ensureCampLabelRoot(); while (this._campLabels.length < this._grids.length) { const n = new Node(`CampId_${this._campLabels.length}`); n.parent = this._campLabelRoot; n.addComponent(UITransform); const label = n.addComponent(Label); label.overflow = Label.Overflow.NONE; label.horizontalAlign = Label.HorizontalAlign.CENTER; label.verticalAlign = Label.VerticalAlign.CENTER; label.isBold = true; label.enableOutline = true; label.outlineColor = Color.BLACK; label.outlineWidth = 2; label.color = Color.WHITE; this._campLabels.push(label); } for (let i = 0; i < this._campLabels.length; i++) { const label = this._campLabels[i]; if (!label?.node?.isValid) continue; label.node.active = i < this._grids.length && this.showCampArea && this.showCampIdLabel; } } private hideCampLabels() { for (let i = 0; i < this._campLabels.length; i++) { const label = this._campLabels[i]; if (label?.node?.isValid) { label.node.active = false; } } } private getConfigSignature() { return [ this.statusKeys.join('|'), this.blockSize, this.blockGap, this.insetX, this.insetY, this.colorArr.length, this.showCampArea ? 1 : 0, this.showCampIdLabel ? 1 : 0, this.campStrokeWidth, this.campIdFontSize, this.campAreaColors.length, ].join('|'); } private getGridMask(grid: MapGrid) { let mask = 0; const maxBits = Math.min(31, this.statusKeys.length); for (let i = 0; i < maxBits; i++) { if ((grid as any)[this.statusKeys[i]]) { mask |= (1 << i); } } return mask; } private getCampColor(campId: number): Color { const colors = this.campAreaColors; if (!colors || colors.length <= 0) { return Color.MAGENTA; } if (campId >= 0 && campId < colors.length && colors[campId]) { return colors[campId]; } // 超出配置时在 1..N-1 循环,避免落回透明的 0 号 const usable = Math.max(1, colors.length - 1); const idx = ((Math.abs(campId) - 1) % usable) + 1; return colors[idx] || Color.MAGENTA; } private ensureReady() { if (!this._graphics) { this._graphics = this.node.getComponent(Graphics) || this.node.addComponent(Graphics); } if (!this._selfTf) { this._selfTf = this.node.getComponent(UITransform) || this.node.addComponent(UITransform); } this.ensureCampLabelRoot(); } updateStatus(force = false) { this.ensureReady(); if (!this._graphics || !this._selfTf) return; if (!this._gridRoot || !this._gridRoot.isValid || this._grids.length <= 0) { this.rebuildGridCache(); } if (this._grids.length <= 0) { this._graphics.clear(); this.hideCampLabels(); return; } const configSignature = this.getConfigSignature(); let dirty = force || (configSignature !== this._lastConfigSignature); const count = this.statusKeys.length; const blockW = this.blockSize; const blockH = this.blockSize; const step = blockW + this.blockGap; for (let i = 0; i < this._grids.length; i++) { const grid = this._grids[i]; if (!grid || !grid.node?.isValid) continue; const gridTf = this._gridTfs[i]; if (!gridTf) continue; const world = grid.node.worldPosition; const mask = this.getGridMask(grid); const campId = grid.CampAreaID; const gridW = gridTf.width; const gridH = gridTf.height; if (mask !== this._lastMasks[i] || campId !== this._lastCampIds[i] || world.x !== this._lastWorldX[i] || world.y !== this._lastWorldY[i] || gridW !== this._lastGridW[i] || gridH !== this._lastGridH[i]) { dirty = true; } this._lastMasks[i] = mask; this._lastCampIds[i] = campId; this._lastWorldX[i] = world.x; this._lastWorldY[i] = world.y; this._lastGridW[i] = gridW; this._lastGridH[i] = gridH; } if (!dirty) return; this._graphics.clear(); this._lastConfigSignature = configSignature; if (this._campLabels.length < this._grids.length) { this.rebuildCampLabels(); } // 1) 阵营整格铺色 + 描边:连成区域,策划一眼能看出分界 if (this.showCampArea) { for (let i = 0; i < this._grids.length; i++) { const grid = this._grids[i]; if (!grid || !grid.node?.isValid) continue; const gridTf = this._gridTfs[i]; if (!gridTf) continue; const world = grid.node.worldPosition; this._tmpWorld.set(world.x, world.y, world.z); this._selfTf.convertToNodeSpaceAR(this._tmpWorld, this._tmpLocal); const gridW = gridTf.width; const gridH = gridTf.height; const x = this._tmpLocal.x - gridW / 2; const y = this._tmpLocal.y - gridH / 2; const campId = this._lastCampIds[i]; const fill = this.getCampColor(campId); this._graphics.fillColor = fill; this._graphics.rect(x, y, gridW, gridH); this._graphics.fill(); if (this.campStrokeWidth > 0) { this._strokeColor.set(fill.r, fill.g, fill.b, Math.min(255, fill.a + 100)); this._graphics.strokeColor = this._strokeColor; this._graphics.lineWidth = this.campStrokeWidth; this._graphics.rect(x, y, gridW, gridH); this._graphics.stroke(); } } } // 2) 状态小色块(叠在阵营铺色之上) if (count > 0) { for (let i = 0; i < this._grids.length; i++) { const grid = this._grids[i]; if (!grid || !grid.node?.isValid) continue; const gridTf = this._gridTfs[i]; if (!gridTf) continue; const world = grid.node.worldPosition; this._tmpWorld.set(world.x, world.y, world.z); this._selfTf.convertToNodeSpaceAR(this._tmpWorld, this._tmpLocal); const gridW = gridTf.width; const gridH = gridTf.height; const startX = this._tmpLocal.x - gridW / 2 + this.insetX; const startY = this._tmpLocal.y + gridH / 2 - this.insetY - blockH; const contentW = Math.max(1, gridW - this.insetX * 2); const maxCols = Math.max(1, Math.floor((contentW + this.blockGap) / step)); const mask = this._lastMasks[i]; for (let j = 0; j < count; j++) { const value = j < 31 ? ((mask & (1 << j)) !== 0) : !!(grid as any)[this.statusKeys[j]]; if (!value) continue; const color = this.colorArr[j] || this.colorArr[this.colorArr.length - 1] || Color.WHITE; const col = j % maxCols; const row = Math.floor(j / maxCols); const x = startX + col * step; const y = startY - row * step; this._graphics.fillColor = color; this._graphics.rect(x, y, blockW, blockH); this._graphics.fill(); } } } // 3) 中央阵营数字:同色区域里仍能精确读出 ID const showLabel = this.showCampArea && this.showCampIdLabel; for (let i = 0; i < this._campLabels.length; i++) { const label = this._campLabels[i]; if (!label?.node?.isValid) continue; if (!showLabel || i >= this._grids.length) { label.node.active = false; continue; } const grid = this._grids[i]; if (!grid || !grid.node?.isValid) { label.node.active = false; continue; } const world = grid.node.worldPosition; this._tmpWorld.set(world.x, world.y, world.z); this._selfTf.convertToNodeSpaceAR(this._tmpWorld, this._tmpLocal); label.node.active = true; label.node.setPosition(this._tmpLocal.x, this._tmpLocal.y, 0); label.fontSize = this.campIdFontSize; label.string = String(this._lastCampIds[i]); } } }