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.
281 lines
10 KiB
281 lines
10 KiB
import { Node, sp, Tween, Vec3 } 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 WENCHANG_BI_PREFAB_DIR = BATTLE_HERO_BUFF_PATH;
|
|
/** 文昌笔预制体名 */
|
|
const WENCHANG_BI_PREFAB_NAME = '文昌笔';
|
|
/** 文昌笔 Spine 动画名 */
|
|
const WENCHANG_BI_ANIM_NAME = 'animation';
|
|
/** 属性卡:动画进度到该比例时换皮 */
|
|
const ATTR_REWRITE_AT_RATIO = 0.5;
|
|
|
|
/**
|
|
* 文昌笔改字表现:主动 skill 先播后改字;属性卡播到一半再换皮。
|
|
*/
|
|
export class BattleGameRewriteGlyphPresenter {
|
|
private _bagView: CocosBagBoardView | null = null;
|
|
private _flyLayer: Node | null = null;
|
|
private _playing = false;
|
|
private _fxNode: Node | null = null;
|
|
private _attrFxNode: Node | null = null;
|
|
private _attrGen = 0;
|
|
private _attrRewriteDone = true;
|
|
private _attrOnRewrite: (() => void) | null = null;
|
|
|
|
/** 绑定背包视图与飞行层 */
|
|
bind(bagView: CocosBagBoardView | null, flyLayer: Node | null | undefined): void {
|
|
this.unbind();
|
|
this._bagView = bagView;
|
|
this._flyLayer = flyLayer?.isValid ? flyLayer : null;
|
|
}
|
|
|
|
/** 注销并中断进行中的演出 */
|
|
unbind(): void {
|
|
this._flushAttrRewriteIfNeeded();
|
|
this._abortPresentation();
|
|
this._abortAttrFx();
|
|
this._bagView = null;
|
|
this._flyLayer = null;
|
|
}
|
|
|
|
/** 是否正在播放改字前置动画 */
|
|
get isPlaying(): boolean {
|
|
return this._playing;
|
|
}
|
|
|
|
/**
|
|
* 尝试在目标字块中心播放文昌笔,动画结束后执行随机改字。
|
|
* @param target 地图格 / 背包槽
|
|
* @param onSkillConsumed skill 进入 CD
|
|
*/
|
|
tryPlay(target: BattleBuffSkillDropTarget, onSkillConsumed: () => void): boolean {
|
|
if (this._playing) return false;
|
|
const bagView = this._bagView;
|
|
const flyLayer = this._flyLayer;
|
|
if (!bagView || !flyLayer?.isValid) return false;
|
|
if (!bagView.canApplyBattleBuffRewriteGlyphTarget(target)) return false;
|
|
|
|
const grid = bagView.getTextGridForDropTarget(target);
|
|
if (!grid?.node?.isValid) {
|
|
console.log(`[RewriteGlyph] 目标字块视图缺失 ${target.kind}`);
|
|
return false;
|
|
}
|
|
|
|
const worldPos = grid.node.worldPosition.clone();
|
|
this._playing = true;
|
|
|
|
const fx = this._spawnWenchangBiFx(flyLayer, worldPos);
|
|
if (!fx) {
|
|
console.log('[RewriteGlyph] 文昌笔预制体加载失败');
|
|
this._abortPresentation();
|
|
return false;
|
|
}
|
|
this._fxNode = fx;
|
|
onSkillConsumed();
|
|
|
|
const skel = fx.getComponent(sp.Skeleton) ?? fx.getComponentInChildren(sp.Skeleton);
|
|
if (!skel?.isValid) {
|
|
console.log('[RewriteGlyph] 文昌笔缺少 sp.Skeleton');
|
|
this._abortPresentation();
|
|
return false;
|
|
}
|
|
|
|
BattleSpinePlayback.play(skel, WENCHANG_BI_ANIM_NAME, {
|
|
loop: false,
|
|
onComplete: this._onAnimComplete.bind(this, target),
|
|
});
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 属性卡:先播文昌笔,动画一半时回调换皮,播完回收。
|
|
* @param target 目标字块
|
|
* @param onRewrite 半程换皮(须幂等)
|
|
*/
|
|
tryPlayAttrRewrite(target: BattleBuffSkillDropTarget, onRewrite: () => void): boolean {
|
|
if (this._playing || !this._attrRewriteDone) {
|
|
this._flushAttrRewriteIfNeeded();
|
|
onRewrite();
|
|
return false;
|
|
}
|
|
const bagView = this._bagView;
|
|
if (!bagView) {
|
|
onRewrite();
|
|
return false;
|
|
}
|
|
|
|
const grid = bagView.getTextGridForDropTarget(target);
|
|
if (!grid?.node?.isValid) {
|
|
console.log(`[RewriteGlyph] 属性卡目标字块缺失 ${target.kind}`);
|
|
onRewrite();
|
|
return false;
|
|
}
|
|
|
|
const fx = this._spawnFxOnText(grid);
|
|
if (!fx) {
|
|
console.log('[RewriteGlyph] 属性卡文昌笔加载失败');
|
|
onRewrite();
|
|
return false;
|
|
}
|
|
|
|
const skel = fx.getComponent(sp.Skeleton) ?? fx.getComponentInChildren(sp.Skeleton);
|
|
if (!skel?.isValid) {
|
|
console.log('[RewriteGlyph] 属性卡文昌笔缺少 sp.Skeleton');
|
|
this._releaseAttrFx(fx);
|
|
onRewrite();
|
|
return false;
|
|
}
|
|
|
|
this._attrFxNode = fx;
|
|
this._attrOnRewrite = onRewrite;
|
|
this._attrRewriteDone = false;
|
|
const gen = ++this._attrGen;
|
|
|
|
const duration = BattleSpinePlayback.getAnimDuration(skel, WENCHANG_BI_ANIM_NAME);
|
|
const midDelay = duration != null && duration > 0
|
|
? (duration * ATTR_REWRITE_AT_RATIO)
|
|
: 0;
|
|
schedulePresentation(midDelay, this._onAttrMidRewrite.bind(this, gen));
|
|
|
|
const played = BattleSpinePlayback.play(skel, WENCHANG_BI_ANIM_NAME, {
|
|
loop: false,
|
|
onComplete: this._onAttrFxComplete.bind(this, gen, fx, skel),
|
|
});
|
|
if (!played) {
|
|
this._onAttrMidRewrite(gen);
|
|
this._releaseAttrFx(fx);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** 动画播完:移除预制体并提交随机改字 */
|
|
private _onAnimComplete(target: BattleBuffSkillDropTarget): void {
|
|
this._releaseFx();
|
|
const bagView = this._bagView;
|
|
if (!bagView) {
|
|
this._playing = false;
|
|
return;
|
|
}
|
|
if (!bagView.canApplyBattleBuffRewriteGlyphTarget(target)) {
|
|
console.log(`[RewriteGlyph] 动画结束时目标已不可改字 ${target.kind}`);
|
|
this._playing = false;
|
|
return;
|
|
}
|
|
if (!bagView.applyBattleBuffRewriteGlyphTarget(target)) {
|
|
console.log(`[RewriteGlyph] 随机改字失败 ${target.kind}`);
|
|
}
|
|
this._playing = false;
|
|
}
|
|
|
|
/** 属性卡半程:换皮 */
|
|
private _onAttrMidRewrite(gen: number): void {
|
|
if (gen !== this._attrGen || this._attrRewriteDone) return;
|
|
this._attrRewriteDone = true;
|
|
const cb = this._attrOnRewrite;
|
|
this._attrOnRewrite = null;
|
|
cb?.();
|
|
}
|
|
|
|
/** 属性卡播完:确保已换皮并回收 */
|
|
private _onAttrFxComplete(gen: number, fx: Node, skel: sp.Skeleton): void {
|
|
if (gen !== this._attrGen) return;
|
|
this._onAttrMidRewrite(gen);
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
|
this._releaseAttrFx(fx);
|
|
}
|
|
|
|
/** 中断时若尚未换皮则立刻提交 */
|
|
private _flushAttrRewriteIfNeeded(): void {
|
|
if (this._attrRewriteDone) return;
|
|
this._attrRewriteDone = true;
|
|
const cb = this._attrOnRewrite;
|
|
this._attrOnRewrite = null;
|
|
this._attrGen++;
|
|
cb?.();
|
|
}
|
|
|
|
/** 在字块中心生成文昌笔表现 */
|
|
private _spawnWenchangBiFx(flyLayer: Node, worldPos: Readonly<Vec3>): Node | null {
|
|
if (!flyLayer.active) flyLayer.active = true;
|
|
let node: Node | null = null;
|
|
try {
|
|
node = gg.res.getNode(WENCHANG_BI_PREFAB_DIR, WENCHANG_BI_PREFAB_NAME, GBundle.BattleHero);
|
|
} catch (e) {
|
|
console.log('[RewriteGlyph] 文昌笔加载异常', String(e));
|
|
return null;
|
|
}
|
|
if (!node?.isValid) return null;
|
|
node.parent = flyLayer;
|
|
node.setWorldPosition(worldPos);
|
|
node.setSiblingIndex(flyLayer.children.length - 1);
|
|
node.active = true;
|
|
return node;
|
|
}
|
|
|
|
/** 挂到字块上叠播文昌笔 */
|
|
private _spawnFxOnText(grid: TextGrid): Node | null {
|
|
if (!grid?.node?.isValid) return null;
|
|
let node: Node | null = null;
|
|
try {
|
|
node = gg.res.getNode(WENCHANG_BI_PREFAB_DIR, WENCHANG_BI_PREFAB_NAME, GBundle.BattleHero);
|
|
} catch (e) {
|
|
console.log('[RewriteGlyph] 属性卡文昌笔加载异常', String(e));
|
|
return null;
|
|
}
|
|
if (!node?.isValid) return null;
|
|
grid.attachBattleBuffFx(node, 'over');
|
|
return node;
|
|
}
|
|
|
|
/** 回收属性卡文昌笔 */
|
|
private _releaseAttrFx(node: Node | null): void {
|
|
if (this._attrFxNode === node) this._attrFxNode = null;
|
|
if (!node?.isValid) return;
|
|
const skel = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton);
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
|
const host = node.parent?.getComponent(TextGrid);
|
|
if (host?.isValid) {
|
|
host.releaseAttachedBattleBuffFx(node);
|
|
return;
|
|
}
|
|
Tween.stopAllByTarget(node);
|
|
if (node['resKey']) gg.res.putNode(node);
|
|
else node.destroy();
|
|
}
|
|
|
|
/** 中断属性卡表现(须先提交未换皮回调,避免卡已耗尽却未改字) */
|
|
private _abortAttrFx(): void {
|
|
this._flushAttrRewriteIfNeeded();
|
|
this._attrGen++;
|
|
const node = this._attrFxNode;
|
|
this._attrFxNode = null;
|
|
this._attrOnRewrite = null;
|
|
this._attrRewriteDone = true;
|
|
if (node?.isValid) this._releaseAttrFx(node);
|
|
}
|
|
|
|
/** 回收文昌笔节点 */
|
|
private _releaseFx(): void {
|
|
const node = this._fxNode;
|
|
this._fxNode = null;
|
|
if (!node?.isValid) return;
|
|
const skel = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton);
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
|
Tween.stopAllByTarget(node);
|
|
if (node['resKey']) gg.res.putNode(node);
|
|
else node.destroy();
|
|
}
|
|
|
|
/** 中断演出并清理 */
|
|
private _abortPresentation(): void {
|
|
this._releaseFx();
|
|
this._playing = false;
|
|
}
|
|
}
|
|
|