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/BattleGameRandomLevelFxPres...

226 lines
8.5 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 { schedulePresentation } from '../../battle/skill/BattlePresentationClock';
import type { BattleBuffSkillDropTarget, CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import type { TextGrid } from '../../battle/view/TextGrid';
/** 授仙箓上层预制体(字块上方) */
const RANDOM_LEVEL_UPPER_PREFAB_NAME = '授仙箓上层';
/** 授仙箓预制体(字块下方) */
const RANDOM_LEVEL_LOWER_PREFAB_NAME = '授仙箓';
/** 上层 Spine 动画 */
const RANDOM_LEVEL_UPPER_ANIM_NAME = '授仙箓';
/** 下层升级 Spine 动画 */
const RANDOM_LEVEL_UP_ANIM_NAME = '升级效果';
/** 下层降级 Spine 动画 */
const RANDOM_LEVEL_DOWN_ANIM_NAME = '降级效果';
/** 下层相对上层延迟(逻辑秒) */
const LOWER_FX_DELAY_SEC = 1;
/** 授仙箓特效相对锚点 Y 偏移 */
const RANDOM_LEVEL_FX_OFFSET_Y = 20;
import type { TextGridBattleBuffFxLayer } from '../../battle/view/TextGrid';
/**
* 授仙箓(functiontype=7):上层播授仙箓,播完随机升/降级(合成武将升级补剩余经验);下层延迟 1s 起播升/降级效果。
*/
export class BattleGameRandomLevelFxPresenter {
private _bagView: CocosBagBoardView | null = null;
private readonly _activeFxNodes: Node[] = [];
private _playing = false;
/** 延迟下层特效世代(unbind 时递增以作废待触发回调) */
private _scheduleGen = 0;
private _lowerDelayPassed = false;
private _upperDone = false;
private _pendingLevelDelta: number | null = null;
private _pendingLowerGrid: TextGrid | null = null;
/** 绑定背包视图 */
bind(bagView: CocosBagBoardView | null, _flyLayer: Node | null | undefined): void {
this.unbind();
this._bagView = bagView;
}
/** 注销并中断全部进行中的表现 */
unbind(): void {
this._scheduleGen++;
this._abortAllFx();
this._bagView = null;
}
/**
* 上层播完后随机升/降级;下层在距上层开始满 1s 且已有结果后播升/降级效果。
* @param target 地图格 / 背包槽
* @param levelUpProbsWan 授仙箓概率配置
* @param onApplied 升/降级成功并进入 CD
*/
tryPlay(target: BattleBuffSkillDropTarget, levelUpProbsWan: readonly number[], onApplied: () => void): boolean {
if (this._playing) return false;
const bagView = this._bagView;
if (!bagView || levelUpProbsWan.length < 4) return false;
if (!bagView.canApplyBattleBuffRandomLevelTarget(target)) return false;
const grid = bagView.getTextGridForDropTarget(target);
if (!grid?.node?.isValid) {
console.log(`[RandomLevelFx] 目标字块视图缺失 ${target.kind}`);
return false;
}
const upperFx = this._spawnFxOnText(grid, RANDOM_LEVEL_UPPER_PREFAB_NAME, 'over');
if (!upperFx) {
console.log('[RandomLevelFx] 授仙箓上层预制体加载失败');
return false;
}
const skel = upperFx.getComponent(sp.Skeleton) ?? upperFx.getComponentInChildren(sp.Skeleton);
if (!skel?.isValid) {
console.log('[RandomLevelFx] 授仙箓上层缺少 sp.Skeleton');
this._releaseFx(upperFx);
return false;
}
this._playing = true;
this._lowerDelayPassed = false;
this._upperDone = false;
this._pendingLevelDelta = null;
this._pendingLowerGrid = grid;
const scheduleGen = ++this._scheduleGen;
schedulePresentation(LOWER_FX_DELAY_SEC, () => {
if (scheduleGen !== this._scheduleGen) return;
this._lowerDelayPassed = true;
this._tryPlayLowerFx();
});
BattleSpinePlayback.play(skel, RANDOM_LEVEL_UPPER_ANIM_NAME, {
loop: false,
onComplete: this._onUpperAnimComplete.bind(this, upperFx, skel, target, levelUpProbsWan, onApplied),
});
return true;
}
/** 上层播完:回收上层 → 随机升/降级 */
private _onUpperAnimComplete(
upperFx: Node,
skel: sp.Skeleton,
target: BattleBuffSkillDropTarget,
levelUpProbsWan: readonly number[],
onApplied: () => void,
): void {
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
this._releaseFx(upperFx);
const bagView = this._bagView;
if (!bagView) {
this._resetPlayState();
return;
}
if (!bagView.canApplyBattleBuffRandomLevelTarget(target)) {
console.log(`[RandomLevelFx] 上层动画结束时目标已不可施放 ${target.kind}`);
this._resetPlayState();
return;
}
const levelDelta = bagView.applyBattleBuffRandomLevelTarget(target, levelUpProbsWan);
if (levelDelta == null) {
console.log(`[RandomLevelFx] 随机升/降级失败 ${target.kind}`);
this._resetPlayState();
return;
}
onApplied();
this._pendingLevelDelta = levelDelta;
this._upperDone = true;
this._tryPlayLowerFx();
}
/** 下层:距上层开始满 1s 且升/降级结果已出时播放 */
private _tryPlayLowerFx(): void {
if (!this._upperDone || !this._lowerDelayPassed) return;
const levelDelta = this._pendingLevelDelta;
const grid = this._pendingLowerGrid;
this._resetPlayState();
if (levelDelta == null || levelDelta === 0 || !grid?.node?.isValid) return;
const animName = levelDelta > 0 ? RANDOM_LEVEL_UP_ANIM_NAME : RANDOM_LEVEL_DOWN_ANIM_NAME;
const lowerFx = this._spawnFxOnText(grid, RANDOM_LEVEL_LOWER_PREFAB_NAME, 'under');
if (!lowerFx) {
console.log('[RandomLevelFx] 授仙箓预制体加载失败');
return;
}
const skel = lowerFx.getComponent(sp.Skeleton) ?? lowerFx.getComponentInChildren(sp.Skeleton);
if (!skel?.isValid) {
console.log('[RandomLevelFx] 授仙箓缺少 sp.Skeleton');
this._releaseFx(lowerFx);
return;
}
BattleSpinePlayback.play(skel, animName, {
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('[RandomLevelFx] 预制体加载异常', prefabName, String(e));
return null;
}
if (!node?.isValid) return null;
grid.attachBattleBuffFx(node, layer);
const pos = node.position;
node.setPosition(pos.x, pos.y + RANDOM_LEVEL_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 _resetPlayState(): void {
this._playing = false;
this._lowerDelayPassed = false;
this._upperDone = false;
this._pendingLevelDelta = null;
this._pendingLowerGrid = null;
}
/** 中断并清理全部表现节点 */
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._resetPlayState();
}
}