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.
184 lines
6.7 KiB
184 lines
6.7 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 { BattleBuffSkillDropTarget, CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
|
|
import type { TextGrid } from '../../battle/view/TextGrid';
|
|
|
|
/** 封神榜上层预制体(字块上方) */
|
|
const UNIT_LEVEL_UP_UPPER_PREFAB_NAME = '封神榜上层';
|
|
/** 封神榜预制体(字块下方) */
|
|
const UNIT_LEVEL_UP_LOWER_PREFAB_NAME = '封神榜';
|
|
/** 上层 Spine 动画 */
|
|
const UNIT_LEVEL_UP_UPPER_ANIM_NAME = '授仙箓';
|
|
/** 下层 Spine 动画 */
|
|
const UNIT_LEVEL_UP_LOWER_ANIM_NAME = '升级效果';
|
|
/** 封神榜特效相对锚点 Y 偏移 */
|
|
const UNIT_LEVEL_UP_FX_OFFSET_Y = 20;
|
|
|
|
import type { TextGridBattleBuffFxLayer } from '../../battle/view/TextGrid';
|
|
|
|
/**
|
|
* 封神榜(functiontype=2):先播上层授仙箓,播完再升级(合成武将补剩余升级经验)并播下层升级效果。
|
|
*/
|
|
export class BattleGameUnitLevelUpFxPresenter {
|
|
private _bagView: CocosBagBoardView | null = null;
|
|
private readonly _activeFxNodes: Node[] = [];
|
|
private _playing = false;
|
|
|
|
/** 绑定背包视图 */
|
|
bind(bagView: CocosBagBoardView | null, _flyLayer: Node | null | undefined): void {
|
|
this.unbind();
|
|
this._bagView = bagView;
|
|
}
|
|
|
|
/** 注销并中断全部进行中的表现 */
|
|
unbind(): void {
|
|
this._abortAllFx();
|
|
this._bagView = null;
|
|
}
|
|
|
|
/**
|
|
* 上层动画播完后升级,并播下层升级效果。
|
|
* @param target 地图格 / 背包槽
|
|
* @param levelDelta functionpar 等级增量
|
|
* @param onApplied 升级成功并进入 CD
|
|
*/
|
|
tryPlay(target: BattleBuffSkillDropTarget, levelDelta: number, onApplied: () => void): boolean {
|
|
if (this._playing) return false;
|
|
const bagView = this._bagView;
|
|
if (!bagView) return false;
|
|
if (!bagView.canApplyBattleBuffLevelUpTarget(target, levelDelta)) return false;
|
|
|
|
const grid = bagView.getTextGridForDropTarget(target);
|
|
if (!grid?.node?.isValid) {
|
|
console.log(`[UnitLevelUpFx] 目标字块视图缺失 ${target.kind}`);
|
|
return false;
|
|
}
|
|
|
|
const upperFx = this._spawnFxOnText(grid, UNIT_LEVEL_UP_UPPER_PREFAB_NAME, 'over');
|
|
if (!upperFx) {
|
|
console.log('[UnitLevelUpFx] 封神榜上层预制体加载失败');
|
|
return false;
|
|
}
|
|
|
|
const skel = upperFx.getComponent(sp.Skeleton) ?? upperFx.getComponentInChildren(sp.Skeleton);
|
|
if (!skel?.isValid) {
|
|
console.log('[UnitLevelUpFx] 封神榜上层缺少 sp.Skeleton');
|
|
this._releaseFx(upperFx);
|
|
return false;
|
|
}
|
|
|
|
this._playing = true;
|
|
BattleSpinePlayback.play(skel, UNIT_LEVEL_UP_UPPER_ANIM_NAME, {
|
|
loop: false,
|
|
onComplete: this._onUpperAnimComplete.bind(this, upperFx, skel, target, levelDelta, grid, onApplied),
|
|
});
|
|
return true;
|
|
}
|
|
|
|
/** 上层播完:回收上层 → 升级 → 播下层升级效果 */
|
|
private _onUpperAnimComplete(
|
|
upperFx: Node,
|
|
skel: sp.Skeleton,
|
|
target: BattleBuffSkillDropTarget,
|
|
levelDelta: number,
|
|
grid: TextGrid,
|
|
onApplied: () => void,
|
|
): void {
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
|
this._releaseFx(upperFx);
|
|
|
|
const bagView = this._bagView;
|
|
if (!bagView) {
|
|
this._playing = false;
|
|
return;
|
|
}
|
|
if (!bagView.canApplyBattleBuffLevelUpTarget(target, levelDelta)) {
|
|
console.log(`[UnitLevelUpFx] 上层动画结束时目标已不可升级 ${target.kind}`);
|
|
this._playing = false;
|
|
return;
|
|
}
|
|
if (!bagView.applyBattleBuffLevelUpTarget(target, levelDelta)) {
|
|
console.log(`[UnitLevelUpFx] 升级失败 ${target.kind}`);
|
|
this._playing = false;
|
|
return;
|
|
}
|
|
|
|
onApplied();
|
|
this._playLowerFx(grid);
|
|
this._playing = false;
|
|
}
|
|
|
|
/** 播放下层封神榜升级效果 */
|
|
private _playLowerFx(grid: TextGrid): void {
|
|
if (!grid.node?.isValid) return;
|
|
|
|
const lowerFx = this._spawnFxOnText(grid, UNIT_LEVEL_UP_LOWER_PREFAB_NAME, 'under');
|
|
if (!lowerFx) {
|
|
console.log('[UnitLevelUpFx] 封神榜预制体加载失败');
|
|
return;
|
|
}
|
|
|
|
const skel = lowerFx.getComponent(sp.Skeleton) ?? lowerFx.getComponentInChildren(sp.Skeleton);
|
|
if (!skel?.isValid) {
|
|
console.log('[UnitLevelUpFx] 封神榜缺少 sp.Skeleton');
|
|
this._releaseFx(lowerFx);
|
|
return;
|
|
}
|
|
|
|
BattleSpinePlayback.play(skel, UNIT_LEVEL_UP_LOWER_ANIM_NAME, {
|
|
loop: false,
|
|
onComplete: this._onLowerAnimComplete.bind(this, lowerFx, skel),
|
|
});
|
|
}
|
|
|
|
/** 下层播完:回收 */
|
|
private _onLowerAnimComplete(fx: Node, skel: sp.Skeleton): void {
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
|
this._releaseFx(fx);
|
|
}
|
|
|
|
/** 挂到字块根节点并按层级插入 */
|
|
private _spawnFxOnText(grid: TextGrid, prefabName: string, layer: TextGridBattleBuffFxLayer): Node | null {
|
|
if (!grid?.node?.isValid) return null;
|
|
|
|
let node: Node | null = null;
|
|
try {
|
|
node = gg.res.getNode(BATTLE_HERO_BUFF_PATH, prefabName, GBundle.BattleHero);
|
|
} catch (e) {
|
|
console.log('[UnitLevelUpFx] 预制体加载异常', prefabName, String(e));
|
|
return null;
|
|
}
|
|
if (!node?.isValid) return null;
|
|
|
|
grid.attachBattleBuffFx(node, layer);
|
|
const pos = node.position;
|
|
node.setPosition(pos.x, pos.y + UNIT_LEVEL_UP_FX_OFFSET_Y, pos.z);
|
|
this._activeFxNodes.push(node);
|
|
return node;
|
|
}
|
|
|
|
/** 回收特效节点 */
|
|
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);
|
|
}
|
|
this._playing = false;
|
|
}
|
|
}
|
|
|