import { Node, UITransform, Vec3, sp } from 'cc'; import { MergeEventType, type MergeEvent } from 'db://assets/mx/module/merge'; import type { BattleBagSession } from '../../battle/bag/BattleBagSession'; import { BattleSpinePlayback } from '../../battle/presentation/BattleSpinePlayback'; import { RankDataManager } from '../../manager/RankDataManager'; /** 引导手指 Spine 动画 */ const FINGER_ANIM = '点击'; /** 步骤节点父名 */ const STEPS_NODE = 'steps'; /** 第一步:点召唤 */ const STEP_SUMMON = '2'; /** 第二步:拖字上阵 */ const STEP_DRAG = '3'; /** 手指节点名 */ const FINGER_NODE = '引导手指'; /** * 新玩家首次进战弱引导:点召唤 → 拖字上阵。 * 手指对齐召唤按钮世界坐标,避免预制体旧坐标偏移。 */ export class BattleFirstGuidePresenter { /** 引导根 */ private _root: Node | null = null; /** 点召唤文案 */ private _stepSummon: Node | null = null; /** 拖字文案 */ private _stepDrag: Node | null = null; /** 手指 */ private _finger: Node | null = null; /** 手指 Spine */ private _fingerSk: sp.Skeleton | null = null; /** 召唤按钮(对齐目标) */ private _summonBtn: Node | null = null; /** 本机背包 */ private _bag: BattleBagSession | null = null; /** 当前步:0 未开 / 2 点召唤 / 3 拖字 */ private _step = 0; /** 复用世界坐标,避免每帧 new */ private readonly _alignWorld = new Vec3(); /** * 绑定并按存档决定是否展示第一步。 * @param root ui_guide * @param bag 本机背包会话 * @param summonBtn 召唤按钮(btnRefresh) */ bind(root: Node | null, bag: BattleBagSession | null, summonBtn?: Node | null): void { this.unbind(); if (!root?.isValid) { console.error('[BattleFirstGuide] ui_guide 缺失'); return; } root.active = false; if (!RankDataManager.shouldShowFirstBattleGuide()) return; const steps = root.getChildByName(STEPS_NODE); this._stepSummon = steps?.getChildByName(STEP_SUMMON) ?? null; this._stepDrag = steps?.getChildByName(STEP_DRAG) ?? null; this._finger = root.getChildByName(FINGER_NODE) ?? null; this._fingerSk = this._finger?.getComponent(sp.Skeleton) ?? null; if (!this._stepSummon?.isValid || !this._stepDrag?.isValid || !this._finger?.isValid) { console.error('[BattleFirstGuide] ui_guide 子节点缺失'); return; } if (!bag) { console.error('[BattleFirstGuide] bagSession 未就绪'); return; } if (!summonBtn?.isValid) { console.warn('[BattleFirstGuide] 召唤按钮缺失,手指可能错位'); } this._root = root; this._bag = bag; this._summonBtn = summonBtn?.isValid ? summonBtn : null; this._bag.on(MergeEventType.TilePlaced, this._onPlayerDrag); this._bag.on(MergeEventType.TileMoved, this._onPlayerDrag); this._showSummon(); } /** Widget 布局后再对齐一次(由界面 scheduleOnce 调用) */ realignFinger(): void { if (this._step !== 2) return; this._alignFingerToSummon(); } /** 召唤刷新成功:藏手指,切到拖字提示 */ onSummonOk(): void { if (this._step !== 2) return; this._showDrag(); const curWaveNum = gg.game.CurentBattle.CurentWaveNum; gg.sdk.reportDY('inLevel', `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-新手引导1点召唤`); } /** 停动画、解监听、藏根 */ unbind(): void { if (this._bag) { this._bag.off(MergeEventType.TilePlaced, this._onPlayerDrag); this._bag.off(MergeEventType.TileMoved, this._onPlayerDrag); } this._stopFinger(); if (this._root?.isValid) this._root.active = false; this._root = null; this._stepSummon = null; this._stepDrag = null; this._finger = null; this._fingerSk = null; this._summonBtn = null; this._bag = null; this._step = 0; } /** 第一步:手指点召唤 + 文案 2 */ private _showSummon(): void { const root = this._root; if (!root?.isValid) return; this._step = 2; root.active = true; if (this._stepSummon) this._stepSummon.active = true; if (this._stepDrag) this._stepDrag.active = false; if (this._finger) this._finger.active = true; this._alignFingerToSummon(); this._playFinger(); } /** 将手指对齐到召唤按钮中心(不拦截点击,否则会挡住召唤) */ private _alignFingerToSummon(): void { const finger = this._finger; const btn = this._summonBtn; if (!finger?.isValid || !btn?.isValid) return; btn.getWorldPosition(this._alignWorld); const put = finger.parent?.getComponent(UITransform); if (!put) { finger.setWorldPosition(this._alignWorld); } else { const local = put.convertToNodeSpaceAR(this._alignWorld); finger.setPosition(local.x, local.y, 0); } // Spine 仍绘制;命中盒清零,触摸落到下方 btnRefresh const fut = finger.getComponent(UITransform); if (fut) fut.setContentSize(0, 0); } /** 第二步:只留拖字文案 */ private _showDrag(): void { this._step = 3; if (this._stepSummon) this._stepSummon.active = false; if (this._stepDrag) this._stepDrag.active = true; this._stopFinger(); if (this._finger) this._finger.active = false; } /** 玩家拖到地图:结束引导 */ private readonly _onPlayerDrag = (_event: MergeEvent): void => { if (this._step !== 3) return; RankDataManager.markFirstBattleGuideDone(); this.unbind(); const curWaveNum = gg.game.CurentBattle.CurentWaveNum; gg.sdk.reportDY('inLevel', `${gg.game.getReportDYBattleCountLabel(curWaveNum)}-新手引导2拖字上阵`); }; /** 循环播手指点击 */ private _playFinger(): void { const sk = this._fingerSk; if (!sk?.isValid) { console.error('[BattleFirstGuide] 引导手指缺少 Spine'); return; } BattleSpinePlayback.play(sk, FINGER_ANIM, { loop: true }); } /** 停手指 Spine */ private _stopFinger(): void { const sk = this._fingerSk; if (!sk?.isValid) return; BattleSpinePlayback.clearPendingComplete(sk); BattleSpinePlayback.resetTracks(sk); } }