|
|
@@ -6,6 +6,7 @@
|
|
|
import { sp, Node, director, Vec3, find, UITransform, tween, Tween, _decorator, Component } from "cc";
|
|
|
import EventBus, { GameEvents } from "../../../scripts/Core/EventBus";
|
|
|
import BlinkScaleAnimator from "../../../scripts/Animations/BlinkScaleAnimator";
|
|
|
+import { NewbieGuideManager } from '../../../scripts/Core/NewbieGuideManager';
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
|
|
@ccclass('GuideUIController')
|
|
|
@@ -244,6 +245,38 @@ export class GuideUIController extends Component {
|
|
|
this.guideStepsFrameDecorators[6] = this.guideStepsFrameDecorators[2];
|
|
|
}
|
|
|
|
|
|
+ // Step 7: 第八步,主界面升级按钮(点击类型)
|
|
|
+ if (!this.guideStepsTargets[7]) {
|
|
|
+ let upgradeBtn: Node | null = null;
|
|
|
+ // 优先通过 NavBarController 的 buttons[2] 获取可点击范围
|
|
|
+ const navBar = find('Canvas/NavBar');
|
|
|
+ const navCtrl = navBar ? (navBar.getComponent('NavBarController' as any) as any) : null;
|
|
|
+ if (navCtrl && navCtrl.buttons && navCtrl.buttons.length > 2) {
|
|
|
+ upgradeBtn = navCtrl.buttons[2];
|
|
|
+ }
|
|
|
+ // 兜底:直接查找路径 Canvas/NavBar/Layout/UPGRADE
|
|
|
+ if (!upgradeBtn) {
|
|
|
+ upgradeBtn = find('Canvas/NavBar/Layout/UPGRADE') ?? null;
|
|
|
+ }
|
|
|
+ if (!upgradeBtn) {
|
|
|
+ console.warn('[GuideUIController] UPGRADE button node not found for step 8');
|
|
|
+ }
|
|
|
+ this.guideStepsTargets[7] = this.guideStepsTargets[7] ?? upgradeBtn ?? null;
|
|
|
+ }
|
|
|
+ this.guideStepsActions[7] = this.guideStepsActions[7] ?? 'tap';
|
|
|
+ // 遮罩兜底为 step_2
|
|
|
+ if (!this.guideStepsMaskAreas[7]) {
|
|
|
+ const step2Mask = find('Canvas/GuideMaskAreas/step_2');
|
|
|
+ if (!step2Mask) {
|
|
|
+ console.warn('[GuideUIController] Mask fallback step_2 not found for step 8');
|
|
|
+ }
|
|
|
+ this.guideStepsMaskAreas[7] = this.guideStepsMaskAreas[7] ?? step2Mask ?? null;
|
|
|
+ }
|
|
|
+ // 可选:Frame装饰器复用第三步(如有)
|
|
|
+ if (!this.guideStepsFrameDecorators[7] && this.guideStepsFrameDecorators[2]) {
|
|
|
+ this.guideStepsFrameDecorators[7] = this.guideStepsFrameDecorators[2];
|
|
|
+ }
|
|
|
+
|
|
|
console.log('[GuideUIController] Tutorial steps ensured (editor config first, find as fallback)');
|
|
|
}
|
|
|
|
|
|
@@ -495,6 +528,53 @@ export class GuideUIController extends Component {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 点击类型步骤的统一手指展示:将手指放在按钮右下角并闪烁
|
|
|
+ */
|
|
|
+ private showFingerForClickStep(stepIndex: number, target: Node): void {
|
|
|
+ if (this._activeBlinkComp) {
|
|
|
+ this._activeBlinkComp.stop();
|
|
|
+ this._activeBlinkComp = null;
|
|
|
+ }
|
|
|
+ const finger = this.getGuideNode('guild_1');
|
|
|
+ if (!finger) {
|
|
|
+ console.warn('[GuideUIController] 未找到 guild_1 手指节点');
|
|
|
+ const ui = target.getComponent(UITransform);
|
|
|
+ const worldPos = ui ? ui.convertToWorldSpaceAR(new Vec3(0, 0, 0)) : target.worldPosition;
|
|
|
+ this.createPointingAnimation('guild_1', worldPos);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 停止手指的Spine动画,避免与缩放冲突
|
|
|
+ this.stopAnimation('guild_1');
|
|
|
+
|
|
|
+ // 计算目标按钮右下角世界坐标
|
|
|
+ const ui = target.getComponent(UITransform);
|
|
|
+ const baseWorldPos = ui
|
|
|
+ ? ui.convertToWorldSpaceAR(new Vec3(ui.width * 0.5, -ui.height * 0.5, 0))
|
|
|
+ : target.worldPosition;
|
|
|
+
|
|
|
+ // 转换为手指父节点的本地坐标并叠加指尖偏移
|
|
|
+ const localCorner = this.convertWorldToLocalPosition(baseWorldPos, finger.parent || finger);
|
|
|
+ const finalPos = new Vec3(
|
|
|
+ localCorner.x + this.fingerTipOffset.x,
|
|
|
+ localCorner.y + this.fingerTipOffset.y,
|
|
|
+ localCorner.z + this.fingerTipOffset.z
|
|
|
+ );
|
|
|
+ finger.setPosition(finalPos);
|
|
|
+
|
|
|
+ // 显示手指UI并播放闪烁缩放动画
|
|
|
+ this.showGuideUI('guild_1');
|
|
|
+ Tween.stopAllByTarget(finger);
|
|
|
+ this._activeBlinkComp = BlinkScaleAnimator.ensure(finger, {
|
|
|
+ scaleFactor: 1.3,
|
|
|
+ upDuration: 0.15,
|
|
|
+ downDuration: 0.15,
|
|
|
+ easingUp: 'sineOut',
|
|
|
+ easingDown: 'sineIn'
|
|
|
+ });
|
|
|
+ console.log(`[GuideUIController] 点击步骤 ${stepIndex}:手指位于按钮右下角并进行缩放闪烁`);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取节点的世界坐标位置
|
|
|
*/
|
|
|
@@ -700,6 +780,8 @@ export class GuideUIController extends Component {
|
|
|
if (guideLayer) {
|
|
|
guideLayer.active = false;
|
|
|
}
|
|
|
+ // 在完整序列结束时标记新手引导完成
|
|
|
+ NewbieGuideManager.getInstance().markNewbieGuideCompleted();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -741,8 +823,13 @@ export class GuideUIController extends Component {
|
|
|
|
|
|
console.log(`[GuideUIController] 运行步骤 idx=${idx}, action=${action}, hasFingerConfig=${!!hasFingerConfig}, target=${target?.name || 'null'}`);
|
|
|
|
|
|
- // 特殊处理第一步:显示从Block1到Grid_3_1的拖拽动画
|
|
|
- if (idx === 0 && action === 'wait_event') {
|
|
|
+ // 点击类步骤统一处理:在按钮右下角显示手指并闪烁
|
|
|
+ if (action === 'tap') {
|
|
|
+ this.showFingerForClickStep(idx, target);
|
|
|
+ } else if ((idx === 2 || idx === 6) && action === 'wait_event') {
|
|
|
+ // 第三/七步:沿用点击类的视觉表现,但等待事件完成
|
|
|
+ this.showFingerForClickStep(idx, target);
|
|
|
+ } else if (idx === 0 && action === 'wait_event') {
|
|
|
if (hasFingerConfig) {
|
|
|
// 使用配置的节点位置创建拖拽动画
|
|
|
this.createDragAnimation('guild_1', undefined, undefined, idx);
|
|
|
@@ -765,50 +852,6 @@ export class GuideUIController extends Component {
|
|
|
console.warn('[GuideUIController] 未找到Grid_3_1,使用普通指向动画');
|
|
|
}
|
|
|
}
|
|
|
- } else if ((idx === 2 || idx === 6) && action === 'wait_event') {
|
|
|
- // 第三步或第七步:将手指(guild_1)放到确认按钮右下角,并让手指原地缩放
|
|
|
- if (this._activeBlinkComp) {
|
|
|
- this._activeBlinkComp.stop();
|
|
|
- this._activeBlinkComp = null;
|
|
|
- }
|
|
|
-
|
|
|
- const finger = this.getGuideNode('guild_1');
|
|
|
- if (finger) {
|
|
|
- // 先停止手指的Spine动画,避免与缩放冲突
|
|
|
- this.stopAnimation('guild_1');
|
|
|
-
|
|
|
- // 计算目标按钮右下角的世界坐标
|
|
|
- const cornerWorldPos = ui
|
|
|
- ? ui.convertToWorldSpaceAR(new Vec3(ui.width * 0.5, -ui.height * 0.5, 0))
|
|
|
- : worldPos;
|
|
|
-
|
|
|
- // 转换为手指父节点的本地坐标并叠加指尖偏移
|
|
|
- const localCorner = this.convertWorldToLocalPosition(cornerWorldPos, finger.parent || finger);
|
|
|
- const finalPos = new Vec3(
|
|
|
- localCorner.x + this.fingerTipOffset.x,
|
|
|
- localCorner.y + this.fingerTipOffset.y,
|
|
|
- localCorner.z + this.fingerTipOffset.z
|
|
|
- );
|
|
|
- finger.setPosition(finalPos);
|
|
|
-
|
|
|
- // 显示手指UI(不启动移动动画)
|
|
|
- this.showGuideUI('guild_1');
|
|
|
- Tween.stopAllByTarget(finger);
|
|
|
-
|
|
|
- // 在手指上播放闪烁缩放动画
|
|
|
- this._activeBlinkComp = BlinkScaleAnimator.ensure(finger, {
|
|
|
- scaleFactor: 1.3,
|
|
|
- upDuration: 0.15,
|
|
|
- downDuration: 0.15,
|
|
|
- easingUp: 'sineOut',
|
|
|
- easingDown: 'sineIn'
|
|
|
- });
|
|
|
- console.log('[GuideUIController] 第三/七步:手指位于按钮右下角并进行缩放闪烁');
|
|
|
- } else {
|
|
|
- console.warn('[GuideUIController] 未找到 guild_1 手指节点');
|
|
|
- // 兜底使用普通指向动画
|
|
|
- this.createPointingAnimation('guild_1', worldPos);
|
|
|
- }
|
|
|
} else if (hasFingerConfig && (action === 'wait_event' || action === 'tap' || action === 'auto')) {
|
|
|
// 其他步骤如果配置了手指动画节点,也使用拖拽动画
|
|
|
this.createDragAnimation('guild_1', undefined, undefined, idx);
|
|
|
@@ -821,6 +864,78 @@ export class GuideUIController extends Component {
|
|
|
// 清理旧监听
|
|
|
this.clearCurrentStepListeners();
|
|
|
|
|
|
+ // 第四步超时保护:开始后3秒未完成则自动将方块移动到目标格子
|
|
|
+ if (idx === 3) {
|
|
|
+ if (this._autoScheduleCallback) {
|
|
|
+ this.unschedule(this._autoScheduleCallback as any);
|
|
|
+ this._autoScheduleCallback = null;
|
|
|
+ }
|
|
|
+ this._autoScheduleCallback = () => {
|
|
|
+ if (this._currentStepIndex !== 3) {
|
|
|
+ this._autoScheduleCallback = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.log('[GuideUIController] 第四步超时(5秒)未操作,自动将方块移动到目标格子');
|
|
|
+ const scene = director.getScene();
|
|
|
+ const blockManager = scene ? (scene.getComponentInChildren('BlockManager' as any) as any) : null;
|
|
|
+ if (!blockManager) {
|
|
|
+ console.warn('[GuideUIController] 第四步:未找到 BlockManager,无法自动放置');
|
|
|
+ this._autoScheduleCallback = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择要移动的方块:优先 fingerStartNodes[3],兜底在 PlacedBlocks 中找“I”型
|
|
|
+ let block: Node | null = (this.fingerStartNodes && this.fingerStartNodes.length > 3) ? this.fingerStartNodes[3] : null;
|
|
|
+ if (!block || !block.isValid) {
|
|
|
+ const placed = blockManager.placedBlocksContainer as Node | null;
|
|
|
+ if (placed && placed.isValid) {
|
|
|
+ for (let i = 0; i < placed.children.length; i++) {
|
|
|
+ const child = placed.children[i];
|
|
|
+ try {
|
|
|
+ const shapeId = blockManager.getBlockShape ? blockManager.getBlockShape(child) : null;
|
|
|
+ if (shapeId === 'I') { block = child; break; }
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ // 如果未找到“I”型,兜底选择第一个
|
|
|
+ if (!block && placed.children.length > 0) {
|
|
|
+ block = placed.children[0];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择目标格子:优先 Grid_3_9,其次 Grid_4_9
|
|
|
+ const findGrid = (name: string) => {
|
|
|
+ try {
|
|
|
+ if (blockManager.findGridNodeByName) { return blockManager.findGridNodeByName(name); }
|
|
|
+ } catch (e) {}
|
|
|
+ const gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
|
|
|
+ return gridContainer ? gridContainer.getChildByName(name) : null;
|
|
|
+ };
|
|
|
+ const grid3_9 = findGrid('Grid_3_9');
|
|
|
+ const grid4_9 = findGrid('Grid_4_9');
|
|
|
+
|
|
|
+ let placedOk = false;
|
|
|
+ if (block && block.isValid) {
|
|
|
+ if (grid3_9) {
|
|
|
+ try { placedOk = !!blockManager.tryPlaceBlockToSpecificGrid && blockManager.tryPlaceBlockToSpecificGrid(block, grid3_9); } catch (e) {}
|
|
|
+ }
|
|
|
+ if (!placedOk && grid4_9) {
|
|
|
+ try { placedOk = !!blockManager.tryPlaceBlockToSpecificGrid && blockManager.tryPlaceBlockToSpecificGrid(block, grid4_9); } catch (e) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (placedOk) {
|
|
|
+ console.log('[GuideUIController] 自动完成第四步:已将方块移动到目标位置');
|
|
|
+ // 不手动调用 _onStepComplete;等待 BlockManager 派发 TUTORIAL_BLOCK_3_PLACED
|
|
|
+ } else {
|
|
|
+ console.warn('[GuideUIController] 自动完成第四步失败:继续等待玩家操作');
|
|
|
+ }
|
|
|
+
|
|
|
+ this._autoScheduleCallback = null;
|
|
|
+ };
|
|
|
+ this.scheduleOnce(this._autoScheduleCallback as any, 5.0);
|
|
|
+ }
|
|
|
+
|
|
|
switch (action) {
|
|
|
case 'tap':
|
|
|
this._listeningTarget = target;
|
|
|
@@ -872,6 +987,11 @@ export class GuideUIController extends Component {
|
|
|
|
|
|
// 清理事件监听/计时器
|
|
|
this.clearCurrentStepListeners();
|
|
|
+ // 若存在任何未清理的定时器,统一取消
|
|
|
+ if (this._autoScheduleCallback) {
|
|
|
+ this.unschedule(this._autoScheduleCallback as any);
|
|
|
+ this._autoScheduleCallback = null;
|
|
|
+ }
|
|
|
|
|
|
// 进入下一步(第三步结束后延迟5秒再触发第四步;第四步结束等待第二次方块放置再触发第五步)
|
|
|
const prevIndex = this._currentStepIndex;
|
|
|
@@ -899,6 +1019,14 @@ export class GuideUIController extends Component {
|
|
|
this.runCurrentStep();
|
|
|
};
|
|
|
bus.on(gateEvent, onGate, this);
|
|
|
+ } else if (prevIndex === 6 && nextIndex === 7) {
|
|
|
+ // 第七步完成后不要立刻开始第八步,等待返回主界面事件
|
|
|
+ const bus = EventBus.getInstance();
|
|
|
+ const onReturn = () => {
|
|
|
+ bus.off(GameEvents.RETURN_TO_MAIN_MENU, onReturn, this);
|
|
|
+ this.runCurrentStep();
|
|
|
+ };
|
|
|
+ bus.on(GameEvents.RETURN_TO_MAIN_MENU, onReturn, this);
|
|
|
} else {
|
|
|
this.runCurrentStep();
|
|
|
}
|