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

282 lines
10 KiB

3 weeks ago
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';
3 weeks ago
import type { BattleBuffSkillDropTarget, CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import type { TextGrid } from '../../battle/view/TextGrid';
3 weeks ago
/** 文昌笔预制体目录 */
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;
3 weeks ago
/**
* 文昌笔改字表现:主动 skill 先播后改字;属性卡播到一半再换皮。
3 weeks ago
*/
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;
3 weeks ago
/** 绑定背包视图与飞行层 */
bind(bagView: CocosBagBoardView | null, flyLayer: Node | null | undefined): void {
this.unbind();
this._bagView = bagView;
this._flyLayer = flyLayer?.isValid ? flyLayer : null;
}
/** 注销并中断进行中的演出 */
unbind(): void {
this._flushAttrRewriteIfNeeded();
3 weeks ago
this._abortPresentation();
this._abortAttrFx();
3 weeks ago
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;
}
3 weeks ago
/** 动画播完:移除预制体并提交随机改字 */
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?.();
}
3 weeks ago
/** 在字块中心生成文昌笔表现 */
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();
}
5 days ago
/** 中断属性卡表现(须先提交未换皮回调,避免卡已耗尽却未改字) */
private _abortAttrFx(): void {
5 days ago
this._flushAttrRewriteIfNeeded();
this._attrGen++;
const node = this._attrFxNode;
this._attrFxNode = null;
this._attrOnRewrite = null;
this._attrRewriteDone = true;
if (node?.isValid) this._releaseAttrFx(node);
}
3 weeks ago
/** 回收文昌笔节点 */
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;
}
}