import { Node, UIOpacity } from 'cc'; import { GEvent } from 'db://assets/mx/module/event/GEvent'; import type { CocosBattleMonsterPresenter } from './CocosBattleMonsterPresenter'; import { BATTLE_MONSTER_CCCLASS } from './IBattleMonsterView'; /** 拖动期间怪物目标不透明度(须能看清路线) */ export const BAG_DRAG_MONSTER_OPACITY = 160; /** 拖动期间战斗特效目标不透明度(接近不可见) */ export const BAG_DRAG_FX_OPACITY = 28; /** 怪物渐显/落地收尾时:若处于背包拖动则使用拖动透明度 */ export function resolveBagDragMonsterOpacity(fullOpacity = 255): number { const battle = gg.game.CurentBattle; if (!battle?.isBagDragSlowActive) return fullOpacity; return Math.round(fullOpacity * BAG_DRAG_MONSTER_OPACITY / 255); } /** * 背包/地图拖动期间的战斗画面弱化:特效几乎隐藏,怪物半透明但仍可辨认。 * 与 BattleRuntime.beginBagDragSlowMo / forceEndBagDragSlowMo 成对。 */ export class BagDragVisualPresenter { private _flyLayer: Node | null = null; private _scoreLayer: Node | null = null; private _monsterPresenter: CocosBattleMonsterPresenter | null = null; private _active = false; private readonly _savedMonsterOpacity = new Map(); private readonly _savedFxNodeOpacity = new Map(); private _flyLayerOpacity: UIOpacity | null = null; private _savedFlyOpacity = 255; private _scoreLayerOpacity: UIOpacity | null = null; private _savedScoreOpacity = 255; private _refreshToken = 0; bind(opts: { flyLayer: Node | null; scoreLayer: Node | null; monsterPresenter: CocosBattleMonsterPresenter | null; }): void { this.unbind(); this._flyLayer = opts.flyLayer?.isValid ? opts.flyLayer : null; this._scoreLayer = opts.scoreLayer?.isValid ? opts.scoreLayer : null; this._monsterPresenter = opts.monsterPresenter; GEvent.Ins.on(GEvent.BagDragVisualChanged, this._onBagDragVisualChanged, this); } unbind(): void { GEvent.Ins.off(GEvent.BagDragVisualChanged, this._onBagDragVisualChanged, this); this._stopRefresh(); this._restoreAll(); this._active = false; this._flyLayer = null; this._scoreLayer = null; this._monsterPresenter = null; } private _onBagDragVisualChanged = (active: boolean): void => { if (active) this._begin(); else this._end(); }; private _begin(): void { if (this._active) return; this._active = true; this._applyAll(); this._startRefresh(); } private _end(): void { if (!this._active) return; this._active = false; this._stopRefresh(); this._restoreAll(); } private _applyAll(): void { this._applyMonsterNodes(); this._applyMechanismFxNodes(); this._applyFlyLayer(); this._applyScoreLayer(); } private _applyMonsterNodes(): void { const map = this._monsterPresenter?.getEntityNodeMap(); if (!map) return; for (const node of map.values()) { this._dimMonsterNode(node); } } private _applyMechanismFxNodes(): void { const root = this._monsterPresenter?.monsterRoot; if (!root?.isValid) return; for (const child of root.children) { if (!child?.isValid) continue; if (child.getComponent(BATTLE_MONSTER_CCCLASS)) continue; this._dimFxNode(child); } } private _dimMonsterNode(node: Node | null | undefined): void { if (!node?.isValid) return; if (node.opacity <= 0) return; if (!this._savedMonsterOpacity.has(node)) { this._savedMonsterOpacity.set(node, 255); } node.opacity = BAG_DRAG_MONSTER_OPACITY; } private _dimFxNode(node: Node | null | undefined): void { if (!node?.isValid) return; if (!this._savedFxNodeOpacity.has(node)) { this._savedFxNodeOpacity.set(node, node.opacity); } node.opacity = BAG_DRAG_FX_OPACITY; } private _applyFlyLayer(): void { const layer = this._flyLayer; if (!layer?.isValid) return; // 禁止给 ui_fly 整层 UIOpacity:背包拉线 ghost 也挂在此层,会被压成几乎看不见。 // 只弱化非 ghost 宿主的直接子节点。 const layerUo = layer.getComponent(UIOpacity); if (layerUo?.isValid && layerUo.opacity < 255) { layerUo.opacity = 255; } this._flyLayerOpacity = null; this._savedFlyOpacity = 255; for (let i = 0; i < layer.children.length; i++) { const child = layer.children[i]; if (!child?.isValid) continue; if (child.name === '__bagDragGhostHost') continue; this._dimFxNode(child); } } private _applyScoreLayer(): void { const layer = this._scoreLayer; if (!layer?.isValid) return; const uo = layer.getComponent(UIOpacity) ?? layer.addComponent(UIOpacity); if (!this._scoreLayerOpacity) { this._savedScoreOpacity = uo.opacity; } this._scoreLayerOpacity = uo; uo.opacity = BAG_DRAG_FX_OPACITY; } private _restoreAll(): void { this._savedMonsterOpacity.clear(); const map = this._monsterPresenter?.getEntityNodeMap(); if (map) { map.forEach((node, entityId) => { this._monsterPresenter?.ensureMarchVisible(entityId, node); }); } for (const [node, opacity] of this._savedFxNodeOpacity) { if (node?.isValid) node.opacity = opacity; } this._savedFxNodeOpacity.clear(); if (this._flyLayerOpacity?.isValid) { this._flyLayerOpacity.opacity = this._savedFlyOpacity; } this._flyLayerOpacity = null; this._savedFlyOpacity = 255; // 拖动弱化期间可能给 ui_fly 整层加过 UIOpacity,收尾时强制拉回 if (this._flyLayer?.isValid) { const layerUo = this._flyLayer.getComponent(UIOpacity); if (layerUo?.isValid && layerUo.opacity < 255) { layerUo.opacity = 255; } } if (this._scoreLayerOpacity?.isValid) { this._scoreLayerOpacity.opacity = this._savedScoreOpacity; } this._scoreLayerOpacity = null; this._savedScoreOpacity = 255; } private _startRefresh(): void { this._refreshToken++; this._scheduleRefreshTick(this._refreshToken); } private _stopRefresh(): void { this._refreshToken++; } private _scheduleRefreshTick(token: number): void { const session = gg.game.CurentBattle?.session; if (!session || token !== this._refreshToken || !this._active) return; session.clock.schedule(0.3, () => { if (token !== this._refreshToken || !this._active) return; this._applyMonsterNodes(); this._applyMechanismFxNodes(); this._scheduleRefreshTick(token); }); } }