2 次代碼提交 7b90ed7c0f ... 8af10b8e1e

作者 SHA1 備註 提交日期
  181404010226 8af10b8e1e 关卡修复 1 月之前
  181404010226 02e8ffd8dc 新手引导直接战斗 1 月之前
共有 22 個文件被更改,包括 1997 次插入665 次删除
  1. 0 0
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.atlas
  2. 0 0
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.atlas.meta
  3. 5 5
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.json
  4. 二進制
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.png
  5. 134 0
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.png.meta
  6. 二進制
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.skel
  7. 14 0
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.skel.meta
  8. 6 5
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideDataManager.ts
  9. 355 106
      assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideUIController.ts
  10. 0 459
      assets/Scenes/First.scene
  11. 0 11
      assets/Scenes/First.scene.meta
  12. 847 64
      assets/Scenes/GameLevel.scene
  13. 二進制
      assets/images/Blocks/Frame.png
  14. 134 0
      assets/images/Blocks/Frame.png.meta
  15. 101 12
      assets/scripts/CombatSystem/BlockManager.ts
  16. 217 0
      assets/scripts/Core/NewbieGuideManager.ts
  17. 9 0
      assets/scripts/Core/NewbieGuideManager.ts.meta
  18. 37 0
      assets/scripts/FourUI/MainSystem/MainUIControlller.ts
  19. 105 0
      assets/scripts/Guide/DragBlockToGridStep.ts
  20. 9 0
      assets/scripts/Guide/DragBlockToGridStep.ts.meta
  21. 14 1
      assets/scripts/LevelSystem/GameManager.ts
  22. 10 2
      assets/scripts/LevelSystem/SaveDataManager.ts

+ 0 - 0
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.atlas.txt → assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.atlas


+ 0 - 0
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.atlas.txt.meta → assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.atlas.meta


+ 5 - 5
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.json

@@ -2,10 +2,10 @@
   "skeleton": {
     "hash": "tut_hand",
     "spine": "3.8.95",
-    "x": -40,
-    "y": -60,
-    "width": 80,
-    "height": 120
+    "x": -100,
+    "y": -100,
+    "width": 144,
+    "height": 264
   },
   "bones": [
     {
@@ -16,7 +16,7 @@
       "parent": "root",
       "length": 50,
       "x": 0,
-      "y": 0
+      "y": 100
     }
   ],
   "slots": [

二進制
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.png


+ 134 - 0
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "db336bea-8063-4ee0-b63f-a731be3b10d0",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "db336bea-8063-4ee0-b63f-a731be3b10d0@6c48a",
+      "displayName": "tut_hand",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "db336bea-8063-4ee0-b63f-a731be3b10d0",
+        "isUuid": true,
+        "visible": false,
+        "minfilter": "linear",
+        "magfilter": "linear",
+        "mipfilter": "none",
+        "anisotropy": 0
+      },
+      "ver": "1.0.22",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    },
+    "f9941": {
+      "importer": "sprite-frame",
+      "uuid": "db336bea-8063-4ee0-b63f-a731be3b10d0@f9941",
+      "displayName": "tut_hand",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": -1,
+        "offsetY": 0.5,
+        "trimX": 0,
+        "trimY": 2,
+        "width": 142,
+        "height": 259,
+        "rawWidth": 144,
+        "rawHeight": 264,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -71,
+            -129.5,
+            0,
+            71,
+            -129.5,
+            0,
+            -71,
+            129.5,
+            0,
+            71,
+            129.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            262,
+            142,
+            262,
+            0,
+            3,
+            142,
+            3
+          ],
+          "nuv": [
+            0,
+            0.011363636363636364,
+            0.9861111111111112,
+            0.011363636363636364,
+            0,
+            0.9924242424242424,
+            0.9861111111111112,
+            0.9924242424242424
+          ],
+          "minPos": [
+            -71,
+            -129.5,
+            0
+          ],
+          "maxPos": [
+            71,
+            129.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "db336bea-8063-4ee0-b63f-a731be3b10d0@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "db336bea-8063-4ee0-b63f-a731be3b10d0@6c48a"
+  }
+}

二進制
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.skel


+ 14 - 0
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/resources/spine/tut_hand.skel.meta

