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.
AdLike2048/assets/script/ui/BattleGame/BattleGameAttackRangeFxPres...

116 lines
4.1 KiB

3 weeks ago
import { Node, sp, Tween } from 'cc';
import { GBundle } from '../../game/ConfigRes';
import { BATTLE_HERO_BUFF_PATH } from '../../battle/res/BattleHeroResCollector';
import { BattleSpinePlayback } from '../../battle/presentation/BattleSpinePlayback';
import type { AttackRangeBuffFxPreview, BattleBuffSkillDropTarget, CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import type { TextGrid } from '../../battle/view/TextGrid';
/** 千里眼预制体名 */
const ATTACK_RANGE_FX_PREFAB_NAME = '千里眼';
/** 千里眼 Spine 动画名 */
const ATTACK_RANGE_FX_ANIM_NAME = 'animation';
/**
* 千里眼主动 buff 施法表现:字块中心播千里眼动画,与 buff 逻辑并行。
*/
export class BattleGameAttackRangeFxPresenter {
private _bagView: CocosBagBoardView | null = null;
private readonly _activeFxNodes: Node[] = [];
/** 绑定背包视图 */
bind(bagView: CocosBagBoardView | null, _flyLayer: Node | null | undefined): void {
this.unbind();
this._bagView = bagView;
}
/** 注销并中断全部进行中的表现 */
unbind(): void {
this._abortAllFx();
this._bagView = null;
}
/**
* 在目标字块中心播放千里眼动画(纯表现,不阻塞 buff 生效)。
* @returns 是否成功起播
*/
tryPlay(target: BattleBuffSkillDropTarget, preview: AttackRangeBuffFxPreview | null = null): boolean {
const bagView = this._bagView;
if (!bagView) return false;
if (preview) {
bagView.playAttackRangeBuffRangeTween(preview);
}
const grid = bagView.getTextGridForDropTarget(target);
if (!grid?.node?.isValid) {
console.log(`[AttackRangeFx] 目标字块视图缺失 ${target.kind}`);
return false;
}
const fx = this._spawnFxAtTextCenter(grid);
if (!fx) {
console.log('[AttackRangeFx] 千里眼预制体加载失败');
return false;
}
const skel = fx.getComponent(sp.Skeleton) ?? fx.getComponentInChildren(sp.Skeleton);
if (!skel?.isValid) {
console.log('[AttackRangeFx] 千里眼缺少 sp.Skeleton');
this._releaseFx(fx);
return false;
}
BattleSpinePlayback.play(skel, ATTACK_RANGE_FX_ANIM_NAME, {
loop: false,
onComplete: this._onAnimComplete.bind(this, fx, skel),
});
return true;
}
/** 挂到字块根节点;多格组合字时锚点对齐两格中心 */
private _spawnFxAtTextCenter(grid: TextGrid): Node | null {
if (!grid?.node?.isValid) return null;
let node: Node | null = null;
try {
node = gg.res.getNode(BATTLE_HERO_BUFF_PATH, ATTACK_RANGE_FX_PREFAB_NAME, GBundle.BattleHero);
} catch (e) {
console.log('[AttackRangeFx] 千里眼加载异常', String(e));
return null;
}
if (!node?.isValid) return null;
grid.attachBattleBuffFx(node, 'under');
this._activeFxNodes.push(node);
return node;
}
/** 动画播完:回收单个预制体 */
private _onAnimComplete(fx: Node, skel: sp.Skeleton): void {
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
this._releaseFx(fx);
}
/** 回收千里眼节点 */
private _releaseFx(node: Node | null): void {
if (!node?.isValid) return;
const idx = this._activeFxNodes.indexOf(node);
if (idx >= 0) this._activeFxNodes.splice(idx, 1);
Tween.stopAllByTarget(node);
if (node['resKey']) gg.res.putNode(node);
else node.destroy();
}
/** 中断并清理全部表现节点 */
private _abortAllFx(): void {
const pending = this._activeFxNodes.slice();
this._activeFxNodes.length = 0;
for (let i = 0; i < pending.length; i++) {
const node = pending[i];
const skel = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton);
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
this._releaseFx(node);
}
}
}