فهرست منبع

解释文档以及美术优化

181404010226 3 ماه پیش
والد
کامیت
1fd984b32b

+ 7 - 4
assets/Scenes/GameLevel.scene

@@ -12992,6 +12992,9 @@
     "positionHistorySize": 20,
     "oscillationDistanceThreshold": 100,
     "testMode": true,
+    "enableBallPositionCheck": true,
+    "positionCheckInterval": 2,
+    "positionCheckBoundaryExtension": 50,
     "bulletContainerPrefab": {
       "__uuid__": "b67bcc20-b46f-4cd9-9020-820246f608d6",
       "__expectedType__": "cc.Prefab"
@@ -26842,7 +26845,7 @@
     "_lpos": {
       "__type__": "cc.Vec3",
       "x": 0,
-      "y": -15,
+      "y": 0,
       "z": 0
     },
     "_lrot": {
@@ -27257,7 +27260,7 @@
     "_lpos": {
       "__type__": "cc.Vec3",
       "x": 0,
-      "y": 25,
+      "y": 0,
       "z": 0
     },
     "_lrot": {
@@ -27371,7 +27374,7 @@
     },
     "_enabled": true,
     "__prefab": null,
-    "_alignFlags": 17,
+    "_alignFlags": 18,
     "_target": null,
     "_left": -25,
     "_right": 0,
@@ -27507,7 +27510,7 @@
     },
     "_enabled": true,
     "__prefab": null,
-    "_alignFlags": 17,
+    "_alignFlags": 18,
     "_target": null,
     "_left": 0,
     "_right": 0,

+ 9 - 12
assets/assets/Prefabs/Enemy.prefab

@@ -283,10 +283,7 @@
       "b": 240,
       "a": 255
     },
-    "_spriteFrame": {
-      "__uuid__": "45bd99da-8893-435b-8110-5da90043375b@f9941",
-      "__expectedType__": "cc.SpriteFrame"
-    },
+    "_spriteFrame": null,
     "_type": 1,
     "_fillType": 0,
     "_sizeMode": 0,
@@ -364,8 +361,8 @@
     "_dstBlendFactor": 4,
     "_color": {
       "__type__": "cc.Color",
-      "r": 0,
-      "g": 0,
+      "r": 255,
+      "g": 255,
       "b": 0,
       "a": 255
     },
@@ -510,7 +507,7 @@
     },
     "_lpos": {
       "__type__": "cc.Vec3",
-      "x": -26.901,
+      "x": -24.148,
       "y": 0,
       "z": 0
     },
@@ -551,8 +548,8 @@
     },
     "_contentSize": {
       "__type__": "cc.Size",
-      "width": 53,
-      "height": 15
+      "width": 50,
+      "height": 8
     },
     "_anchorPoint": {
       "__type__": "cc.Vec2",
@@ -637,8 +634,8 @@
     },
     "_contentSize": {
       "__type__": "cc.Size",
-      "width": 51,
-      "height": 13
+      "width": 50,
+      "height": 8
     },
     "_anchorPoint": {
       "__type__": "cc.Vec2",
@@ -709,7 +706,7 @@
       "__id__": 21
     },
     "_mode": 0,
-    "_totalLength": 53,
+    "_totalLength": 50,
     "_progress": 1,
     "_reverse": false,
     "_id": ""

+ 11 - 0
assets/resources/data/backups/levels/Level1_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "2677b093-4839-4a24-925d-71040c5e37a2",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level2_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "90fb9f32-7317-49f6-8873-16fdf7287081",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level3_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "8475661a-fc8b-49aa-a8dd-aa9b3b3ded9e",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level4_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "11e6c2c5-2271-4b25-9da5-bd1447e87978",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level5_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "eb16dfa2-d1be-4095-83d2-d38efd1ffc20",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level6_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "9d9e1535-2ce9-465d-8095-65659d15071e",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level7_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "c7cfadc1-5ddb-48de-a27d-86355562bac4",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level8_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "762a5a7c-124b-4715-98e4-35e61f4f62cc",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/backups/levels/Level9_20250904_185450.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "82cb8e48-2200-4259-adff-16b28f00b701",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 11 - 0
assets/resources/data/enemies_backup_20250904_185518.json.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "2.0.1",
+  "importer": "json",
+  "imported": true,
+  "uuid": "6fdfed7b-9016-476e-be8d-f6a55e0a9b1b",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 2 - 2
assets/resources/prefabs/Upgrade/Lock.prefab

