|
|
@@ -1,13 +1,57 @@
|
|
|
/**
|
|
|
- * 引导UI控制器
|
|
|
+ * 引导UI控制器 (Cocos Creator 3.x 适配版)
|
|
|
* 负责引导相关UI的显示、隐藏和动画控制
|
|
|
*/
|
|
|
|
|
|
-export class GuideUIController {
|
|
|
- private _guideNodes: Map<string, cc.Node> = new Map();
|
|
|
+import { sp, Node, director, Vec3, Color, Graphics, find, UITransform, tween, Tween, _decorator, Component } from "cc";
|
|
|
+import EventBus from "../../../scripts/Core/EventBus";
|
|
|
+const { ccclass, property } = _decorator;
|
|
|
+
|
|
|
+@ccclass('GuideUIController')
|
|
|
+export class GuideUIController extends Component {
|
|
|
+ private _guideNodes: Map<string, Node> = new Map();
|
|
|
private _spineComponents: Map<string, sp.Skeleton> = new Map();
|
|
|
private _isInitialized: boolean = false;
|
|
|
-
|
|
|
+
|
|
|
+ @property({ type: Node, tooltip: 'guild_0 节点(可选)' })
|
|
|
+ public guild0Node: Node = null;
|
|
|
+
|
|
|
+ @property({ type: Node, tooltip: 'guild_1 节点(手指/提示)' })
|
|
|
+ public guild1Node: Node = null;
|
|
|
+
|
|
|
+ @property({ type: [Node], tooltip: '按顺序配置的引导目标节点' })
|
|
|
+ public guideStepsTargets: Node[] = [];
|
|
|
+
|
|
|
+ @property({ type: [String], tooltip: '每步动作:tap | auto | wait_event' })
|
|
|
+ public guideStepsActions: string[] = [];
|
|
|
+
|
|
|
+ @property({ type: [String], tooltip: 'wait_event 对应事件名' })
|
|
|
+ public guideStepsEvents: string[] = [];
|
|
|
+
|
|
|
+ @property({ type: [Node], tooltip: '每步高亮区域占位节点(可选)' })
|
|
|
+ public guideStepsMaskAreas: Node[] = [];
|
|
|
+
|
|
|
+ // 统一使用圆角矩形遮罩;无需在检查器为每步配置参数
|
|
|
+// 如需精准控制每步区域,请在属性面板绑定 guideStepsMaskAreas[] 的占位节点
|
|
|
+// 未绑定时将使用 guideStepsTargets[idx] 的 UITransform 作为高亮区域
|
|
|
+
|
|
|
+ @property({ type: Number, tooltip: '暗遮罩不透明度(0-255)' })
|
|
|
+ public maskDarkAlpha: number = 160;
|
|
|
+
|
|
|
+ @property({ type: Number, tooltip: '高亮不透明度(0-255)' })
|
|
|
+ public maskLightAlpha: number = 32;
|
|
|
+
|
|
|
+ @property({ type: Number, tooltip: 'auto 步骤延时秒数' })
|
|
|
+ public autoStepDelay: number = 1.0;
|
|
|
+
|
|
|
+ @property({ type: Boolean, tooltip: '启动时自动开始引导' })
|
|
|
+ public autoStartOnLoad: boolean = false;
|
|
|
+
|
|
|
+ private _currentStepIndex: number = -1;
|
|
|
+ private _autoScheduleCallback: Function | null = null;
|
|
|
+ private _listeningTarget: Node | null = null;
|
|
|
+ private _listeningEventName: string | null = null;
|
|
|
+
|
|
|
/**
|
|
|
* 初始化UI控制器
|
|
|
*/
|
|
|
@@ -15,62 +59,80 @@ export class GuideUIController {
|
|
|
if (this._isInitialized) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
this.findGuideNodes();
|
|
|
this._isInitialized = true;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ start() {
|
|
|
+ this.init();
|
|
|
+ if (this.autoStartOnLoad) {
|
|
|
+ this.startGuideSequence();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * 查找引导节点
|
|
|
+ * 查找引导节点(优先使用属性面板绑定的节点,未绑定时兜底查找)
|
|
|
*/
|
|
|
private findGuideNodes(): void {
|
|
|
- // 查找场景中的引导节点
|
|
|
- const scene = cc.director.getScene();
|
|
|
- if (!scene) {
|
|
|
- console.warn("场景未找到,无法初始化引导UI");
|
|
|
- return;
|
|
|
+ // 优先使用属性配置的节点
|
|
|
+ if (this.guild0Node) {
|
|
|
+ this.registerGuideNode('guild_0', this.guild0Node);
|
|
|
+ }
|
|
|
+ if (this.guild1Node) {
|
|
|
+ this.registerGuideNode('guild_1', this.guild1Node);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如未配置齐全,兜底在场景中查找
|
|
|
+ if (this._guideNodes.size < 2) {
|
|
|
+ const scene = director.getScene();
|
|
|
+ if (!scene) {
|
|
|
+ console.warn("场景未找到,无法初始化引导UI");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!this._guideNodes.has('guild_0')) {
|
|
|
+ this.findAndRegisterGuideNode(scene, 'guild_0');
|
|
|
+ }
|
|
|
+ if (!this._guideNodes.has('guild_1')) {
|
|
|
+ this.findAndRegisterGuideNode(scene, 'guild_1');
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- // 查找guild_0和guild_1节点
|
|
|
- this.findAndRegisterGuideNode(scene, "guild_0");
|
|
|
- this.findAndRegisterGuideNode(scene, "guild_1");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 递归查找并注册引导节点
|
|
|
*/
|
|
|
- private findAndRegisterGuideNode(parent: cc.Node, nodeName: string): void {
|
|
|
+ private findAndRegisterGuideNode(parent: Node, nodeName: string): void {
|
|
|
// 检查当前节点
|
|
|
if (parent.name === nodeName) {
|
|
|
this.registerGuideNode(nodeName, parent);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 递归检查子节点
|
|
|
- for (let i = 0; i < parent.childrenCount; i++) {
|
|
|
- const child = parent.children[i];
|
|
|
+ for (const child of parent.children) {
|
|
|
this.findAndRegisterGuideNode(child, nodeName);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 注册引导节点
|
|
|
*/
|
|
|
- public registerGuideNode(nodeId: string, node: cc.Node): void {
|
|
|
+ public registerGuideNode(nodeId: string, node: Node): void {
|
|
|
this._guideNodes.set(nodeId, node);
|
|
|
-
|
|
|
+
|
|
|
// 查找Spine组件
|
|
|
const spineComp = node.getComponent(sp.Skeleton);
|
|
|
if (spineComp) {
|
|
|
this._spineComponents.set(nodeId, spineComp);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 默认隐藏节点
|
|
|
node.active = false;
|
|
|
-
|
|
|
+
|
|
|
console.log(`注册引导节点: ${nodeId}`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 显示引导UI
|
|
|
*/
|
|
|
@@ -80,21 +142,21 @@ export class GuideUIController {
|
|
|
console.warn(`引导节点未找到: ${nodeId}`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 显示节点
|
|
|
node.active = true;
|
|
|
-
|
|
|
+
|
|
|
// 播放动画
|
|
|
if (animationName) {
|
|
|
this.playAnimation(nodeId, animationName);
|
|
|
}
|
|
|
-
|
|
|
- // 设置层级
|
|
|
+
|
|
|
+ // 设置渲染优先级(替代旧 zIndex)
|
|
|
this.setGuideUIZIndex(node, 1000);
|
|
|
-
|
|
|
+
|
|
|
console.log(`显示引导UI: ${nodeId}`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 隐藏引导UI
|
|
|
*/
|
|
|
@@ -104,21 +166,21 @@ export class GuideUIController {
|
|
|
console.warn(`引导节点未找到: ${nodeId}`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
node.active = false;
|
|
|
console.log(`隐藏引导UI: ${nodeId}`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 隐藏所有引导UI
|
|
|
*/
|
|
|
public hideAllGuideUI(): void {
|
|
|
- for (const [nodeId, node] of this._guideNodes) {
|
|
|
+ for (const [, node] of this._guideNodes) {
|
|
|
node.active = false;
|
|
|
}
|
|
|
console.log("隐藏所有引导UI");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 播放动画
|
|
|
*/
|
|
|
@@ -128,7 +190,7 @@ export class GuideUIController {
|
|
|
console.warn(`Spine组件未找到: ${nodeId}`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
spineComp.setAnimation(0, animationName, loop);
|
|
|
console.log(`播放动画: ${nodeId} - ${animationName}`);
|
|
|
@@ -136,7 +198,7 @@ export class GuideUIController {
|
|
|
console.error(`播放动画失败: ${nodeId} - ${animationName}`, e);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 停止动画
|
|
|
*/
|
|
|
@@ -146,130 +208,317 @@ export class GuideUIController {
|
|
|
console.warn(`Spine组件未找到: ${nodeId}`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
spineComp.clearTracks();
|
|
|
console.log(`停止动画: ${nodeId}`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * 设置引导UI位置
|
|
|
+ * 设置引导UI位置(3.x 使用 Vec3)
|
|
|
*/
|
|
|
- public setGuideUIPosition(nodeId: string, position: cc.Vec2): void {
|
|
|
+ public setGuideUIPosition(nodeId: string, position: Vec3): void {
|
|
|
const node = this._guideNodes.get(nodeId);
|
|
|
if (!node) {
|
|
|
console.warn(`引导节点未找到: ${nodeId}`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- node.position = position;
|
|
|
+
|
|
|
+ node.setPosition(position);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * 设置引导UI层级
|
|
|
+ * 设置引导UI层级(3.x 使用 UITransform.priority 或 siblingIndex)
|
|
|
*/
|
|
|
- private setGuideUIZIndex(node: cc.Node, zIndex: number): void {
|
|
|
- node.zIndex = zIndex;
|
|
|
-
|
|
|
- // 确保父节点也有足够高的层级
|
|
|
+ private setGuideUIZIndex(node: Node, priority: number): void {
|
|
|
+ const ui = node.getComponent(UITransform);
|
|
|
+ if (ui) {
|
|
|
+ ui.priority = priority;
|
|
|
+ } else {
|
|
|
+ // 无 UITransform 时,使用 siblingIndex 作为兜底控制同级渲染顺序
|
|
|
+ node.setSiblingIndex(Number.MAX_SAFE_INTEGER);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保父节点也有足够高的优先级
|
|
|
let parent = node.parent;
|
|
|
while (parent) {
|
|
|
- if (parent.zIndex < zIndex) {
|
|
|
- parent.zIndex = zIndex;
|
|
|
+ const pUi = parent.getComponent(UITransform);
|
|
|
+ if (pUi && pUi.priority < priority) {
|
|
|
+ pUi.priority = priority;
|
|
|
}
|
|
|
parent = parent.parent;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * 创建手指指向动画
|
|
|
+ * 创建手指指向动画(3.x 使用 tween)
|
|
|
*/
|
|
|
- public createPointingAnimation(nodeId: string, targetPosition: cc.Vec2): void {
|
|
|
+ public createPointingAnimation(nodeId: string, targetPosition: Vec3): void {
|
|
|
const node = this._guideNodes.get(nodeId);
|
|
|
if (!node) {
|
|
|
console.warn(`引导节点未找到: ${nodeId}`);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 设置位置到目标位置附近
|
|
|
- const offset = cc.v2(50, 50); // 偏移量,可以调整
|
|
|
- node.position = targetPosition.add(offset);
|
|
|
-
|
|
|
+ const offset = new Vec3(50, 50, 0);
|
|
|
+ node.setPosition(new Vec3(targetPosition.x + offset.x, targetPosition.y + offset.y, targetPosition.z + offset.z));
|
|
|
+
|
|
|
// 显示节点并播放动画
|
|
|
this.showGuideUI(nodeId, "tap");
|
|
|
-
|
|
|
- // 添加缩放动画效果
|
|
|
- node.stopAllActions();
|
|
|
- const scaleAction = cc.repeatForever(
|
|
|
- cc.sequence(
|
|
|
- cc.scaleTo(0.5, 1.2),
|
|
|
- cc.scaleTo(0.5, 1.0)
|
|
|
+
|
|
|
+ // 添加缩放动画效果(替换旧 Action)
|
|
|
+ Tween.stopAllByTarget(node);
|
|
|
+ tween(node)
|
|
|
+ .repeatForever(
|
|
|
+ tween()
|
|
|
+ .to(0.5, { scale: 1.2 })
|
|
|
+ .to(0.5, { scale: 1.0 })
|
|
|
)
|
|
|
- );
|
|
|
- node.runAction(scaleAction);
|
|
|
+ .start();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
- * 创建高亮效果
|
|
|
+ * 创建高亮效果(统一为圆角矩形;可用 GuideMaskAreas 的子节点指定区域)
|
|
|
*/
|
|
|
- public createHighlightEffect(targetNode: cc.Node): void {
|
|
|
- if (!targetNode) {
|
|
|
+ public createHighlightEffect(targetNode: Node, stepIndex: number = -1): void {
|
|
|
+ if (!targetNode) return;
|
|
|
+
|
|
|
+ const canvas = find("Canvas") as Node;
|
|
|
+ if (!canvas) {
|
|
|
+ console.warn("未找到 Canvas,无法创建高亮遮罩");
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- // 创建高亮遮罩
|
|
|
- const maskNode = new cc.Node("guide_mask");
|
|
|
- maskNode.addComponent(cc.Graphics);
|
|
|
-
|
|
|
- const graphics = maskNode.getComponent(cc.Graphics);
|
|
|
- const canvas = cc.find("Canvas");
|
|
|
- if (canvas) {
|
|
|
+
|
|
|
+ let maskNode = canvas.getChildByName("guide_mask");
|
|
|
+ if (!maskNode) {
|
|
|
+ maskNode = new Node("guide_mask");
|
|
|
canvas.addChild(maskNode);
|
|
|
- maskNode.zIndex = 999;
|
|
|
-
|
|
|
- // 绘制半透明遮罩
|
|
|
- const canvasSize = canvas.getContentSize();
|
|
|
- graphics.fillColor = cc.Color.BLACK;
|
|
|
- graphics.fillRect(-canvasSize.width/2, -canvasSize.height/2, canvasSize.width, canvasSize.height);
|
|
|
-
|
|
|
- // 在目标位置挖洞(这里简化处理,实际可能需要更复杂的遮罩逻辑)
|
|
|
- const targetPos = targetNode.convertToWorldSpaceAR(cc.Vec2.ZERO);
|
|
|
- const localPos = maskNode.convertToNodeSpaceAR(targetPos);
|
|
|
- const targetSize = targetNode.getContentSize();
|
|
|
-
|
|
|
- graphics.fillColor = cc.Color.TRANSPARENT;
|
|
|
- graphics.fillRect(
|
|
|
- localPos.x - targetSize.width/2,
|
|
|
- localPos.y - targetSize.height/2,
|
|
|
- targetSize.width,
|
|
|
- targetSize.height
|
|
|
- );
|
|
|
}
|
|
|
+
|
|
|
+ const maskUi = maskNode.getComponent(UITransform) || maskNode.addComponent(UITransform);
|
|
|
+ const graphics = maskNode.getComponent(Graphics) || maskNode.addComponent(Graphics);
|
|
|
+ const canvasUi = canvas.getComponent(UITransform);
|
|
|
+ if (!canvasUi) return;
|
|
|
+
|
|
|
+ // 统一参数(无需在检查器填写)
|
|
|
+ const darkAlpha = (this.maskDarkAlpha ?? 160);
|
|
|
+ const lightAlpha = (this.maskLightAlpha ?? 32);
|
|
|
+ const defaultRadius = 12;
|
|
|
+ const defaultPadding = 8;
|
|
|
+
|
|
|
+ graphics.clear();
|
|
|
+
|
|
|
+ // 背景遮罩
|
|
|
+ graphics.fillColor = new Color(0, 0, 0, darkAlpha);
|
|
|
+ graphics.rect(-canvasUi.width / 2, -canvasUi.height / 2, canvasUi.width, canvasUi.height);
|
|
|
+ graphics.fill();
|
|
|
+
|
|
|
+ // 选择用于计算形状的节点:优先使用检查器绑定的 guideStepsMaskAreas[idx]
|
|
|
+ const shapeTargetCandidate = (stepIndex >= 0 && this.guideStepsMaskAreas && this.guideStepsMaskAreas[stepIndex])
|
|
|
+ ? this.guideStepsMaskAreas[stepIndex]
|
|
|
+ : null;
|
|
|
+ const shapeTarget = (shapeTargetCandidate && shapeTargetCandidate.isValid) ? shapeTargetCandidate : targetNode;
|
|
|
+ const targetUi = shapeTarget.getComponent(UITransform) || targetNode.getComponent(UITransform);
|
|
|
+ if (!targetUi) {
|
|
|
+ console.warn("目标或遮罩区域缺少 UITransform");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const worldPos = targetUi.convertToWorldSpaceAR(new Vec3(0, 0, 0));
|
|
|
+ const localPos = maskUi.convertToNodeSpaceAR(worldPos);
|
|
|
+
|
|
|
+ graphics.fillColor = new Color(255, 255, 255, lightAlpha);
|
|
|
+
|
|
|
+ // 如果使用占位节点,默认不再附加 padding;否则对目标加少量 padding
|
|
|
+ const usingCustomArea = (shapeTarget !== targetNode);
|
|
|
+ const padding = usingCustomArea ? 0 : defaultPadding;
|
|
|
+ const radius = defaultRadius;
|
|
|
+
|
|
|
+ const width = targetUi.width + padding * 2;
|
|
|
+ const height = targetUi.height + padding * 2;
|
|
|
+ const x = localPos.x - width / 2;
|
|
|
+ const y = localPos.y - height / 2;
|
|
|
+
|
|
|
+ const gAny: any = graphics as any;
|
|
|
+ if (gAny.roundRect) {
|
|
|
+ gAny.roundRect(x, y, width, height, Math.max(0, radius));
|
|
|
+ } else {
|
|
|
+ const r = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
|
|
|
+ graphics.moveTo(x + r, y);
|
|
|
+ graphics.lineTo(x + width - r, y);
|
|
|
+ graphics.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
|
+ graphics.lineTo(x + width, y + height - r);
|
|
|
+ graphics.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
|
+ graphics.lineTo(x + r, y + height);
|
|
|
+ graphics.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
|
+ graphics.lineTo(x, y + r);
|
|
|
+ graphics.quadraticCurveTo(x, y, x + r, y);
|
|
|
+ }
|
|
|
+ graphics.fill();
|
|
|
+
|
|
|
+ this.setGuideUIZIndex(maskNode, 999);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ // 已移除:遮罩区域节点查找方法,改为使用 guideStepsMaskAreas[] 属性绑定
|
|
|
+
|
|
|
/**
|
|
|
* 移除高亮效果
|
|
|
*/
|
|
|
public removeHighlightEffect(): void {
|
|
|
- const canvas = cc.find("Canvas");
|
|
|
+ const canvas = find("Canvas") as Node;
|
|
|
if (canvas) {
|
|
|
const maskNode = canvas.getChildByName("guide_mask");
|
|
|
if (maskNode) {
|
|
|
maskNode.removeFromParent();
|
|
|
+ maskNode.destroy();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按数组顺序开始新手引导
|
|
|
+ */
|
|
|
+ public startGuideSequence(): void {
|
|
|
+ if (!this._isInitialized) {
|
|
|
+ this.init();
|
|
|
+ }
|
|
|
+ if (!this.guideStepsTargets || this.guideStepsTargets.length === 0) {
|
|
|
+ console.warn('[GuideUIController] 未配置引导步骤');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this._currentStepIndex = 0;
|
|
|
+ this.runCurrentStep();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 运行当前步骤
|
|
|
+ */
|
|
|
+ private runCurrentStep(): void {
|
|
|
+ const idx = this._currentStepIndex;
|
|
|
+ if (idx < 0 || idx >= this.guideStepsTargets.length) {
|
|
|
+ console.log('[GuideUIController] 引导步骤结束');
|
|
|
+ this.hideAllGuideUI();
|
|
|
+ this.removeHighlightEffect();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const target = this.guideStepsTargets[idx];
|
|
|
+ const action = (this.guideStepsActions[idx] || 'tap').toLowerCase();
|
|
|
+
|
|
|
+ if (!target || !target.isValid) {
|
|
|
+ console.warn(`[GuideUIController] 当前步骤目标节点无效,自动跳过 index=${idx}`);
|
|
|
+ this.nextStep();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const ui = target.getComponent(UITransform);
|
|
|
+ const worldPos = ui ? ui.convertToWorldSpaceAR(new Vec3(0, 0, 0)) : target.worldPosition;
|
|
|
+
|
|
|
+ // 显示手指与高亮
|
|
|
+ this.createPointingAnimation('guild_1', worldPos);
|
|
|
+ this.createHighlightEffect(target, idx);
|
|
|
+
|
|
|
+ // 清理旧监听
|
|
|
+ this.clearCurrentStepListeners();
|
|
|
+
|
|
|
+ switch (action) {
|
|
|
+ case 'tap':
|
|
|
+ this._listeningTarget = target;
|
|
|
+ target.on(Node.EventType.TOUCH_END, this._onStepComplete, this);
|
|
|
+ break;
|
|
|
+ case 'auto':
|
|
|
+ this._autoScheduleCallback = () => this._onStepComplete();
|
|
|
+ this.scheduleOnce(this._autoScheduleCallback as any, this.autoStepDelay);
|
|
|
+ break;
|
|
|
+ case 'wait_event':
|
|
|
+ const evt = this.guideStepsEvents[idx];
|
|
|
+ if (!evt) {
|
|
|
+ console.warn('[GuideUIController] wait_event 步骤未配置事件名,自动跳过');
|
|
|
+ this.nextStep();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this._listeningEventName = evt;
|
|
|
+ EventBus.getInstance().on(evt, this._onStepComplete, this);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ console.warn(`[GuideUIController] 未知步骤动作 '${action}',使用 tap`);
|
|
|
+ this._listeningTarget = target;
|
|
|
+ target.on(Node.EventType.TOUCH_END, this._onStepComplete, this);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 当前步骤完成
|
|
|
+ */
|
|
|
+ private _onStepComplete(): void {
|
|
|
+ // 清理当前指引
|
|
|
+ this.hideGuideUI('guild_1');
|
|
|
+ const finger = this.getGuideNode('guild_1');
|
|
|
+ if (finger) {
|
|
|
+ Tween.stopAllByTarget(finger);
|
|
|
+ }
|
|
|
+ this.removeHighlightEffect();
|
|
|
+
|
|
|
+ // 清理事件监听/计时器
|
|
|
+ this.clearCurrentStepListeners();
|
|
|
+
|
|
|
+ // 进入下一步
|
|
|
+ this._currentStepIndex++;
|
|
|
+ this.runCurrentStep();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清理当前步骤的监听
|
|
|
+ */
|
|
|
+ private clearCurrentStepListeners(): void {
|
|
|
+ if (this._listeningTarget) {
|
|
|
+ this._listeningTarget.off(Node.EventType.TOUCH_END, this._onStepComplete, this);
|
|
|
+ this._listeningTarget = null;
|
|
|
+ }
|
|
|
+ if (this._listeningEventName) {
|
|
|
+ EventBus.getInstance().off(this._listeningEventName, this._onStepComplete, this);
|
|
|
+ this._listeningEventName = null;
|
|
|
+ }
|
|
|
+ if (this._autoScheduleCallback) {
|
|
|
+ this.unschedule(this._autoScheduleCallback as any);
|
|
|
+ this._autoScheduleCallback = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 进入下一步(外部可调用)
|
|
|
+ */
|
|
|
+ public nextStep(): void {
|
|
|
+ this._currentStepIndex++;
|
|
|
+ this.runCurrentStep();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止引导序列(外部可调用)
|
|
|
+ */
|
|
|
+ public stopGuideSequence(): void {
|
|
|
+ this.clearCurrentStepListeners();
|
|
|
+ this.hideAllGuideUI();
|
|
|
+ this.removeHighlightEffect();
|
|
|
+ this._currentStepIndex = -1;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 检查节点是否已注册
|
|
|
*/
|
|
|
public hasGuideNode(nodeId: string): boolean {
|
|
|
return this._guideNodes.has(nodeId);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 获取引导节点
|
|
|
*/
|
|
|
- public getGuideNode(nodeId: string): cc.Node {
|
|
|
- return this._guideNodes.get(nodeId);
|
|
|
+ public getGuideNode(nodeId: string): Node {
|
|
|
+ return this._guideNodes.get(nodeId) || null;
|
|
|
+ }
|
|
|
+
|
|
|
+ onDestroy() {
|
|
|
+ this.stopGuideSequence();
|
|
|
}
|
|
|
}
|