@@ -0,0 +1,14 @@
+{
+  "ver": "1.2.7",
+  "importer": "spine-data",
+  "imported": true,
+  "uuid": "f2b29f12-002f-4458-a460-8a6badb9fe9c",
+  "files": [
+    ".bin",
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {
+    "atlasUuid": "02e6b549-cc01-4e72-9be2-073fe38485c0"
+  }
+}

+ 6 - 5
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideDataManager.ts

@@ -22,13 +22,13 @@ export class GuideDataManager {
      */
     private loadGuideData(): void {
         // 从本地存储加载引导索引
-        const savedIndex = cc.sys.localStorage.getItem(GuideDataManager.GUIDE_INDEX_KEY);
+        const savedIndex = sys.localStorage.getItem(GuideDataManager.GUIDE_INDEX_KEY);
         if (savedIndex !== null) {
             this._guideIndex = parseInt(savedIndex) || 0;
         }
         
         // 从本地存储加载引导数据
-        const savedData = cc.sys.localStorage.getItem(GuideDataManager.GUIDE_DATA_KEY);
+        const savedData = sys.localStorage.getItem(GuideDataManager.GUIDE_DATA_KEY);
         if (savedData) {
             try {
                 this._guideData = JSON.parse(savedData);
@@ -44,12 +44,12 @@ export class GuideDataManager {
      */
     private saveGuideData(): void {
         // 保存引导索引
-        cc.sys.localStorage.setItem(GuideDataManager.GUIDE_INDEX_KEY, this._guideIndex.toString());
+        sys.localStorage.setItem(GuideDataManager.GUIDE_INDEX_KEY, this._guideIndex.toString());
         
         // 保存引导数据
         try {
             const dataStr = JSON.stringify(this._guideData);
-            cc.sys.localStorage.setItem(GuideDataManager.GUIDE_DATA_KEY, dataStr);
+            sys.localStorage.setItem(GuideDataManager.GUIDE_DATA_KEY, dataStr);
         } catch (e) {
             console.error("保存引导数据失败", e);
         }
@@ -177,4 +177,5 @@ export class GuideDataManager {
             return false;
         }
     }
-}
+}
+import { sys } from 'cc';

+ 355 - 106
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideUIController.ts

@@ -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();
     }
 }

+ 0 - 459
assets/Scenes/First.scene

@@ -1,459 +0,0 @@
-[
-  {
-    "__type__": "cc.SceneAsset",
-    "_name": "First",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "_native": "",
-    "scene": {
-      "__id__": 1
-    }
-  },
-  {
-    "__type__": "cc.Scene",
-    "_name": "First",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "_parent": null,
-    "_children": [
-      {
-        "__id__": 2
-      }
-    ],
-    "_active": true,
-    "_components": [],
-    "_prefab": {
-      "__id__": 8
-    },
-    "_lpos": {
-      "__type__": "cc.Vec3",
-      "x": 0,
-      "y": 0,
-      "z": 0
-    },
-    "_lrot": {
-      "__type__": "cc.Quat",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 1
-    },
-    "_lscale": {
-      "__type__": "cc.Vec3",
-      "x": 1,
-      "y": 1,
-      "z": 1
-    },
-    "_mobility": 0,
-    "_layer": 1073741824,
-    "_euler": {
-      "__type__": "cc.Vec3",
-      "x": 0,
-      "y": 0,
-      "z": 0
-    },
-    "autoReleaseAssets": false,
-    "_globals": {
-      "__id__": 9
-    },
-    "_id": "8a125a67-dbc5-4c64-b398-6a616deee4aa"
-  },
-  {
-    "__type__": "cc.Node",
-    "_name": "Canvas",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "_parent": {
-      "__id__": 1
-    },
-    "_children": [
-      {
-        "__id__": 3
-      }
-    ],
-    "_active": true,
-    "_components": [
-      {
-        "__id__": 5
-      },
-      {
-        "__id__": 6
-      },
-      {
-        "__id__": 7
-      }
-    ],
-    "_prefab": null,
-    "_lpos": {
-      "__type__": "cc.Vec3",
-      "x": 360,
-      "y": 667,
-      "z": 0
-    },
-    "_lrot": {
-      "__type__": "cc.Quat",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 1
-    },
-    "_lscale": {
-      "__type__": "cc.Vec3",
-      "x": 1,
-      "y": 1,
-      "z": 1
-    },
-    "_mobility": 0,
-    "_layer": 33554432,
-    "_euler": {
-      "__type__": "cc.Vec3",
-      "x": 0,
-      "y": 0,
-      "z": 0
-    },
-    "_id": "beI88Z2HpFELqR4T5EMHpg"
-  },
-  {
-    "__type__": "cc.Node",
-    "_name": "Camera",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "_parent": {
-      "__id__": 2
-    },
-    "_children": [],
-    "_active": true,
-    "_components": [
-      {
-        "__id__": 4
-      }
-    ],
-    "_prefab": null,
-    "_lpos": {
-      "__type__": "cc.Vec3",
-      "x": 0,
-      "y": 0,
-      "z": 1000
-    },
-    "_lrot": {
-      "__type__": "cc.Quat",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 1
-    },
-    "_lscale": {
-      "__type__": "cc.Vec3",
-      "x": 1,
-      "y": 1,
-      "z": 1
-    },
-    "_mobility": 0,
-    "_layer": 1073741824,
-    "_euler": {
-      "__type__": "cc.Vec3",
-      "x": 0,
-      "y": 0,
-      "z": 0
-    },
-    "_id": "ebFwiq8gBFaYpqYbdoDODe"
-  },
-  {
-    "__type__": "cc.Camera",
-    "_name": "",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "node": {
-      "__id__": 3
-    },
-    "_enabled": true,
-    "__prefab": null,
-    "_projection": 0,
-    "_priority": 0,
-    "_fov": 45,
-    "_fovAxis": 0,
-    "_orthoHeight": 667,
-    "_near": 0,
-    "_far": 2000,
-    "_color": {
-      "__type__": "cc.Color",
-      "r": 0,
-      "g": 0,
-      "b": 0,
-      "a": 255
-    },
-    "_depth": 1,
-    "_stencil": 0,
-    "_clearFlags": 7,
-    "_rect": {
-      "__type__": "cc.Rect",
-      "x": 0,
-      "y": 0,
-      "width": 1,
-      "height": 1
-    },
-    "_aperture": 19,
-    "_shutter": 7,
-    "_iso": 0,
-    "_screenScale": 1,
-    "_visibility": 1108344832,
-    "_targetTexture": null,
-    "_postProcess": null,
-    "_usePostProcess": false,
-    "_cameraType": -1,
-    "_trackingType": 0,
-    "_id": "63WIch3o5BEYRlXzTT0oWc"
-  },
-  {
-    "__type__": "cc.UITransform",
-    "_name": "",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "node": {
-      "__id__": 2
-    },
-    "_enabled": true,
-    "__prefab": null,
-    "_contentSize": {
-      "__type__": "cc.Size",
-      "width": 720,
-      "height": 1334
-    },
-    "_anchorPoint": {
-      "__type__": "cc.Vec2",
-      "x": 0.5,
-      "y": 0.5
-    },
-    "_id": "d6rUX5yfhMlKoWX2bSbawx"
-  },
-  {
-    "__type__": "cc.Canvas",
-    "_name": "",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "node": {
-      "__id__": 2
-    },
-    "_enabled": true,
-    "__prefab": null,
-    "_cameraComponent": {
-      "__id__": 4
-    },
-    "_alignCanvasWithScreen": true,
-    "_id": "12O/ljcVlEqLmVm3U2gEOQ"
-  },
-  {
-    "__type__": "cc.Widget",
-    "_name": "",
-    "_objFlags": 0,
-    "__editorExtras__": {},
-    "node": {
-      "__id__": 2
-    },
-    "_enabled": true,
-    "__prefab": null,
-    "_alignFlags": 45,
-    "_target": null,
-    "_left": 0,
-    "_right": 0,
-    "_top": 5.684341886080802e-14,
-    "_bottom": 5.684341886080802e-14,
-    "_horizontalCenter": 0,
-    "_verticalCenter": 0,
-    "_isAbsLeft": true,
-    "_isAbsRight": true,
-    "_isAbsTop": true,
-    "_isAbsBottom": true,
-    "_isAbsHorizontalCenter": true,
-    "_isAbsVerticalCenter": true,
-    "_originalWidth": 0,
-    "_originalHeight": 0,
-    "_alignMode": 2,
-    "_lockFlags": 0,
-    "_id": "c5V1EV8IpMtrIvY1OE9t2u"
-  },
-  {
-    "__type__": "cc.PrefabInfo",
-    "root": null,
-    "asset": null,
-    "fileId": "8a125a67-dbc5-4c64-b398-6a616deee4aa",
-    "instance": null,
-    "targetOverrides": null
-  },
-  {
-    "__type__": "cc.SceneGlobals",
-    "ambient": {
-      "__id__": 10
-    },
-    "shadows": {
-      "__id__": 11
-    },
-    "_skybox": {
-      "__id__": 12
-    },
-    "fog": {
-      "__id__": 13
-    },
-    "octree": {
-      "__id__": 14
-    },
-    "skin": {
-      "__id__": 15
-    },
-    "lightProbeInfo": {
-      "__id__": 16
-    },
-    "postSettings": {
-      "__id__": 17
-    },
-    "bakedWithStationaryMainLight": false,
-    "bakedWithHighpLightmap": false
-  },
-  {
-    "__type__": "cc.AmbientInfo",
-    "_skyColorHDR": {
-      "__type__": "cc.Vec4",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 0.520833125
-    },
-    "_skyColor": {
-      "__type__": "cc.Vec4",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 0.520833125
-    },
-    "_skyIllumHDR": 20000,
-    "_skyIllum": 20000,
-    "_groundAlbedoHDR": {
-      "__type__": "cc.Vec4",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 0
-    },
-    "_groundAlbedo": {
-      "__type__": "cc.Vec4",
-      "x": 0,
-      "y": 0,
-      "z": 0,
-      "w": 0
-    },
-    "_skyColorLDR": {
-      "__type__": "cc.Vec4",
-      "x": 0.2,
-      "y": 0.5,
-      "z": 0.8,
-      "w": 1
-    },
-    "_skyIllumLDR": 20000,
-    "_groundAlbedoLDR": {
-      "__type__": "cc.Vec4",
-      "x": 0.2,
-      "y": 0.2,
-      "z": 0.2,
-      "w": 1
-    }
-  },
-  {
-    "__type__": "cc.ShadowsInfo",
-    "_enabled": false,
-    "_type": 0,
-    "_normal": {
-      "__type__": "cc.Vec3",
-      "x": 0,
-      "y": 1,
-      "z": 0
-    },
-    "_distance": 0,
-    "_planeBias": 1,
-    "_shadowColor": {
-      "__type__": "cc.Color",
-      "r": 76,
-      "g": 76,
-      "b": 76,
-      "a": 255
-    },
-    "_maxReceived": 4,
-    "_size": {
-      "__type__": "cc.Vec2",
-      "x": 512,
-      "y": 512
-    }
-  },
-  {
-    "__type__": "cc.SkyboxInfo",
-    "_envLightingType": 0,
-    "_envmapHDR": null,
-    "_envmap": null,
-    "_envmapLDR": null,
-    "_diffuseMapHDR": null,
-    "_diffuseMapLDR": null,
-    "_enabled": false,
-    "_useHDR": true,
-    "_editableMaterial": null,
-    "_reflectionHDR": null,
-    "_reflectionLDR": null,
-    "_rotationAngle": 0
-  },
-  {
-    "__type__": "cc.FogInfo",
-    "_type": 0,
-    "_fogColor": {
-      "__type__": "cc.Color",
-      "r": 200,
-      "g": 200,
-      "b": 200,
-      "a": 255
-    },
-    "_enabled": false,
-    "_fogDensity": 0.3,
-    "_fogStart": 0.5,
-    "_fogEnd": 300,
-    "_fogAtten": 5,
-    "_fogTop": 1.5,
-    "_fogRange": 1.2,
-    "_accurate": false
-  },
-  {
-    "__type__": "cc.OctreeInfo",
-    "_enabled": false,
-    "_minPos": {
-      "__type__": "cc.Vec3",
-      "x": -1024,
-      "y": -1024,
-      "z": -1024
-    },
-    "_maxPos": {
-      "__type__": "cc.Vec3",
-      "x": 1024,
-      "y": 1024,
-      "z": 1024
-    },
-    "_depth": 8
-  },
-  {
-    "__type__": "cc.SkinInfo",
-    "_enabled": false,
-    "_blurRadius": 0.01,
-    "_sssIntensity": 3
-  },
-  {
-    "__type__": "cc.LightProbeInfo",
-    "_giScale": 1,
-    "_giSamples": 1024,
-    "_bounces": 2,
-    "_reduceRinging": 0,
-    "_showProbe": true,
-    "_showWireframe": true,
-    "_showConvex": false,
-    "_data": null,
-    "_lightProbeSphereVolume": 1
-  },
-  {
-    "__type__": "cc.PostSettingsInfo",
-    "_toneMappingType": 0
-  }
-]

+ 0 - 11
assets/Scenes/First.scene.meta

@@ -1,11 +0,0 @@
-{
-  "ver": "1.1.50",
-  "importer": "scene",
-  "imported": true,
-  "uuid": "8a125a67-dbc5-4c64-b398-6a616deee4aa",
-  "files": [
-    ".json"
-  ],
-  "subMetas": {},
-  "userData": {}
-}

File diff suppressed because it is too large
+ 847 - 64
assets/Scenes/GameLevel.scene


二進制
assets/images/Blocks/Frame.png


+ 134 - 0
assets/images/Blocks/Frame.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "e1fd154e-fd63-4ab7-b135-8102d1301306",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "e1fd154e-fd63-4ab7-b135-8102d1301306@6c48a",
+      "displayName": "Frame",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "e1fd154e-fd63-4ab7-b135-8102d1301306",
+        "isUuid": true,
+        "visible": false,
+        "minfilter": "linear",
+        "magfilter": "linear",
+        "mipfilter": "none",
+        "anisotropy": 0
+      },
+      "ver": "1.0.22",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    },
+    "f9941": {
+      "importer": "sprite-frame",
+      "uuid": "e1fd154e-fd63-4ab7-b135-8102d1301306@f9941",
+      "displayName": "Frame",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 109,
+        "height": 65,
+        "rawWidth": 109,
+        "rawHeight": 65,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -54.5,
+            -32.5,
+            0,
+            54.5,
+            -32.5,
+            0,
+            -54.5,
+            32.5,
+            0,
+            54.5,
+            32.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            65,
+            109,
+            65,
+            0,
+            0,
+            109,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -54.5,
+            -32.5,
+            0
+          ],
+          "maxPos": [
+            54.5,
+            32.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "e1fd154e-fd63-4ab7-b135-8102d1301306@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "e1fd154e-fd63-4ab7-b135-8102d1301306@6c48a"
+  }
+}