@@ -444,7 +444,7 @@
     },
     "_contentSize": {
       "__type__": "cc.Size",
-      "width": 176.4599609375,
+      "width": 177.2470703125,
       "height": 38.76
     },
     "_anchorPoint": {
@@ -485,7 +485,7 @@
     "_verticalAlign": 1,
     "_actualFontSize": 26,
     "_fontSize": 26,
-    "_fontFamily": "Arial",
+    "_fontFamily": "Adobe 黑体 Std",
     "_lineHeight": 0,
     "_overflow": 0,
     "_enableWrapText": true,

+ 4 - 4
assets/resources/prefabs/Upgrade/Unlock.prefab

@@ -234,7 +234,7 @@
     "_verticalAlign": 1,
     "_actualFontSize": 26,
     "_fontSize": 26,
-    "_fontFamily": "Arial",
+    "_fontFamily": "Adobe 黑体 Std",
     "_lineHeight": 0,
     "_overflow": 0,
     "_enableWrapText": true,
@@ -1083,7 +1083,7 @@
     },
     "_contentSize": {
       "__type__": "cc.Size",
-      "width": 103.734375,
+      "width": 86.7734375,
       "height": 36.76
     },
     "_anchorPoint": {
@@ -1119,12 +1119,12 @@
       "b": 255,
       "a": 255
     },
-    "_string": "Upgrade",
+    "_string": "升    级",
     "_horizontalAlign": 1,
     "_verticalAlign": 1,
     "_actualFontSize": 26,
     "_fontSize": 26,
-    "_fontFamily": "Arial",
+    "_fontFamily": "Adobe 黑体 Std",
     "_lineHeight": 0,
     "_overflow": 0,
     "_enableWrapText": true,

+ 275 - 25
assets/scripts/CombatSystem/BallController.ts

@@ -22,7 +22,7 @@ export class BallController extends Component {
     // 已放置方块容器节点
     @property({
         type: Node,
-        tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/PlacedBlocks)'
+        tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/GameArea/PlacedBlocks)'
     })
     public placedBlocksContainer: Node = null;
 
@@ -116,6 +116,22 @@ export class BallController extends Component {
     })
     public testMode: boolean = true;
 
+    // 小球位置检查配置
+    @property({
+        tooltip: '启用小球位置检查:定期检查小球是否在游戏区域内,如果不在则找回'
+    })
+    public enableBallPositionCheck: boolean = true;
+    
+    @property({
+        tooltip: '位置检查间隔(秒):多久检查一次小球位置'
+    })
+    public positionCheckInterval: number = 2.0;
+    
+    @property({
+        tooltip: '位置检查边界扩展(像素):检查边界比游戏区域稍大,避免误判'
+    })
+    public positionCheckBoundaryExtension: number = 50;
+
     // 防围困机制状态
     private ballHitHistory: Map<string, number[]> = new Map(); // 记录每个球的撞击时间历史
     private ballPhaseThrough: Map<string, number> = new Map(); // 记录每个球的穿透结束时间
