You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
7.0 KiB
202 lines
7.0 KiB
|
3 weeks ago
|
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<Node, number>();
|
||
|
|
private readonly _savedFxNodeOpacity = new Map<Node, number>();
|
||
|
|
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);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|