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.
115 lines
4.1 KiB
115 lines
4.1 KiB
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);
|
|
}
|
|
}
|
|
}
|
|
|