@@ -141,10 +157,13 @@ export class BallController extends Component {
     // 正在被拖拽的方块集合
     private draggingBlocks: Set<Node> = new Set();
 
+    // 小球位置检查相关变量
+    private lastPositionCheckTime: number = 0; // 上次位置检查的时间
+
     start() {
         // 如果没有指定placedBlocksContainer,尝试找到它
         if (!this.placedBlocksContainer) {
-            this.placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
+            this.placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
             if (!this.placedBlocksContainer) {
                 // 找不到PlacedBlocks节点,某些功能可能无法正常工作
             }
@@ -666,14 +685,10 @@ export class BallController extends Component {
         
         // 查找PlacedBlocks节点,它包含所有放置的方块
         if (!this.placedBlocksContainer) {
-            this.setRandomPositionDefault(minX, maxX, minY, maxY);
-            return;
-        }
-        
-        if (!this.placedBlocksContainer.isValid) {
-            this.setRandomPositionDefault(minX, maxX, minY, maxY);
+            this.setRandomPositionDefault(this.activeBall, minX, maxX, minY, maxY);
             return;
         }
+    
         
         // 获取所有已放置的方块
         const placedBlocks = [];
@@ -687,7 +702,7 @@ export class BallController extends Component {
         
         // 如果没有方块,使用默认随机位置
         if (placedBlocks.length === 0) {
-            this.setRandomPositionDefault(minX, maxX, minY, maxY);
+            this.setRandomPositionDefault(this.activeBall, minX, maxX, minY, maxY);
             return;
         }
         
@@ -745,22 +760,6 @@ export class BallController extends Component {
         this.activeBall.position = localPos;
     }
 
-    // 设置默认随机位置
-    setRandomPositionDefault(minX, maxX, minY, maxY) {
-        // 随机生成位置
-        const randomX = Math.random() * (maxX - minX) + minX;
-        const randomY = Math.random() * (maxY - minY) + minY;
-
-        // 将世界坐标转换为相对于GameArea的本地坐标
-        const gameArea = find('Canvas/GameLevelUI/GameArea');
-        if (gameArea) {
-            const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
-            this.activeBall.position = localPos;
-        } else {
-            // 直接设置位置(不太准确,但作为后备)
-            this.activeBall.position = new Vec3(randomX - this.gameBounds.left, randomY - this.gameBounds.bottom, 0);
-        }
-    }
 
     // 设置碰撞组件
     setupCollider() {
@@ -1443,6 +1442,13 @@ export class BallController extends Component {
         // 清理过期的防围困状态
         this.cleanupExpiredAntiTrapStates();
         
+        // 定期检查小球位置,防止小球被挤出游戏区域
+        this.lastPositionCheckTime += dt;
+        if (this.lastPositionCheckTime >= this.positionCheckInterval) {
+            this.checkAndRescueAllBalls();
+            this.lastPositionCheckTime = 0;
+        }
+        
         // 定期检查小球是否接近方块但没有触发碰撞(调试用)
         if (this.activeBall && this.activeBall.isValid) {
             this.debugCheckNearBlocks();
@@ -2043,4 +2049,248 @@ export class BallController extends Component {
             this.currentSpeed = this.baseSpeed;
         }
     }
+
+    // ==================== 小球位置检查机制 ====================
+    
+    /**
+     * 获取所有活跃的小球
+     * @returns 返回所有活跃小球的数组
+     */
+    private getAllActiveBalls(): Node[] {
+        const balls: Node[] = [];
+        
+        // 添加主小球
+        if (this.activeBall && this.activeBall.isValid) {
+            balls.push(this.activeBall);
+        }
+        
+        // 查找所有额外小球
+        const gameArea = find('Canvas/GameLevelUI/GameArea');
+        if (gameArea) {
+            // 遍历GameArea的所有子节点,查找小球
+            gameArea.children.forEach(child => {
+                if (child.name === 'AdditionalBall' && child.isValid) {
+                    balls.push(child);
+                }
+            });
+        }
+        
+        return balls;
+    }
+    
+    /**
+     * 检查单个小球是否在游戏区域内
+     * @param ball 要检查的小球节点
+     * @returns 如果小球在游戏区域内返回true,否则返回false
+     */
+    private checkBallPosition(ball: Node): boolean {
+        if (!ball || !ball.isValid) {
+            return false;
+        }
+        
+        // 获取小球的世界坐标
+        const worldPos = ball.getWorldPosition();
+        
+        // 计算扩展后的边界(比游戏区域稍大,避免误判)
+        const extension = this.positionCheckBoundaryExtension;
+        const extendedBounds = {
+            left: this.gameBounds.left - extension,
+            right: this.gameBounds.right + extension,
+            top: this.gameBounds.top + extension,
+            bottom: this.gameBounds.bottom - extension
+        };
+        
+        // 检查小球是否在扩展边界内
+        const isInBounds = worldPos.x >= extendedBounds.left && 
+                          worldPos.x <= extendedBounds.right && 
+                          worldPos.y >= extendedBounds.bottom && 
+                          worldPos.y <= extendedBounds.top;
+        
+        if (!isInBounds) {
+            console.warn(`[BallController] 小球 ${ball.name} 超出游戏区域:`, {
+                position: { x: worldPos.x, y: worldPos.y },
+                bounds: extendedBounds
+            });
+        }
+        
+        return isInBounds;
+    }
+    
+    /**
+     * 将小球重置回游戏区域
+     * @param ball 要重置的小球节点
+     */
+    private rescueBallToGameArea(ball: Node): void {
+        if (!ball || !ball.isValid) {
+            return;
+        }
+        
+        console.log(`[BallController] 正在找回小球 ${ball.name}`);
+        
+        // 使用与小球生成相同的安全位置设置逻辑
+        this.positionBallSafely(ball);
+        
+        // 重新设置小球的运动方向和速度
+        const rigidBody = ball.getComponent(RigidBody2D);
+        if (rigidBody) {
+            // 随机新的运动方向
+            const angle = Math.random() * Math.PI * 2;
+            const direction = new Vec2(Math.cos(angle), Math.sin(angle)).normalize();
+            
+            // 设置新的速度
+            rigidBody.linearVelocity = new Vec2(
+                direction.x * this.currentSpeed,
+                direction.y * this.currentSpeed
+            );
+        }
+        
+        console.log(`[BallController] 小球 ${ball.name} 已重置到安全位置`);
+    }
+    
+    /**
+     * 为小球设置安全位置(复用生成位置逻辑)
+     * @param ball 要设置位置的小球节点
+     */
+    private positionBallSafely(ball: Node): void {
+        if (!ball) return;
+
+        const transform = ball.getComponent(UITransform);
+        const ballRadius = transform ? transform.width / 2 : this.ballRadius;
+        
+        // 计算可生成的范围(考虑小球半径,避免生成在边缘)
+        const minX = this.gameBounds.left + ballRadius + this.edgeOffset;
+        const maxX = this.gameBounds.right - ballRadius - this.edgeOffset;
+        const minY = this.gameBounds.bottom + ballRadius + this.edgeOffset;
+        const maxY = this.gameBounds.top - ballRadius - this.edgeOffset;
+
+        // 获取GameArea节点
+        const gameArea = find('Canvas/GameLevelUI/GameArea');
+        if (!gameArea) {
+            return;
+        }
+        
+        // 查找PlacedBlocks节点,它包含所有放置的方块
+        if (!this.placedBlocksContainer) {
+            this.setRandomPositionDefault(ball, minX, maxX, minY, maxY);
+            return;
+        }
+        
+        if (!this.placedBlocksContainer.isValid) {
+            this.setRandomPositionDefault(ball, minX, maxX, minY, maxY);
+            return;
+        }
+        
+        // 获取所有已放置的方块
+        const placedBlocks = [];
+        for (let i = 0; i < this.placedBlocksContainer.children.length; i++) {
+            const block = this.placedBlocksContainer.children[i];
+            // 检查是否是方块节点(通常以Block命名或有特定标识)
+            if (block.name.includes('Block') || block.getChildByName('B1')) {
+                placedBlocks.push(block);
+            }
+        }
+        
+        // 如果没有方块,使用默认随机位置
+        if (placedBlocks.length === 0) {
+            this.setRandomPositionDefault(ball, minX, maxX, minY, maxY);
+            return;
+        }
+        
+        // 尝试找到一个不与任何方块重叠的位置
+        let validPosition = false;
+        let attempts = 0;
+        const maxAttempts = this.maxAttempts;
+        let randomX, randomY;
+        
+        while (!validPosition && attempts < maxAttempts) {
+            // 随机生成位置
+            randomX = Math.random() * (maxX - minX) + minX;
+            randomY = Math.random() * (maxY - minY) + minY;
+            
+            // 检查是否与任何方块重叠
+            let overlapping = false;
+            for (const block of placedBlocks) {
+                // 获取方块的世界坐标
+                const blockWorldPos = block.worldPosition;
+                
+                // 计算小球与方块的距离
+                const distance = Math.sqrt(
+                    Math.pow(randomX - blockWorldPos.x, 2) + 
+                    Math.pow(randomY - blockWorldPos.y, 2)
+                );
+                
+                // 获取方块的尺寸
+                const blockTransform = block.getComponent(UITransform);
+                const blockSize = blockTransform ? 
+                    Math.max(blockTransform.width, blockTransform.height) / 2 : this.safeDistance;
+                
+                // 如果距离小于小球半径+方块尺寸的一半+安全距离,认为重叠
+                if (distance < ballRadius + blockSize + this.safeDistance) {
+                    overlapping = true;
+                    break;
+                }
+            }
+            
+            // 如果没有重叠,找到了有效位置
+            if (!overlapping) {
+                validPosition = true;
+            }
+            
+            attempts++;
+        }
+        
+        // 如果找不到有效位置,使用默认位置(游戏区域底部中心)
+        if (!validPosition) {
+            randomX = (this.gameBounds.left + this.gameBounds.right) / 2;
+            randomY = this.gameBounds.bottom + ballRadius + this.safeDistance;
+        }
+        
+        // 将世界坐标转换为相对于GameArea的本地坐标
+        const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
+        ball.position = localPos;
+    }
+    
+    /**
+     * 设置小球默认位置
+     * @param ball 小球节点
+     * @param minX 最小X坐标
+     * @param maxX 最大X坐标
+     * @param minY 最小Y坐标
+     * @param maxY 最大Y坐标
+     */
+    private setRandomPositionDefault(ball: Node, minX: number, maxX: number, minY: number, maxY: number): void {
+        const gameArea = find('Canvas/GameLevelUI/GameArea');
+        if (!gameArea) return;
+        
+        // 随机生成位置
+        const randomX = Math.random() * (maxX - minX) + minX;
+        const randomY = Math.random() * (maxY - minY) + minY;
+        
+        // 将世界坐标转换为相对于GameArea的本地坐标
+        const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
+        ball.position = localPos;
+    }
+    
+    /**
+     * 检查并找回所有超出游戏区域的小球
+     */
+    private checkAndRescueAllBalls(): void {
+        if (!this.enableBallPositionCheck) {
+            return;
+        }
+        
+        const balls = this.getAllActiveBalls();
+        let rescuedCount = 0;
+        
+        balls.forEach(ball => {
+            if (!this.checkBallPosition(ball)) {
+                this.rescueBallToGameArea(ball);
+                rescuedCount++;
+            }
+        });
+        
+        if (rescuedCount > 0) {
+            console.log(`[BallController] 本次检查找回了 ${rescuedCount} 个小球`);
+        }
+    }
 }

+ 3 - 3
assets/scripts/CombatSystem/BlockManager.ts

@@ -100,7 +100,7 @@ export class BlockManager extends Component {
     // 已放置方块容器节点
     @property({
         type: Node,
-        tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/PlacedBlocks)'
+        tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/GameArea/PlacedBlocks)'
     })
     public placedBlocksContainer: Node = null;
     
@@ -355,7 +355,7 @@ export class BlockManager extends Component {
         
         // 如果没有指定placedBlocksContainer,尝试找到它
         if (!this.placedBlocksContainer) {
-            this.placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');   
+            this.placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');   
         }
         
         // 如果没有指定价格节点,尝试找到它们
@@ -416,7 +416,7 @@ export class BlockManager extends Component {
         }
         
         // 尝试查找节点
-        this.placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
+        this.placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
         if (this.placedBlocksContainer) {
             return;
         }

+ 3 - 3
assets/scripts/CombatSystem/BlockSelection/GameBlockSelection.ts

@@ -328,7 +328,7 @@ export class GameBlockSelection extends Component {
                 console.log('[GameBlockSelection] 开始刷新方块流程');
                 
                 // 找到PlacedBlocks容器
-                const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
+                const placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
                 if (placedBlocksContainer) {
                     // 移除已放置方块的标签
                     // 移除已放置方块的标签
@@ -481,7 +481,7 @@ export class GameBlockSelection extends Component {
              this.blockManager.resetGridOccupation();
             
             // 重新计算所有已放置方块的占用情况
-            const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
+            const placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
             if (placedBlocksContainer) {
                 for (let i = 0; i < placedBlocksContainer.children.length; i++) {
                     const block = placedBlocksContainer.children[i];
@@ -539,7 +539,7 @@ export class GameBlockSelection extends Component {
         }
         
         // 备用方案:直接检查PlacedBlocks容器
-        const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
+        const placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
         if (!placedBlocksContainer) {
             console.warn('找不到PlacedBlocks容器');
             return false;

+ 12 - 2
assets/scripts/CombatSystem/GameEnd.ts

@@ -399,8 +399,18 @@ export class GameEnd extends Component {
         
         try {
             if (this.isGameSuccess) {
-                // 游戏成功,给予完整奖励
-                console.log('[GameEnd] 准备给予成功奖励');
+                // 游戏成功,先完成关卡(更新maxUnlockedLevel),再给予奖励
+                console.log('[GameEnd] 准备完成关卡并给予成功奖励');
+                
+                // 获取游戏时长
+                const gameTime = this.inGameManager ? Math.floor(this.inGameManager.getGameDuration() / 1000) : 0;
+                console.log(`[GameEnd] 游戏时长: ${gameTime}秒`);
+                
+                // 先调用completeLevel更新maxUnlockedLevel,这样武器解锁机制才能正常工作
+                this.saveDataManager.completeLevel(currentLevel, 0, gameTime);
+                console.log('[GameEnd] 已完成关卡,maxUnlockedLevel已更新');
+                
+                // 然后给予奖励
                 await this.saveDataManager.giveCompletionRewards(currentLevel);
                 console.log('[GameEnd] 已给予成功奖励');
             } else {

+ 211 - 0
test_weapon_unlock_fix.js

@@ -0,0 +1,211 @@
+/**
+ * 武器解锁修复验证脚本
+ * 用于测试关卡完成后武器是否正确解锁
+ */
+
+console.log('=== 武器解锁修复验证脚本 ===');
+
+// 获取必要的管理器实例
+function getManagers() {
+    const gameManagerNode = cc.find('GameManager');
+    const gameManager = gameManagerNode ? gameManagerNode.getComponent('GameManager') : null;
+    
+    const saveDataManager = window.SaveDataManager ? window.SaveDataManager.getInstance() : null;
+    
+    const upgradeControllerNode = cc.find('Canvas/UpgradeUI');
+    const upgradeController = upgradeControllerNode ? upgradeControllerNode.getComponent('UpgradeController') : null;
+    
+    return { gameManager, saveDataManager, upgradeController };
+}
+
+// 检查当前状态
+function checkCurrentStatus() {
+    console.log('\n=== 当前状态检查 ===');
+    
+    const { saveDataManager, upgradeController } = getManagers();
+    
+    if (!saveDataManager) {
+        console.error('SaveDataManager未找到');
+        return;
+    }
+    
+    const currentLevel = saveDataManager.getCurrentLevel();
+    const maxUnlockedLevel = saveDataManager.getMaxUnlockedLevel();
+    const money = saveDataManager.getMoney();
+    const diamonds = saveDataManager.getDiamonds();
+    
+    console.log(`当前关卡: ${currentLevel}`);
+    console.log(`最大解锁关卡: ${maxUnlockedLevel}`);
+    console.log(`金钱: ${money}`);
+    console.log(`钻石: ${diamonds}`);
+    
+    // 检查武器解锁状态
+    if (upgradeController && upgradeController.getWeaponUnlockStatus) {
+        const weaponStatus = upgradeController.getWeaponUnlockStatus();
+        console.log('\n=== 武器解锁状态 ===');
+        console.log(`总武器数: ${weaponStatus.summary.total}`);
+        console.log(`已解锁: ${weaponStatus.summary.unlocked}`);
+        console.log(`应该解锁: ${weaponStatus.summary.shouldBeUnlocked}`);
+        console.log(`需要更新: ${weaponStatus.summary.needsUpdate}`);
+        
+        if (weaponStatus.summary.needsUpdate > 0) {
+            console.warn('⚠️ 发现武器解锁状态不一致!');
+            weaponStatus.weapons.forEach(weapon => {
+                if (weapon.needsUpdate) {
+                    console.warn(`  - ${weapon.name}: 应该解锁但未解锁 (需要关卡${weapon.requiredLevel})`);
+                }
+            });
+        } else {
+            console.log('✅ 武器解锁状态正常');
+        }
+    }
+}
+
+// 模拟关卡完成测试
+function simulateLevelComplete(targetLevel) {
+    console.log(`\n=== 模拟关卡${targetLevel}完成测试 ===`);
+    
+    const { saveDataManager, upgradeController } = getManagers();
+    
+    if (!saveDataManager) {
+        console.error('SaveDataManager未找到');
+        return;
+    }
+    
+    // 记录修复前状态
+    const beforeMaxLevel = saveDataManager.getMaxUnlockedLevel();
+    console.log(`修复前最大解锁关卡: ${beforeMaxLevel}`);
+    
+    // 模拟GameEnd.ts中的修复逻辑
+    console.log('执行修复后的关卡完成逻辑...');
+    
+    // 1. 先调用completeLevel更新maxUnlockedLevel
+    saveDataManager.completeLevel(targetLevel, 0, 60); // 模拟60秒游戏时长
+    console.log('✅ 已调用completeLevel更新maxUnlockedLevel');
+    
+    // 2. 然后给予奖励
+    saveDataManager.giveCompletionRewards(targetLevel).then(() => {
+        console.log('✅ 已给予完成奖励');
+        
+        // 3. 检查maxUnlockedLevel是否正确更新
+        const afterMaxLevel = saveDataManager.getMaxUnlockedLevel();
+        console.log(`修复后最大解锁关卡: ${afterMaxLevel}`);
+        
+        if (afterMaxLevel > beforeMaxLevel) {
+            console.log('✅ maxUnlockedLevel正确更新');
+            
+            // 4. 触发GAME_SUCCESS事件,让UpgradeController检查武器解锁
+            console.log('触发GAME_SUCCESS事件...');
+            const EventBus = window.EventBus || cc.EventBus;
+            if (EventBus) {
+                EventBus.getInstance().emit('GAME_SUCCESS');
+                console.log('✅ 已触发GAME_SUCCESS事件');
+                
+                // 5. 检查武器是否正确解锁
+                setTimeout(() => {
+                    if (upgradeController && upgradeController.getWeaponUnlockStatus) {
+                        const weaponStatus = upgradeController.getWeaponUnlockStatus();
+                        console.log('\n=== 武器解锁结果检查 ===');
+                        
+                        const newlyUnlocked = weaponStatus.weapons.filter(w => 
+                            w.requiredLevel <= afterMaxLevel && w.isCurrentlyUnlocked
+                        );
+                        
+                        console.log(`应该解锁的武器数量: ${weaponStatus.summary.shouldBeUnlocked}`);
+                        console.log(`实际解锁的武器数量: ${weaponStatus.summary.unlocked}`);
+                        
+                        if (weaponStatus.summary.needsUpdate === 0) {
+                            console.log('🎉 武器解锁修复成功!所有武器状态正确');
+                        } else {
+                            console.error('❌ 武器解锁仍有问题,需要进一步检查');
+                        }
+                        
+                        // 显示详细的武器状态
+                        weaponStatus.weapons.forEach(weapon => {
+                            const status = weapon.isCurrentlyUnlocked ? '✅已解锁' : 
+                                          weapon.shouldBeUnlocked ? '❌应解锁' : '⏳未达成';
+                            console.log(`  ${weapon.name}: ${status} (需要关卡${weapon.requiredLevel})`);
+                        });
+                    }
+                }, 100);
+            } else {
+                console.error('EventBus未找到,无法触发事件');
+            }
+        } else {
+            console.error('❌ maxUnlockedLevel未正确更新');
+        }
+    }).catch(error => {
+        console.error('给予奖励时出错:', error);
+    });
+}
+
+// 强制同步武器解锁状态
+function forceSyncWeapons() {
+    console.log('\n=== 强制同步武器解锁状态 ===');
+    
+    const { upgradeController } = getManagers();
+    
+    if (upgradeController && upgradeController.forceSyncWeaponUnlocks) {
+        const result = upgradeController.forceSyncWeaponUnlocks();
+        if (result) {
+            console.log('✅ 武器解锁状态强制同步完成');
+        } else {
+            console.error('❌ 武器解锁状态同步失败');
+        }
+    } else {
+        console.error('UpgradeController或forceSyncWeaponUnlocks方法未找到');
+    }
+}
+
+// 诊断武器解锁问题
+function diagnoseWeaponUnlock() {
+    console.log('\n=== 武器解锁问题诊断 ===');
+    
+    const { upgradeController } = getManagers();
+    
+    if (upgradeController && upgradeController.diagnoseWeaponUnlockIssue) {
+        upgradeController.diagnoseWeaponUnlockIssue();
+    } else {
+        console.error('UpgradeController或diagnoseWeaponUnlockIssue方法未找到');
+    }
+}
+
+// 主测试函数
+function runWeaponUnlockTest() {
+    console.log('开始武器解锁修复验证测试...');
+    
+    // 1. 检查当前状态
+    checkCurrentStatus();
+    
+    // 2. 诊断问题
+    diagnoseWeaponUnlock();
+    
+    // 3. 模拟关卡完成(测试关卡2,应该解锁尖胡萝卜)
+    setTimeout(() => {
+        simulateLevelComplete(2);
+    }, 1000);
+}
+
+// 导出测试函数到全局
+window.weaponUnlockTest = {
+    runTest: runWeaponUnlockTest,
+    checkStatus: checkCurrentStatus,
+    simulateComplete: simulateLevelComplete,
+    forceSync: forceSyncWeapons,
+    diagnose: diagnoseWeaponUnlock
+};
+
+console.log('\n=== 测试函数已准备就绪 ===');
+console.log('使用方法:');
+console.log('1. window.weaponUnlockTest.runTest() - 运行完整测试');
+console.log('2. window.weaponUnlockTest.checkStatus() - 检查当前状态');
+console.log('3. window.weaponUnlockTest.simulateComplete(2) - 模拟完成关卡2');
+console.log('4. window.weaponUnlockTest.forceSync() - 强制同步武器状态');
+console.log('5. window.weaponUnlockTest.diagnose() - 诊断武器解锁问题');
+
+// 自动运行测试
+if (typeof window !== 'undefined') {
+    setTimeout(() => {
+        runWeaponUnlockTest();
+    }, 2000);
+}

+ 163 - 0
游戏配置字段说明文档.md

@@ -0,0 +1,163 @@
+# 游戏配置字段说明文档
+
+本文档详细说明了游戏中各个配置表和JSON文件的字段作用,供策划参考。
+
+## 1. 方块武器配置表 (weapons.json)
+
+### 武器基础配置
+- **ID**: 武器的唯一标识符,用于代码中引用该武器
+- **名称**: 武器的显示名称,玩家在游戏中看到的名字
+- **类型**: 武器的攻击类型,决定武器的基本行为模式
+- **稀有度**: 武器的稀有等级,影响获取概率和属性强度
+- **权重**: 武器在随机生成时的权重值,数值越高出现概率越大
+- **伤害**: 武器造成的基础伤害值
+- **射速**: 武器的攻击频率,数值越高攻击越频繁
+- **射程**: 武器的攻击距离,超出此距离无法攻击
+- **子弹速度**: 发射物的移动速度
+- **子弹数量**: 单次攻击发射的子弹数量
+- **子弹轨迹类型**: 子弹的飞行路径类型(直线、弧线、追踪等)
+- **穿透数**: 子弹可以穿透的敌人数量,999表示无限穿透
+- **反弹次数**: 子弹碰撞后可以反弹的次数
+- **精灵路径**: 武器图标的资源路径
+- **音效路径**: 武器攻击时播放的音效文件路径
+
+### 武器升级费用配置
+- 配置武器在不同等级下的升级所需资源
+
+### 游戏内成本配置
+- 配置武器在游戏内购买或使用的成本
+
+### 方块形状配置
+- 配置武器对应的方块外观和形状属性
+
+## 2. 敌人配置表 (enemies.json)
+
+### 敌人基础配置
+- **敌人ID**: 敌人的唯一标识符
+- **敌人名称**: 敌人的显示名称
+- **敌人类型**: 敌人的分类,决定基本行为模式
+- **生命值**: 敌人的当前血量
+- **最大生命值**: 敌人的血量上限
+- **防御力**: 敌人的防御值,减少受到的伤害
+- **移动速度**: 敌人的移动速度
+- **掉落能量**: 击败敌人后获得的能量值
+
+### 战斗配置
+- **attackDamage**: 敌人的攻击伤害值
+- **attackRange**: 敌人的攻击范围
+- **attackSpeed**: 敌人的攻击频率
+- **canBlock**: 敌人是否具有格挡能力
+- **blockChance**: 格挡成功的概率
+- **attackCooldown**: 攻击间隔时间
+- **attackType**: 攻击类型(近战/远程)
+- **weaponType**: 敌人使用的武器类型
+- **projectileSpeed**: 远程攻击的发射物速度
+
+### 移动配置
+- **pattern**: 移动模式(直接移动、巡逻等)
+- **patrolRange**: 巡逻范围
+- **chaseRange**: 追击范围
+- **rotationSpeed**: 转向速度
+- **swingAmplitude**: 摆动幅度
+- **speedVariation**: 速度变化范围
+
+### 视觉配置
+- **spritePath**: 敌人精灵图片路径
+- **scale**: 缩放比例
+- **animationSpeed**: 动画播放速度
+- **animations**: 各种状态的动画名称配置
+
+## 3. 关卡配置表 (levels/*.json)
+
+### 关卡基础配置
+- **关卡ID**: 关卡的唯一标识符
+- **关卡名称**: 关卡的显示名称
+- **场景**: 关卡使用的场景类型
+- **描述**: 关卡的文字描述
+- **难度**: 关卡难度等级
+- **生命倍数**: 敌人血量的倍数修正
+- **钞票奖励**: 通关后获得的金币奖励
+- **钻石奖励**: 通关后获得的钻石奖励
+- **关卡背景图路径**: 关卡背景图片的资源路径
+- **初始金币**: 关卡开始时的初始金币数量
+- **能量最大值**: 关卡中能量的上限值
+
+### 关卡波次配置
+- **waveId**: 波次编号
+- **enemies**: 该波次出现的敌人配置
+- **enemyType**: 敌人类型
+- **count**: 敌人数量
+- **spawnInterval**: 敌人生成间隔
+- **spawnDelay**: 波次开始延迟
+
+### 关卡武器配置
+- 配置该关卡可使用的武器列表
+
+## 4. 技能配置表 (skill.json & skill_config.json)
+
+### 技能信息表
+- **技能ID**: 技能的唯一标识符
+- **技能名称**: 技能的显示名称
+- **技能描述**: 技能效果的文字说明
+- **图标路径**: 技能图标的资源路径
+- **最大等级**: 技能可升级的最高等级
+- **当前等级**: 技能的当前等级
+- **价格减少**: 降低购买成本的百分比
+- **暴击几率增加**: 提升暴击概率的数值
+- **暴击伤害加成**: 暴击时的伤害倍数
+- **生命值增加**: 增加生命值的百分比
+- **多重射击几率**: 触发多重射击的概率
+- **能量加成**: 增加能量获取的百分比
+- **速度提升**: 提升移动或攻击速度的百分比
+
+### 局外技能配置
+- **skillTypes**: 技能类型配置,定义不同类型技能的基本属性
+- **skillGroups**: 技能组配置,定义技能升级的成本和效果
+- **effectPercent**: 技能效果的百分比数值
+- **diamondCost**: 升级技能所需的钻石数量
+
+## 5. BallController配置 (ballController.json)
+
+- **baseSpeed**: 球的基础移动速度
+- **maxReflectionRandomness**: 反射时的最大随机性
+- **antiTrapTimeWindow**: 防卡死机制的时间窗口
+- **antiTrapHitThreshold**: 触发防卡死的碰撞次数阈值
+- **deflectionAttemptThreshold**: 偏转尝试的阈值
+- **antiTrapDeflectionMultiplier**: 防卡死时的偏转倍数
+- **FIRE_COOLDOWN**: 发射冷却时间
+- **ballRadius**: 球的碰撞半径
+- **gravityScale**: 重力缩放系数
+- **linearDamping**: 线性阻尼
+- **angularDamping**: 角度阻尼
+- **friction**: 摩擦系数
+- **restitution**: 弹性系数
+- **safeDistance**: 安全距离
+- **edgeOffset**: 边缘偏移量
+
+## 6. 玩家数据 (player_data.json)
+
+- **playerId**: 玩家的唯一标识符
+- **createTime**: 账号创建时间戳
+- **wallLevel**: 墙体的当前等级
+- **money**: 玩家当前拥有的金币数量
+- **diamonds**: 玩家当前拥有的钻石数量
+- **currentLevel**: 玩家当前所在关卡
+- **maxUnlockedLevel**: 玩家已解锁的最高关卡
+- **wallBaseHealth**: 墙体的基础血量
+- **inventory**: 玩家的物品背包
+- **statistics**: 玩家的游戏统计数据
+
+## 7. 墙体配置 (wall.json)
+
+- **maxLevel**: 墙体可升级的最高等级
+- **healthByLevel**: 各等级对应的血量值
+- **upgradeCosts**: 各等级升级所需的金币成本
+- **description**: 配置文件的说明信息
+
+---
+
+**注意事项:**
+1. 表格路径以及名称要一致,策划若配了表格,直接替换现有项目中表格位置即可
+2. 数值类型的字段需要注意合理的数值范围,避免游戏平衡性问题
+3. 墙体配置在wall.json没有配表(因为墙体配置比较简单),直接改json即可
+4. 其他配置文件的字段说明在文档中已经详细说明,根据表格名匹配json,使用启动配置工具即可更改配置文件