+ 101 - 12
assets/scripts/CombatSystem/BlockManager.ts

@@ -11,6 +11,7 @@ import EventBus, { GameEvents } from '../Core/EventBus';
 import { sp } from 'cc';
 import { BundleLoader } from '../Core/BundleLoader';
 import { Audio } from '../AudioManager/AudioManager';
+import { GuideUIController } from '../../NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideUIController';
 const { ccclass, property } = _decorator;
 
 @ccclass('BlockManager')
@@ -105,6 +106,7 @@ export class BlockManager extends Component {
         tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/GameArea/PlacedBlocks)'
     })
     public placedBlocksContainer: Node = null;
+    private guideUIController: GuideUIController = null;
     
     
     // 游戏是否已开始
@@ -273,6 +275,18 @@ export class BlockManager extends Component {
     private onGenerateBlocksEvent() {
         console.log('[BlockManager] 接收到方块生成事件');
         this.generateRandomBlocksInKuang();
+
+        // 引导:指向可拖拽的方块
+        this.scheduleOnce(() => {
+            if (this.guideUIController && this.block1Container) {
+                const ui = this.block1Container.getComponent(UITransform);
+                if (ui) {
+                    const worldPos = ui.convertToWorldSpaceAR(new Vec3(0, 0, 0));
+                    this.guideUIController.createPointingAnimation('guild_1', worldPos);
+                    this.guideUIController.createHighlightEffect(this.block1Container);
+                }
+            }
+        }, 0);
     }
     
     // 处理游戏开始事件 - 已废弃,避免与GameBlockSelection重复生成方块
@@ -285,6 +299,18 @@ export class BlockManager extends Component {
     private onWaveCompletedEvent() {
         console.log('[BlockManager] 接收到波次完成事件,生成新方块');
         this.generateRandomBlocksInKuang();
+
+        // 引导:波次结束后新生成方块也展示指引
+        this.scheduleOnce(() => {
+            if (this.guideUIController && this.block1Container) {
+                const ui = this.block1Container.getComponent(UITransform);
+                if (ui) {
+                    const worldPos = ui.convertToWorldSpaceAR(new Vec3(0, 0, 0));
+                    this.guideUIController.createPointingAnimation('guild_1', worldPos);
+                    this.guideUIController.createHighlightEffect(this.block1Container);
+                }
+            }
+        }, 0);
     }
     
     /**
@@ -504,6 +530,13 @@ export class BlockManager extends Component {
         
         // 设置事件监听器
         this.setupEventListeners();
+
+        // 新手引导控制器初始化(从 Canvas 获取已挂载的组件)
+        const canvas = find('Canvas');
+        this.guideUIController = canvas ? canvas.getComponent(GuideUIController) : null;
+        if (!this.guideUIController) {
+            console.warn('[BlockManager] Canvas未挂载GuideUIController组件,指引功能不可用');
+        }
         
         // 调试绘制功能已迁移到GameBlockSelection
         
@@ -707,6 +740,12 @@ export class BlockManager extends Component {
         
         // 获取当前关卡的武器配置列表
         const levelWeapons = await this.getCurrentLevelWeapons();
+
+        // 是否处于新手引导的首次生成场景(第1关、未使用过刷新/加球、且尚未放置方块)
+        const isNewbieTutorial = this.isNewbieTutorialContext();
+        if (isNewbieTutorial) {
+            console.log('[BlockManager] 检测到新手引导场景,优先生成固定形状的方块');
+        }
         
         for (let i = 0; i < 3; i++) {
             let weaponConfig: WeaponConfig | null = null;
@@ -741,20 +780,26 @@ export class BlockManager extends Component {
                 continue;
             }
             
-            // 从武器配置的形状成本中随机选择一个形状ID
+            // 选择形状ID:新手引导优先使用固定形状,否则随机
             let targetShapeId: string | null = null;
-            try {
-                const shapeCosts = weaponConfig.inGameCostConfig?.shapeCosts;
-                if (shapeCosts && Object.keys(shapeCosts).length > 0) {
-                    const availableShapes = Object.keys(shapeCosts);
-                    targetShapeId = availableShapes[Math.floor(Math.random() * availableShapes.length)];
-                    console.log(`[BlockManager] 为武器 ${weaponConfig.name} 选择形状: ${targetShapeId}`);
-                    console.log(`[BlockManager] 可用形状列表: ${availableShapes.join(', ')}`);
-                } else {
-                    console.warn(`[BlockManager] 武器 ${weaponConfig.name} 没有配置shapeCosts或为空`);
+            if (isNewbieTutorial) {
+                targetShapeId = this.getTutorialShapeForIndex(i, weaponConfig);
+                console.log(`[BlockManager] 新手引导为武器 ${weaponConfig.name} 指定形状: ${targetShapeId ?? '未找到合适形状,回退随机'}`);
+            }
+            if (!targetShapeId) {
+                try {
+                    const shapeCosts = weaponConfig.inGameCostConfig?.shapeCosts;
+                    if (shapeCosts && Object.keys(shapeCosts).length > 0) {
+                        const availableShapes = Object.keys(shapeCosts);
+                        targetShapeId = availableShapes[Math.floor(Math.random() * availableShapes.length)];
+                        console.log(`[BlockManager] 为武器 ${weaponConfig.name} 随机选择形状: ${targetShapeId}`);
+                        console.log(`[BlockManager] 可用形状列表: ${availableShapes.join(', ')}`);
+                    } else {
+                        console.warn(`[BlockManager] 武器 ${weaponConfig.name} 没有配置shapeCosts或为空`);
+                    }
+                } catch (error) {
+                    console.warn(`[BlockManager] 无法从武器配置获取形状信息: ${error}`);
                 }
-            } catch (error) {
-                console.warn(`[BlockManager] 无法从武器配置获取形状信息: ${error}`);
             }
             
             // 基于武器配置和目标形状选择合适的预制体
@@ -821,6 +866,44 @@ export class BlockManager extends Component {
         
         this.updateCoinDisplay();
     }
+
+    /**
+     * 新手引导环境判定:仅在第1关、未使用过刷新/加球、且未放置任何方块时生效
+     */
+    private isNewbieTutorialContext(): boolean {
+        try {
+            const levelOk = !!this.saveDataManager && typeof this.saveDataManager.getCurrentLevel === 'function' && this.saveDataManager.getCurrentLevel() === 1;
+            const sessionOk = !!this.session && typeof this.session.getRefreshUsageCount === 'function' && typeof this.session.getAddBallUsageCount === 'function'
+                && this.session.getRefreshUsageCount() === 0 && this.session.getAddBallUsageCount() === 0;
+            const noPlaced = !this.hasPlacedBlocks();
+            return levelOk && sessionOk && noPlaced;
+        } catch (e) {
+            console.warn(`[BlockManager] 新手引导上下文检测失败: ${e}`);
+            return false;
+        }
+    }
+
+    /**
+     * 新手引导下为第 i 个容器选择固定形状(I、S、L),并确保形状在该武器可用
+     */
+    private getTutorialShapeForIndex(index: number, weaponConfig: WeaponConfig): string | null {
+        const preferredOrder = ['I', 'H-I', 'L'];
+        const desired = preferredOrder[index % preferredOrder.length];
+        const shapeCosts = weaponConfig?.inGameCostConfig?.shapeCosts || null;
+        if (shapeCosts && typeof shapeCosts === 'object') {
+            if (shapeCosts[desired] !== undefined) {
+                return desired;
+            }
+            // 选择首个在该武器可用的优先形状
+            const fallbackPreferred = preferredOrder.find(s => shapeCosts[s] !== undefined);
+            if (fallbackPreferred) {
+                return fallbackPreferred;
+            }
+            const anyShape = Object.keys(shapeCosts)[0];
+            return anyShape || null;
+        }
+        return null;
+    }
     
     // 将db节点与方块关联
     // 将Block容器与方块关联
@@ -1184,6 +1267,12 @@ export class BlockManager extends Component {
             weaponNode.setScale(0.4, 0.4, 1.0);
             console.log(`[BlockManager] 方块 ${block.name} 放置到网格,植物图标缩放设置为0.4倍`);
         }
+
+        // 引导:放置成功后隐藏指引与高亮
+        if (this.guideUIController) {
+            this.guideUIController.hideAllGuideUI();
+            this.guideUIController.removeHighlightEffect();
+        }
         
         return true;
     }

+ 217 - 0
assets/scripts/Core/NewbieGuideManager.ts

@@ -0,0 +1,217 @@
+import { _decorator, director } from 'cc';
+import { SaveDataManager } from '../LevelSystem/SaveDataManager';
+import EventBus, { GameEvents } from './EventBus';
+
+const { ccclass } = _decorator;
+
+/**
+ * 新手引导管理器
+ * 负责管理新手引导的状态和自动触发机制
+ */
+@ccclass('NewbieGuideManager')
+export class NewbieGuideManager {
+    private static _instance: NewbieGuideManager;
+    private saveDataManager: SaveDataManager;
+    private isInProgress: boolean = false;
+    private hasCheckedOnSceneLoad: boolean = false;
+
+    private constructor() {
+        this.saveDataManager = SaveDataManager.getInstance();
+        this.setupEventListeners();
+    }
+
+    public static getInstance(): NewbieGuideManager {
+        if (!NewbieGuideManager._instance) {
+            NewbieGuideManager._instance = new NewbieGuideManager();
+        }
+        return NewbieGuideManager._instance;
+    }
+
+    /**
+     * 设置事件监听器
+     */
+    private setupEventListeners(): void {
+        // 监听游戏开始事件,检查是否需要启动新手引导
+        EventBus.getInstance().on(GameEvents.GAME_START, this.onGameStart, this);
+    }
+
+    /**
+     * 在GameLevel场景加载时检查并自动启动新手引导
+     * 这个方法应该在MainUIController的onLoad中调用
+     */
+    public checkAndStartNewbieGuideOnSceneLoad(): void {
+        if (this.hasCheckedOnSceneLoad) {
+            return; // 避免重复检查
+        }
+        
+        this.hasCheckedOnSceneLoad = true;
+        console.log('[NewbieGuideManager] 场景加载时检查新手引导状态');
+        
+        if (this.isNewPlayer()) {
+            console.log('[NewbieGuideManager] 检测到新用户,启动新手引导流程');
+            this.startNewbieGuide();
+        } else {
+            console.log('[NewbieGuideManager] 用户已完成新手引导,跳过引导流程');
+        }
+    }
+
+    /**
+     * 启动新手引导流程
+     */
+    private startNewbieGuide(): void {
+        this.isInProgress = true;
+        console.log('[NewbieGuideManager] 新手引导开始,自动触发战斗流程');
+        
+        // 立即触发战斗流程
+        this.triggerBattleFlow();
+    }
+
+    /**
+     * 检查用户是否是新用户(第一次游戏)
+     * @returns true表示是新用户,需要新手引导
+     */
+    public isNewUser(): boolean {
+        return this.isNewPlayer();
+    }
+
+    /**
+     * 检查玩家是否是新手(第一次游戏)
+     * @returns true表示是新手,需要新手引导
+     */
+    public isNewPlayer(): boolean {
+        if (!this.saveDataManager) {
+            console.warn('[NewbieGuideManager] SaveDataManager未初始化');
+            return false;
+        }
+
+        const playerData = this.saveDataManager.getPlayerData();
+        
+        // 检查是否有新手引导完成标记
+        if (playerData.settings && playerData.settings.newbieGuideCompleted !== undefined) {
+            return !playerData.settings.newbieGuideCompleted;
+        }
+
+        // 如果没有设置,检查其他指标判断是否是新手
+        // 比如当前关卡是否为1,总游戏时间是否为0等
+        const isNewbie = playerData.currentLevel === 1 && 
+                         playerData.statistics.totalPlayTime === 0 &&
+                         playerData.statistics.totalGamesPlayed === 0;
+
+        console.log('[NewbieGuideManager] 新手检查结果:', {
+            currentLevel: playerData.currentLevel,
+            totalPlayTime: playerData.statistics.totalPlayTime,
+            totalGamesPlayed: playerData.statistics.totalGamesPlayed,
+            isNewbie: isNewbie
+        });
+
+        return isNewbie;
+    }
+
+    /**
+     * 标记新手引导已完成
+     */
+    public markNewbieGuideCompleted(): void {
+        if (!this.saveDataManager) {
+            console.warn('[NewbieGuideManager] SaveDataManager未初始化');
+            return;
+        }
+
+        const playerData = this.saveDataManager.getPlayerData();
+        if (!playerData.settings) {
+            playerData.settings = {
+                soundEnabled: true,
+                musicEnabled: true,
+                soundVolume: 0.8,
+                musicVolume: 0.6,
+                vibrationEnabled: true,
+                autoSaveEnabled: true,
+                language: 'zh-CN',
+                graphics: 'medium',
+                newbieGuideCompleted: false
+            };
+        }
+        
+        playerData.settings.newbieGuideCompleted = true;
+        this.saveDataManager.savePlayerData();
+        this.isInProgress = false;
+        
+        console.log('[NewbieGuideManager] 新手引导已标记为完成');
+    }
+
+    /**
+     * 重置新手引导状态(用于测试)
+     */
+    public resetNewbieGuideStatus(): void {
+        if (!this.saveDataManager) {
+            console.warn('[NewbieGuideManager] SaveDataManager未初始化');
+            return;
+        }
+
+        const playerData = this.saveDataManager.getPlayerData();
+        if (!playerData.settings) {
+            playerData.settings = {
+                soundEnabled: true,
+                musicEnabled: true,
+                soundVolume: 0.8,
+                musicVolume: 0.6,
+                vibrationEnabled: true,
+                autoSaveEnabled: true,
+                language: 'zh-CN',
+                graphics: 'medium',
+                newbieGuideCompleted: false
+            };
+        }
+        
+        playerData.settings.newbieGuideCompleted = false;
+        this.saveDataManager.savePlayerData();
+        
+        console.log('[NewbieGuideManager] 新手引导状态已重置');
+    }
+
+    /**
+     * 检查当前是否正在进行新手引导
+     */
+    public isNewbieGuideInProgress(): boolean {
+        return this.isInProgress;
+    }
+
+    /**
+     * 自动触发战斗流程
+     * 模拟玩家点击战斗按钮的行为
+     */
+    private triggerBattleFlow(): void {
+        console.log('[NewbieGuideManager] 新手引导:自动触发战斗流程');
+        
+        // 发送新手引导开始事件
+        EventBus.getInstance().emit('NEWBIE_GUIDE_BATTLE_START');
+        
+        // 这里我们不直接调用MainUIController的onBattle方法
+        // 而是通过事件系统通知MainUIController执行战斗流程
+        // 这样可以保持代码的解耦
+    }
+
+    /**
+     * 游戏开始时的处理逻辑
+     */
+    private onGameStart(): void {
+        console.log('[NewbieGuideManager] 游戏开始,检查新手引导状态');
+        
+        if (this.isNewUser() && !this.isInProgress) {
+            console.log('[NewbieGuideManager] 检测到新用户,但新手引导未启动,这可能是正常游戏流程');
+        }
+    }
+
+    /**
+     * 获取新手引导进度
+     * 可以用于显示引导进度或判断引导阶段
+     */
+    public getNewbieGuideProgress(): number {
+        if (!this.isInProgress) {
+            return this.isNewPlayer() ? 0 : 100;
+        }
+        
+        // 这里可以根据具体的引导步骤来计算进度
+        // 暂时返回50表示正在进行中
+        return 50;
+    }
+}

+ 9 - 0
assets/scripts/Core/NewbieGuideManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "9ec9ac92-bbb6-4b8b-9a66-054d83d890e2",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 37 - 0
assets/scripts/FourUI/MainSystem/MainUIControlller.ts

@@ -10,6 +10,7 @@ import EventBus, { GameEvents } from '../../Core/EventBus';
 import { Audio } from '../../AudioManager/AudioManager';
 import { BundleLoader } from '../../Core/BundleLoader';
 import { ResourcePreloader } from '../../Core/ResourcePreloader';
+import { NewbieGuideManager } from '../../Core/NewbieGuideManager';
 const { ccclass, property } = _decorator;
 
 @ccclass('MainUIController')
@@ -49,11 +50,13 @@ export class MainUIController extends Component {
 
   private sdm: SaveDataManager = null;
   private wallConfig: any = null;
+  private newbieGuideManager: NewbieGuideManager = null;
 
   async onLoad () {
     console.log('[MainUIController] onLoad 开始执行');
     
     this.sdm = SaveDataManager.getInstance();
+    this.newbieGuideManager = NewbieGuideManager.getInstance();
     await this.loadWallConfig();
     this.sdm.initialize();
     console.log('[MainUIController] SaveDataManager 初始化完成');
@@ -70,6 +73,9 @@ export class MainUIController extends Component {
     // 预加载当前关卡和下一关的资源
     this.preloadLevelResources();
     
+    // 检查并启动新手引导(如果是新用户)
+    this.newbieGuideManager.checkAndStartNewbieGuideOnSceneLoad();
+    
     console.log('[MainUIController] onLoad 执行完成');
   }
 
@@ -99,6 +105,10 @@ export class MainUIController extends Component {
       console.error('[MainUIController] battleBtn未设置,无法绑定战斗事件');
     }
     
+    // 监听新手引导的自动战斗事件
+    EventBus.getInstance().on('NEWBIE_GUIDE_BATTLE_START', this.onNewbieGuideBattle, this);
+    console.log('[MainUIController] 新手引导战斗事件已绑定');
+    
     console.log('[MainUIController] bindButtons 执行完成');
   }
 
@@ -227,6 +237,31 @@ export class MainUIController extends Component {
   }
 
   private onBattle () {
+    console.log('[MainUIController] onBattle 被调用');
+    this.executeBattleFlow();
+  }
+
+  /**
+   * 新手引导自动触发战斗流程
+   */
+  private onNewbieGuideBattle() {
+    console.log('[MainUIController] 新手引导自动触发战斗流程');
+    this.executeBattleFlow();
+  }
+
+  /**
+   * 执行战斗流程的核心逻辑
+   * 无论是玩家点击战斗按钮还是新手引导自动触发,都使用相同的流程
+   */
+  private executeBattleFlow() {
+    console.log('[MainUIController] 执行战斗流程开始');
+    
+    // 检查是否在新手引导中
+    const isNewbieGuide = this.newbieGuideManager?.isNewbieGuideInProgress() || false;
+    if (isNewbieGuide) {
+      console.log('[MainUIController] 当前处于新手引导模式');
+    }
+    
     // 停止主界面背景音乐,避免与游戏内音效冲突
     Audio.stopMusic();
     // 播放战斗背景音乐,音量较小
@@ -262,6 +297,8 @@ export class MainUIController extends Component {
     gm?.loadCurrentLevelConfig();
 
     // 镜头下移动画现在已集成到StartGame流程中的slideUpFromBottom方法里
+    
+    console.log('[MainUIController] 执行战斗流程完成');
   }
 
 

+ 105 - 0
assets/scripts/Guide/DragBlockToGridStep.ts

@@ -0,0 +1,105 @@
+import { find, Node } from 'cc';
+import { GuideStep } from '../../NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideStep';
+import { BlockManager } from '../CombatSystem/BlockManager';
+import EventBus, { GameEvents } from '../Core/EventBus';
+
+/**
+ * 新手引导步骤:提示并等待玩家将任意方块拖到网格
+ * 触发条件:场景中存在方块选择区域,且尚未放置任何方块
+ * 完成条件:BlockManager 检测到至少一个方块已移动到 PlacedBlocks
+ */
+export class DragBlockToGridStep extends GuideStep {
+  private _blockManager: BlockManager | null = null;
+  private _pollTimer: any = null;
+  private _dragEventsReady = false;
+  private _unsubscribers: Array<() => void> = [];
+
+  constructor() {
+    super('drag_block_to_grid');
+  }
+
+  public canTrigger(): boolean {
+    // 查找 BlockManager(位于方块选择系统下)
+    const root = find('Canvas');
+    if (!root) return false;
+
+    // 在整棵树中查找 BlockManager 组件
+    const nodes: Node[] = [];
+    const stack: Node[] = [root];
+    while (stack.length) {
+      const n = stack.pop()!;
+      nodes.push(n);
+      for (const child of n.children) stack.push(child);
+    }
+
+    for (const n of nodes) {
+      const bm = n.getComponent(BlockManager as any) as BlockManager | null;
+      if (bm) {
+        this._blockManager = bm;
+        break;
+      }
+    }
+
+    if (!this._blockManager) return false;
+
+    // 如果已经有方块被放置则不触发
+    const hasPlaced = typeof (this._blockManager as any).hasPlacedBlocks === 'function'
+      ? (this._blockManager as any).hasPlacedBlocks()
+      : false;
+    if (hasPlaced) return false;
+
+    // 确认方块选择 UI 存在(避免在战斗中或加载前触发)
+    const selectionUI = find('Canvas/GameLevelUI/BlockSelectionUI');
+    return !!selectionUI;
+  }
+
+  public doExecute(): void {
+    // 监听拖拽相关事件,便于在拖拽区域就绪时给出提示或开始轮询
+    const bus = EventBus.getInstance();
+
+    const onSetup = () => {
+      this._dragEventsReady = true;
+    };
+    bus.on(GameEvents.SETUP_BLOCK_DRAG_EVENTS, onSetup, this);
+    this._unsubscribers.push(() => bus.off(GameEvents.SETUP_BLOCK_DRAG_EVENTS, onSetup, this));
+
+    const onDragEnd = () => {
+      // 尝试立即检测一次是否已经放置成功
+      this.checkPlacedAndComplete();
+    };
+    bus.on(GameEvents.BLOCK_DRAG_END, onDragEnd, this);
+    this._unsubscribers.push(() => bus.off(GameEvents.BLOCK_DRAG_END, onDragEnd, this));
+
+    // 启动轮询,容错检测放置完成(避免事件遗漏)
+    this._pollTimer = setInterval(() => this.checkPlacedAndComplete(), 500);
+  }
+
+  private checkPlacedAndComplete() {
+    if (!this._blockManager) return;
+    try {
+      const hasPlaced = typeof (this._blockManager as any).hasPlacedBlocks === 'function'
+        ? (this._blockManager as any).hasPlacedBlocks()
+        : false;
+      if (hasPlaced) {
+        this.complete();
+      }
+    } catch (e) {
+      console.error('[DragBlockToGridStep] 检测放置状态失败:', e);
+    }
+  }
+
+  protected onComplete(): void {
+    // 清理事件与轮询
+    for (const unsub of this._unsubscribers) {
+      try { unsub(); } catch {}
+    }
+    this._unsubscribers = [];
+    if (this._pollTimer) {
+      clearInterval(this._pollTimer);
+      this._pollTimer = null;
+    }
+    // 记录由基类完成 complete() 统一处理
+  }
+}
+
+export default DragBlockToGridStep;

+ 9 - 0
assets/scripts/Guide/DragBlockToGridStep.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "da4ee23c-a429-4397-b949-daa07c5bc122",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 14 - 1
assets/scripts/LevelSystem/GameManager.ts

@@ -14,6 +14,8 @@ import { Wall } from '../CombatSystem/Wall';
 import { GameStartMove } from '../Animations/GameStartMove';
 import { StartGame } from './StartGame';
 import { InGameManager, GameState } from './IN_game';
+import { GuideManager } from '../../NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideManager';
+import DragBlockToGridStep from '../Guide/DragBlockToGridStep';
 const { ccclass, property } = _decorator;
 
 /**
@@ -204,11 +206,22 @@ export class GameManager extends Component {
         // GameBlockSelection的确认回调将在游戏真正开始时设置,避免场景加载时的意外触发
         
         // 关卡配置加载已移至StartGame.initializeGameData()中,确保正确的时序
-        
+    
         // 监听GamePause状态变化事件
         this.setupGamePauseEventListeners();
         
         // 游戏启动流程将在用户点击战斗按钮时触发,而不是在场景加载时自动触发
+
+        // 初始化并启动新手引导(仅注册基础拖拽到网格步骤)
+        try {
+            const guideManager = GuideManager.getInstance();
+            guideManager.init();
+            guideManager.registerStep(new DragBlockToGridStep());
+            guideManager.startGuide();
+            console.log('[GameManager] 新手引导已初始化并启动');
+        } catch (e) {
+            console.warn('[GameManager] 新手引导初始化失败:', e);
+        }
     }
 
     /**

+ 10 - 2
assets/scripts/LevelSystem/SaveDataManager.ts

@@ -23,6 +23,7 @@ interface WeaponConfig {
  * 玩家数据结构
  */
 export interface PlayerData {
+    playerData: {};
     // 基本信息
     playerId: string;
     playerName: string;
@@ -122,6 +123,7 @@ export interface GameStatistics {
     totalShotsfired: number;
     totalDamageDealt: number;
     totalTimePlayed: number;
+    totalPlayTime: number;
     highestLevel: number;
     consecutiveWins: number;
     bestWinStreak: number;
@@ -142,6 +144,7 @@ export interface PlayerSettings {
     autoSaveEnabled: boolean;
     language: string;
     graphics: 'low' | 'medium' | 'high';
+    newbieGuideCompleted: boolean;
 }
 
 /**
@@ -299,6 +302,8 @@ export class SaveDataManager {
                 totalShotsfired: 0,
                 totalDamageDealt: 0,
                 totalTimePlayed: 0,
+                totalPlayTime: 0,
+                totalPlayTime: 0,
                 highestLevel: 1,
                 consecutiveWins: 0,
                 bestWinStreak: 0,
@@ -315,7 +320,8 @@ export class SaveDataManager {
                 vibrationEnabled: true,
                 autoSaveEnabled: true,
                 language: 'zh-CN',
-                graphics: 'medium'
+                graphics: 'medium',
+                newbieGuideCompleted: false
             }
         };
         
@@ -388,6 +394,7 @@ export class SaveDataManager {
                 totalShotsfired: 0,
                 totalDamageDealt: 0,
                 totalTimePlayed: 0,
+                totalPlayTime: 0,
                 highestLevel: 1,
                 consecutiveWins: 0,
                 bestWinStreak: 0,
@@ -403,7 +410,8 @@ export class SaveDataManager {
                 vibrationEnabled: true,
                 autoSaveEnabled: true,
                 language: 'zh-CN',
-                graphics: 'medium'
+                graphics: 'medium',
+                newbieGuideCompleted: false
             }
         };
     }

Some files were not shown because too many files changed in this diff