181404010226 6 месяцев назад
Родитель
Сommit
6f0a6aa7f4

Разница между файлами не показана из-за своего большого размера
+ 257 - 223
assets/Scenes/GameLevel.scene


+ 47 - 4
assets/assets/Prefabs/Ball.prefab

@@ -31,10 +31,13 @@
       },
       {
         "__id__": 8
+      },
+      {
+        "__id__": 10
       }
     ],
     "_prefab": {
-      "__id__": 10
+      "__id__": 12
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -155,7 +158,7 @@
     "_density": 1,
     "_sensor": false,
     "_friction": 0.2,
-    "_restitution": 0,
+    "_restitution": 1,
     "_offset": {
       "__type__": "cc.Vec2",
       "x": 0,
@@ -169,7 +172,7 @@
     "fileId": "dc8oUVmqZMH49PFwevbU3F"
   },
   {
-    "__type__": "dde4b0Dih1Iu6bhwWqt6Vhk",
+    "__type__": "abfa24X+cZNXb8i3HIHneEP",
     "_name": "",
     "_objFlags": 0,
     "__editorExtras__": {},
@@ -180,11 +183,50 @@
     "__prefab": {
       "__id__": 9
     },
+    "ballPrefab": {
+      "__uuid__": "bf1d1e67-5bab-4c32-b912-5fadfb93974a",
+      "__expectedType__": "cc.Prefab"
+    },
+    "speed": 300,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "d5qLzgvzNEPbITPslfGqAT"
+  },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 11
+    },
+    "enabledContactListener": true,
+    "bullet": true,
+    "awakeOnLoad": true,
+    "_group": 1,
+    "_type": 2,
+    "_allowSleep": true,
+    "_gravityScale": 1,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": false,
     "_id": ""
   },
   {
     "__type__": "cc.CompPrefabInfo",
-    "fileId": "1by6OO771FKb9uT+rYaA0k"
+    "fileId": "7dFc8HJphBuYK+2koSqF6j"
   },
   {
     "__type__": "cc.PrefabInfo",
@@ -195,6 +237,7 @@
       "__id__": 0
     },
     "fileId": "36x9tXzyBK3okp9WtTNuSh",
+    "instance": null,
     "targetOverrides": null
   }
 ]

+ 39 - 2
assets/assets/Prefabs/Block001.prefab

@@ -32,10 +32,13 @@
       },
       {
         "__id__": 20
+      },
+      {
+        "__id__": 22
       }
     ],
     "_prefab": {
-      "__id__": 22
+      "__id__": 24
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -475,7 +478,7 @@
     "_density": 1,
     "_sensor": false,
     "_friction": 0.2,
-    "_restitution": 0,
+    "_restitution": 1,
     "_offset": {
       "__type__": "cc.Vec2",
       "x": 0,
@@ -529,6 +532,40 @@
     "__type__": "cc.CompPrefabInfo",
     "fileId": "6cijxE1kJCRb8YwnU6o0Xq"
   },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 23
+    },
+    "enabledContactListener": true,
+    "bullet": false,
+    "awakeOnLoad": true,
+    "_group": 1,
+    "_type": 0,
+    "_allowSleep": true,
+    "_gravityScale": 0,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": false,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "992/Rw09lBQIO6kfz8uFhm"
+  },
   {
     "__type__": "cc.PrefabInfo",
     "root": {

+ 112 - 1
assets/assets/Prefabs/Block002.prefab

@@ -29,10 +29,16 @@
       },
       {
         "__id__": 22
+      },
+      {
+        "__id__": 24
+      },
+      {
+        "__id__": 26
       }
     ],
     "_prefab": {
-      "__id__": 24
+      "__id__": 28
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -529,6 +535,111 @@
     "__type__": "cc.CompPrefabInfo",
     "fileId": "0eTN2f2zZP+YzsJeLyVWot"
   },
+  {
+    "__type__": "cc.PolygonCollider2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 25
+    },
+    "tag": 0,
+    "_group": 1,
+    "_density": 1,
+    "_sensor": false,
+    "_friction": 0.2,
+    "_restitution": 1,
+    "_offset": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_points": [
+      {
+        "__type__": "cc.Vec2",
+        "x": -45,
+        "y": 26
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": -52,
+        "y": 19
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": -52,
+        "y": -19
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": -45,
+        "y": -26
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": 45,
+        "y": -26
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": 52,
+        "y": -19
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": 52,
+        "y": 19
+      },
+      {
+        "__type__": "cc.Vec2",
+        "x": 45,
+        "y": 26
+      }
+    ],
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "12Zr4WbaFDGrdJgmSXibT6"
+  },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 27
+    },
+    "enabledContactListener": true,
+    "bullet": false,
+    "awakeOnLoad": true,
+    "_group": 1,
+    "_type": 0,
+    "_allowSleep": true,
+    "_gravityScale": 0,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": false,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "faAfPpsTVEhoBUqBKi32zb"
+  },
   {
     "__type__": "cc.PrefabInfo",
     "root": {

+ 39 - 2
assets/assets/Prefabs/Block003.prefab

@@ -32,10 +32,13 @@
       },
       {
         "__id__": 24
+      },
+      {
+        "__id__": 26
       }
     ],
     "_prefab": {
-      "__id__": 26
+      "__id__": 28
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -566,7 +569,7 @@
     "_density": 1,
     "_sensor": false,
     "_friction": 0.2,
-    "_restitution": 0,
+    "_restitution": 1,
     "_offset": {
       "__type__": "cc.Vec2",
       "x": 0,
@@ -635,6 +638,40 @@
     "__type__": "cc.CompPrefabInfo",
     "fileId": "6f1PfufOlJu4f51BsZTF2g"
   },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 27
+    },
+    "enabledContactListener": true,
+    "bullet": false,
+    "awakeOnLoad": true,
+    "_group": 1,
+    "_type": 0,
+    "_allowSleep": true,
+    "_gravityScale": 0,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": false,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "2cLTHHWghLAb6lHK7dKjlU"
+  },
   {
     "__type__": "cc.PrefabInfo",
     "root": {

+ 41 - 2
assets/assets/Prefabs/Block004.prefab

@@ -32,10 +32,13 @@
       },
       {
         "__id__": 28
+      },
+      {
+        "__id__": 30
       }
     ],
     "_prefab": {
-      "__id__": 30
+      "__id__": 32
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -563,6 +566,8 @@
       "__id__": 0
     },
     "fileId": "6fnfs6fF9DMJHeLIKx8GPi",
+    "instance": null,
+    "targetOverrides": null,
     "nestedPrefabInstanceRoots": null
   },
   {
@@ -655,7 +660,7 @@
     "_density": 1,
     "_sensor": false,
     "_friction": 0.2,
-    "_restitution": 0,
+    "_restitution": 1,
     "_offset": {
       "__type__": "cc.Vec2",
       "x": 0,
@@ -739,6 +744,40 @@
     "__type__": "cc.CompPrefabInfo",
     "fileId": "4bzj/orx9DNrYh3L5LRPP8"
   },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 31
+    },
+    "enabledContactListener": true,
+    "bullet": false,
+    "awakeOnLoad": true,
+    "_group": 1,
+    "_type": 0,
+    "_allowSleep": true,
+    "_gravityScale": 0,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": false,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "9892JUH8VCxqxQbb94cWru"
+  },
   {
     "__type__": "cc.PrefabInfo",
     "root": {

+ 41 - 2
assets/assets/Prefabs/Block005.prefab

@@ -32,10 +32,13 @@
       },
       {
         "__id__": 28
+      },
+      {
+        "__id__": 30
       }
     ],
     "_prefab": {
-      "__id__": 30
+      "__id__": 32
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -563,6 +566,8 @@
       "__id__": 0
     },
     "fileId": "e44cCUq8VGrarHSpszc63Q",
+    "instance": null,
+    "targetOverrides": null,
     "nestedPrefabInstanceRoots": null
   },
   {
@@ -655,7 +660,7 @@
     "_density": 1,
     "_sensor": false,
     "_friction": 0.2,
-    "_restitution": 0,
+    "_restitution": 1,
     "_offset": {
       "__type__": "cc.Vec2",
       "x": 0,
@@ -739,6 +744,40 @@
     "__type__": "cc.CompPrefabInfo",
     "fileId": "e8aeMbOjpN8bZ4jZU5s33x"
   },
+  {
+    "__type__": "cc.RigidBody2D",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 1
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 31
+    },
+    "enabledContactListener": true,
+    "bullet": false,
+    "awakeOnLoad": true,
+    "_group": 1,
+    "_type": 0,
+    "_allowSleep": true,
+    "_gravityScale": 0,
+    "_linearDamping": 0,
+    "_angularDamping": 0,
+    "_linearVelocity": {
+      "__type__": "cc.Vec2",
+      "x": 0,
+      "y": 0
+    },
+    "_angularVelocity": 0,
+    "_fixedRotation": false,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "fa27e+tXhIuZKvDZsv19fv"
+  },
   {
     "__type__": "cc.PrefabInfo",
     "root": {

+ 235 - 0
assets/scripts/BallController.ts

@@ -0,0 +1,235 @@
+import { _decorator, Component, Node, Vec2, Vec3, UITransform, Collider2D, Contact2DType, IPhysics2DContact, director, CircleCollider2D, RigidBody2D, Prefab, instantiate, find, js } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('BallController')
+export class BallController extends Component {
+    // 球的预制体
+    @property({
+        type: Prefab,
+        tooltip: '拖拽Ball预制体到这里'
+    })
+    public ballPrefab: Prefab = null;
+
+    // 球的移动速度
+    @property
+    public speed: number = 10;
+
+    // 当前活动的球
+    private activeBall: Node = null;
+
+    // 球的方向向量
+    private direction: Vec2 = new Vec2();
+    
+    // 游戏区域边界
+    private gameBounds = {
+        left: 0,
+        right: 0,
+        top: 0,
+        bottom: 0
+    };
+
+    // 球的半径
+    private radius: number = 0;
+
+    // 是否已初始化
+    private initialized: boolean = false;
+
+    start() {
+        // 计算游戏边界
+        this.calculateGameBounds();
+    }
+
+    // 计算游戏边界(使用整个屏幕)
+    calculateGameBounds() {
+        // 获取屏幕尺寸
+        const canvas = find('Canvas');
+        if (!canvas) {
+            console.error('找不到Canvas节点');
+            return;
+        }
+
+        const canvasUI = canvas.getComponent(UITransform);
+        if (!canvasUI) {
+            console.error('Canvas节点没有UITransform组件');
+            return;
+        }
+
+        // 获取屏幕的尺寸
+        const screenWidth = canvasUI.width;
+        const screenHeight = canvasUI.height;
+        
+        // 获取屏幕的世界坐标位置
+        const worldPos = canvas.worldPosition;
+
+        // 计算屏幕的世界坐标边界
+        this.gameBounds.left = worldPos.x - screenWidth / 2;
+        this.gameBounds.right = worldPos.x + screenWidth / 2;
+        this.gameBounds.bottom = worldPos.y - screenHeight / 2;
+        this.gameBounds.top = worldPos.y + screenHeight / 2;
+
+        console.log('Screen Bounds:', this.gameBounds);
+    }
+
+    // 创建小球
+    createBall() {
+        if (!this.ballPrefab) {
+            console.error('未设置Ball预制体');
+            return;
+        }
+
+        // 如果已经有活动的球,先销毁它
+        if (this.activeBall && this.activeBall.isValid) {
+            this.activeBall.destroy();
+        }
+
+        // 实例化小球
+        this.activeBall = instantiate(this.ballPrefab);
+        this.node.addChild(this.activeBall);
+
+
+        // 随机位置(在屏幕范围内)
+        this.positionBallRandomly();
+
+        // 设置球的半径
+        const transform = this.activeBall.getComponent(UITransform);
+        if (transform) {
+            this.radius = transform.width / 2;
+        }
+
+        // 确保有碰撞组件
+        this.setupCollider();
+
+        // 初始化方向
+        this.initializeDirection();
+
+        this.initialized = true;
+    }
+
+
+    // 随机位置小球
+    positionBallRandomly() {
+        if (!this.activeBall) return;
+
+        const transform = this.activeBall.getComponent(UITransform);
+        const ballRadius = transform ? transform.width / 2 : 25;
+        
+        // 计算可生成的范围(考虑小球半径,避免生成在边缘)
+        const minX = this.gameBounds.left + ballRadius;
+        const maxX = this.gameBounds.right - ballRadius;
+        const minY = this.gameBounds.bottom + ballRadius;
+        const maxY = this.gameBounds.top - ballRadius;
+
+        // 随机生成位置
+        const randomX = Math.random() * (maxX - minX) + minX;
+        const randomY = Math.random() * (maxY - minY) + minY;
+
+        // 将世界坐标转换为相对于节点的本地坐标
+        const canvas = find('Canvas');
+        if (canvas) {
+            const localPos = canvas.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() {
+        if (!this.activeBall) return;
+
+        let collider = this.activeBall.getComponent(CircleCollider2D);
+        if (!collider) {
+            collider = this.activeBall.addComponent(CircleCollider2D);
+            collider.radius = this.radius;
+            collider.tag = 0;
+            collider.group = 1;
+            collider.sensor = false;
+            collider.friction = 0;
+            collider.restitution = 1; // 完全弹性碰撞
+        } else {
+            // 设置碰撞组件的属性
+            collider.restitution = 1; // 确保完全弹性碰撞
+        }
+
+        // 注册碰撞事件
+        collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
+    }
+
+    // 初始化方向
+    initializeDirection() {
+        // 随机初始方向
+        const angle = Math.random() * Math.PI * 2; // 0-2π之间的随机角度
+        this.direction.x = Math.cos(angle);
+        this.direction.y = Math.sin(angle);
+        this.direction.normalize();
+
+        console.log('Ball initialized with direction:', this.direction);
+    }
+
+    // 初始化球的参数 - 公开方法,供GameManager调用
+    initialize() {
+        this.calculateGameBounds();
+        this.createBall();
+    }
+
+    update(dt: number) {
+        // 如果使用物理引擎,不要手动更新位置
+        if (!this.initialized || !this.activeBall || !this.activeBall.isValid) return;
+        
+        // 使用刚体组件控制小球移动,而不是直接设置位置
+        const rigidBody = this.activeBall.getComponent(RigidBody2D);
+        if (rigidBody) {
+            // 设置线性速度而不是位置
+            rigidBody.linearVelocity = new Vec2(
+                this.direction.x * this.speed,
+                this.direction.y * this.speed
+            );
+        }
+        
+        // 不再需要这些代码,因为物理引擎会处理碰撞和位置更新
+        // const newPos = new Vec3(...);
+        // this.checkBoundaryCollision(newPos);
+        // this.activeBall.position = newPos;
+    }
+
+    // 碰撞回调中,只需要更新方向向量,物理引擎会处理实际的反弹
+    onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
+        // 获取碰撞点和法线
+        if (!contact) return;
+
+        // 获取碰撞点的世界坐标
+        const worldManifold = contact.getWorldManifold();
+        if (!worldManifold) return;
+
+        // 获取碰撞法线
+        const normal = worldManifold.normal;
+        
+        // 更新方向向量,但让物理引擎处理实际的反弹
+        this.direction = this.calculateReflection(this.direction, normal);
+        
+        // 如果碰撞的是方块,发射子弹
+        if (otherCollider.node.name.includes('Block')) {
+            this.fireBullet(otherCollider.node);
+        }
+    }
+
+    // 计算反射向量
+    calculateReflection(direction: Vec2, normal: Vec2): Vec2 {
+        // 使用反射公式: R = V - 2(V·N)N
+        const dot = direction.x * normal.x + direction.y * normal.y;
+        const reflection = new Vec2(
+            direction.x - 2 * dot * normal.x,
+            direction.y - 2 * dot * normal.y
+        );
+        reflection.normalize();
+        return reflection;
+    }
+
+    // 发射子弹
+    fireBullet(blockNode: Node) {
+        console.log('发射子弹!击中方块:', blockNode.name);
+        // 这里只是模拟发射子弹,实际的子弹逻辑和动画将在后续添加
+    }
+
+} 

+ 9 - 0
assets/scripts/BallController.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "abfa2e17-f9c6-4d5d-bf22-dc72079de10f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 174 - 35
assets/scripts/BlockManager.ts

@@ -28,8 +28,11 @@ export class BlockManager extends Component {
     })
     public coinLabelNode: Node = null;
     
+    // 游戏是否已开始
+    public gameStarted: boolean = false;
+    
     // 玩家金币数量
-    private playerCoins: number = 200;
+    private playerCoins: number = 699;
     
     // 方块价格标签映射
     private blockPriceMap: Map<Node, Node> = new Map();
@@ -90,6 +93,9 @@ export class BlockManager extends Component {
             }
         }
         
+        // 确保有PlacedBlocks节点用于存放已放置的方块
+        this.ensurePlacedBlocksNode();
+        
         // 初始化玩家金币显示
         this.updateCoinDisplay();
         
@@ -103,6 +109,28 @@ export class BlockManager extends Component {
         this.generateRandomBlocks();
     }
     
+    // 确保有PlacedBlocks节点
+    ensurePlacedBlocksNode() {
+        // 查找Canvas节点
+        const canvas = find('Canvas');
+        if (!canvas) {
+            console.error('找不到Canvas节点');
+            return;
+        }
+        
+        // 查找或创建PlacedBlocks节点
+        let placedBlocksNode = find('Canvas/PlacedBlocks');
+        if (!placedBlocksNode) {
+            placedBlocksNode = new Node('PlacedBlocks');
+            canvas.addChild(placedBlocksNode);
+            // 确保PlacedBlocks节点有UITransform组件
+            if (!placedBlocksNode.getComponent(UITransform)) {
+                placedBlocksNode.addComponent(UITransform);
+            }
+            console.log('已创建PlacedBlocks节点');
+        }
+    }
+    
     // 更新金币显示
     updateCoinDisplay() {
         if (this.coinLabelNode) {
@@ -165,7 +193,7 @@ export class BlockManager extends Component {
     }
     
     generateRandomBlocks() {
-        // 清除现有的
+        // 如果游戏已开始,只清除kuang区域的块,否则清除所有
         this.clearBlocks();
         
         // 检查是否有预制体可用
@@ -174,6 +202,13 @@ export class BlockManager extends Component {
             return;
         }
         
+        // 获取kuang节点
+        const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
+        if (!kuangNode) {
+            console.error('找不到kuang节点');
+            return;
+        }
+        
         // 位置偏移量
         const offsets = [
             new Vec3(-200, 0, 0),
@@ -183,9 +218,9 @@ export class BlockManager extends Component {
         
         // 获取kuang节点下的db01、db02、db03节点
         const dbNodes = [
-            find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang/db01'),
-            find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang/db02'),
-            find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang/db03')
+            kuangNode.getChildByName('db01'),
+            kuangNode.getChildByName('db02'),
+            kuangNode.getChildByName('db03')
         ];
         
         // 生成三个随机块
@@ -196,7 +231,7 @@ export class BlockManager extends Component {
             
             // 实例化预制体
             const block = instantiate(prefab);
-            this.node.addChild(block);
+            kuangNode.addChild(block);  // 直接添加到kuang节点下
             
             // 设置位置
             block.position = offsets[i];
@@ -316,7 +351,7 @@ export class BlockManager extends Component {
             this.currentDragBlock['startLocation'] = this.blockLocations.get(block);
             
             // 将块置于顶层
-            block.setSiblingIndex(this.node.children.length - 1);
+            block.setSiblingIndex(block.parent.children.length - 1);
             
             // 临时保存方块占用的网格,而不是直接移除
             this.tempStoreBlockOccupiedGrids(block);
@@ -352,6 +387,16 @@ export class BlockManager extends Component {
                     // 如果拖回kuang区域,恢复到原始位置
                     const originalPos = this.originalPositions.get(this.currentDragBlock);
                     if (originalPos) {
+                        // 确保方块在kuang节点下
+                        const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
+                        if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
+                            // 将方块从当前父节点移除
+                            this.currentDragBlock.removeFromParent();
+                            // 添加到kuang节点下
+                            kuangNode.addChild(this.currentDragBlock);
+                        }
+                        
+                        // 设置位置
                         this.currentDragBlock.position = originalPos.clone();
                     }
                     
@@ -398,6 +443,17 @@ export class BlockManager extends Component {
                         
                         // 隐藏价格标签
                         this.hidePriceLabel(this.currentDragBlock);
+                        
+                        // 隐藏db节点
+                        const dbNode = this.currentDragBlock['dbNode'];
+                        if (dbNode) {
+                            dbNode.active = false;
+                        }
+                        
+                        // 如果游戏已经开始,将方块移动到PlacedBlocks节点下
+                        if (this.gameStarted) {
+                            this.moveBlockToPlacedBlocks(this.currentDragBlock);
+                        }
                     } else {
                         // 尝试扣除金币
                         if (this.deductPlayerCoins(price)) {
@@ -410,8 +466,19 @@ export class BlockManager extends Component {
                             // 隐藏价格标签
                             this.hidePriceLabel(this.currentDragBlock);
                             
+                            // 隐藏db节点
+                            const dbNode = this.currentDragBlock['dbNode'];
+                            if (dbNode) {
+                                dbNode.active = false;
+                            }
+                            
                             // 标记方块已经放置过
                             this.currentDragBlock['placedBefore'] = true;
+                            
+                            // 如果游戏已经开始,将方块移动到PlacedBlocks节点下
+                            if (this.gameStarted) {
+                                this.moveBlockToPlacedBlocks(this.currentDragBlock);
+                            }
                         } else {
                             // 金币不足,放置失败,返回原位
                             const originalPos = this.originalPositions.get(this.currentDragBlock);
@@ -478,6 +545,17 @@ export class BlockManager extends Component {
                 // 触摸取消,返回原位
                 this.currentDragBlock.position = this.blockStartPos.clone();
                 
+                // 如果当前在kuang区域,确保方块在kuang节点下
+                if (this.blockLocations.get(this.currentDragBlock) === 'kuang') {
+                    const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
+                    if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
+                        // 将方块从当前父节点移除
+                        this.currentDragBlock.removeFromParent();
+                        // 添加到kuang节点下
+                        kuangNode.addChild(this.currentDragBlock);
+                    }
+                }
+                
                 // 恢复方块原来的占用状态
                 this.restoreBlockOccupiedGrids(this.currentDragBlock);
                 
@@ -909,45 +987,106 @@ export class BlockManager extends Component {
     }
     
     clearBlocks() {
-        // 移除所有已经生成的块
+        // 只移除kuang区域的块,保留已放置在网格中的块
+        const blocksToRemove = [];
+        
+        // 找出所有在kuang区域的块
+        for (const block of this.blocks) {
+            if (block.isValid) {
+                const location = this.blockLocations.get(block);
+                if (location === 'kuang') {
+                    blocksToRemove.push(block);
+                }
+            }
+        }
+        
+        // 移除kuang区域的块
+        for (const block of blocksToRemove) {
+            // 如果有关联的db节点,恢复其位置
+            const dbNode = block['dbNode'];
+            if (dbNode && dbNode.isValid) {
+                // 移除方块移动时的监听
+                block.off(Node.EventType.TRANSFORM_CHANGED);
+                
+                // 恢复db节点到原始位置
+                const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
+                if (kuangNode) {
+                    // 确保db节点回到原来的父节点下
+                    const dbName = dbNode.name;
+                    if (!kuangNode.getChildByName(dbName)) {
+                        dbNode.parent = kuangNode;
+                    }
+                }
+            }
+            
+            // 从blocks数组中移除
+            const index = this.blocks.indexOf(block);
+            if (index !== -1) {
+                this.blocks.splice(index, 1);
+            }
+            
+            // 清除相关映射
+            this.originalPositions.delete(block);
+            this.blockLocations.delete(block);
+            this.blockPriceMap.delete(block);
+            
+            // 销毁方块
+            block.destroy();
+        }
+        
+        // 注意:不重置网格占用情况,保留已放置的方块占用信息
+    }
+    
+    // 游戏开始时,确保已放置的方块正确显示
+    onGameStart() {
+        // 遍历所有方块
         for (const block of this.blocks) {
             if (block.isValid) {
-                // 移除占用的格子
-                this.removeBlockFromGrid(block);
+                const location = this.blockLocations.get(block);
                 
-                // 如果有关联的db节点,恢复其位置
-                const dbNode = block['dbNode'];
-                if (dbNode && dbNode.isValid) {
-                    // 移除方块移动时的监听
-                    block.off(Node.EventType.TRANSFORM_CHANGED);
+                // 如果方块在网格中,将其移动到PlacedBlocks节点下
+                if (location === 'grid') {
+                    // 隐藏价格标签
+                    this.hidePriceLabel(block);
                     
-                    // 恢复db节点到原始位置
-                    const originalParent = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
-                    if (originalParent) {
-                        // 确保db节点回到原来的父节点下
-                        const dbName = dbNode.name;
-                        if (!originalParent.getChildByName(dbName)) {
-                            dbNode.parent = originalParent;
-                        }
+                    // 隐藏db节点
+                    const dbNode = block['dbNode'];
+                    if (dbNode) {
+                        dbNode.active = false;
                     }
+                    
+                    // 将方块移动到PlacedBlocks节点下
+                    this.moveBlockToPlacedBlocks(block);
                 }
-                
-                // 销毁方块
-                block.destroy();
             }
         }
-        this.blocks = [];
         
-        // 清空原始位置记录
-        this.originalPositions.clear();
+        // 设置游戏已开始标志
+        this.gameStarted = true;
+    }
+    
+    // 将方块移动到PlacedBlocks节点下
+    moveBlockToPlacedBlocks(block: Node) {
+        // 查找PlacedBlocks节点
+        const placedBlocksNode = find('Canvas/PlacedBlocks');
+        if (!placedBlocksNode) {
+            console.error('找不到PlacedBlocks节点');
+            return;
+        }
+        
+        // 保存方块的世界位置
+        const worldPosition = new Vec3();
+        block.getWorldPosition(worldPosition);
         
-        // 清空位置标记
-        this.blockLocations.clear();
+        // 将方块从当前父节点移除
+        block.removeFromParent();
         
-        // 清空价格标签映射
-        this.blockPriceMap.clear();
+        // 添加到PlacedBlocks节点下
+        placedBlocksNode.addChild(block);
         
-        // 重置网格占用情况
-        this.initGridOccupationMap();
+        // 设置方块的世界位置,确保在屏幕上的位置不变
+        block.setWorldPosition(worldPosition);
+        
+        console.log(`已将方块 ${block.name} 移动到PlacedBlocks节点下`);
     }
 } 

+ 188 - 0
assets/scripts/GameManager.ts

@@ -0,0 +1,188 @@
+import { _decorator, Component, Node, Prefab, instantiate, Vec3, find, director, Canvas, UITransform, Button } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('GameManager')
+export class GameManager extends Component {
+    @property({
+        type: Node,
+        tooltip: '拖拽BallController节点到这里'
+    })
+    public ballController: Node = null;
+
+    @property({
+        type: Node,
+        tooltip: '拖拽BlockSelectionUI节点到这里'
+    })
+    public blockSelectionUI: Node = null;
+
+    @property({
+        type: Node,
+        tooltip: '拖拽GameArea节点到这里'
+    })
+    public gameArea: Node = null;
+
+    // 游戏是否已经开始
+    private gameStarted: boolean = false;
+    // 游戏区域的边界
+    private gameBounds = {
+        left: 0,
+        right: 0,
+        top: 0,
+        bottom: 0
+    };
+
+    start() {
+        // 如果没有指定blockSelectionUI,尝试找到它
+        if (!this.blockSelectionUI) {
+            this.blockSelectionUI = find('Canvas/GameLevelUI/BlockSelectionUI');
+            if (!this.blockSelectionUI) {
+                console.error('找不到BlockSelectionUI节点');
+                return;
+            }
+        }
+
+        // 如果没有指定gameArea,尝试找到它
+        if (!this.gameArea) {
+            this.gameArea = find('Canvas/GameLevelUI/GameArea');
+            if (!this.gameArea) {
+                console.error('找不到GameArea节点');
+                return;
+            }
+        }
+
+        // 如果没有指定ballController,尝试找到它
+        if (!this.ballController) {
+            this.ballController = find('Canvas/GameLevelUI/BallController');
+            if (!this.ballController) {
+                console.error('找不到BallController节点');
+                return;
+            }
+        }
+
+        // 计算游戏区域边界
+        this.calculateGameBounds();
+
+        // 注意:不再需要手动添加按钮事件监听
+        // 可以在编辑器中通过Button组件的Click Events直接绑定onConfirmButtonClicked方法
+    }
+
+    // 计算游戏区域边界
+    calculateGameBounds() {
+        // 获取屏幕尺寸
+        const canvas = find('Canvas');
+        if (!canvas) {
+            console.error('找不到Canvas节点');
+            return;
+        }
+
+        const canvasUI = canvas.getComponent(UITransform);
+        if (!canvasUI) {
+            console.error('Canvas节点没有UITransform组件');
+            return;
+        }
+
+        // 获取屏幕的尺寸
+        const screenWidth = canvasUI.width;
+        const screenHeight = canvasUI.height;
+        
+        // 获取屏幕的世界坐标位置
+        const worldPos = canvas.worldPosition;
+
+        // 计算屏幕的世界坐标边界
+        this.gameBounds.left = worldPos.x - screenWidth / 2;
+        this.gameBounds.right = worldPos.x + screenWidth / 2;
+        this.gameBounds.bottom = worldPos.y - screenHeight / 2;
+        this.gameBounds.top = worldPos.y + screenHeight / 2;
+
+        console.log('Screen Bounds:', this.gameBounds);
+    }
+
+    // 点击确认按钮的回调
+    onConfirmButtonClicked() {
+        console.log('确认按钮被点击');
+        
+        // 隐藏BlockSelectionUI,但保留已放置的方块
+        if (this.blockSelectionUI) {
+            this.blockSelectionUI.active = false;
+            
+            // 查找GridContainer节点,确保它保持可见
+            const gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
+            if (gridContainer) {
+                gridContainer.active = true;
+            }
+            
+            // 确保已放置的方块保持可见
+            this.preservePlacedBlocks();
+        }
+
+        // 开始游戏
+        this.startGame();
+    }
+    
+    // 保存已放置的方块
+    preservePlacedBlocks() {
+        // 查找BlockManager组件
+        const blockController = find('Canvas/GameLevelUI/BlockController');
+        if (blockController) {
+            const blockManager = blockController.getComponent('BlockManager');
+            if (blockManager) {
+                // 调用BlockManager的onGameStart方法
+                (blockManager as any).onGameStart();
+                console.log('已调用BlockManager.onGameStart(),确保已放置的方块正确显示');
+            } else {
+                console.warn('BlockController节点上没有BlockManager组件');
+            }
+        } else {
+            console.warn('找不到BlockController节点,尝试在Canvas上查找BlockManager组件');
+            
+            // 尝试在Canvas上查找
+            const canvasBlockManager = find('Canvas').getComponent('BlockManager');
+            if (canvasBlockManager) {
+                // 调用BlockManager的onGameStart方法
+                (canvasBlockManager as any).onGameStart();
+                console.log('已调用Canvas上的BlockManager.onGameStart()');
+            } else {
+                console.warn('找不到BlockManager组件,无法保存已放置的方块状态');
+            }
+        }
+    }
+
+    // 开始游戏
+    startGame() {
+        if (this.gameStarted) return;
+        
+        console.log('游戏开始');
+        this.gameStarted = true;
+
+        // 使用BallController生成小球
+        this.spawnBall();
+    }
+
+    // 生成小球
+    spawnBall() {
+        if (!this.ballController) {
+            console.error('未设置BallController节点');
+            return;
+        }
+
+        // 获取BallController组件
+        const ballControllerComp = this.ballController.getComponent('BallController');
+        if (ballControllerComp) {
+            // 调用initialize方法创建小球
+            (ballControllerComp as any).initialize();
+        } else {
+            console.error('BallController节点没有BallController组件');
+        }
+    }
+
+    // 游戏结束
+    gameOver() {
+        this.gameStarted = false;
+        console.log('游戏结束');
+        
+        // 显示BlockSelectionUI
+        if (this.blockSelectionUI) {
+            this.blockSelectionUI.active = true;
+        }
+    }
+} 

+ 9 - 0
assets/scripts/GameManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "b8ecea70-c6dc-4a82-a28f-37de3a5207af",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 21 - 0
assets/scripts/GameStartup.ts

@@ -0,0 +1,21 @@
+import { _decorator, Component, Node, director, find } from 'cc';
+import { SceneSetup } from './SceneSetup';
+const { ccclass, property } = _decorator;
+
+@ccclass('GameStartup')
+export class GameStartup extends Component {
+    start() {
+        console.log('GameStartup: 初始化游戏...');
+        
+        // 添加场景设置组件
+        const canvas = find('Canvas');
+        if (canvas) {
+            if (!canvas.getComponent(SceneSetup)) {
+                canvas.addComponent(SceneSetup);
+                console.log('GameStartup: 已添加SceneSetup组件到Canvas');
+            }
+        } else {
+            console.error('GameStartup: 找不到Canvas节点');
+        }
+    }
+} 

+ 9 - 0
assets/scripts/GameStartup.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "87d4f56b-dbcf-44a9-9edb-ea76357d9253",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 28 - 0
assets/scripts/PhysicsManager.ts

@@ -0,0 +1,28 @@
+import { _decorator, Component, Node, PhysicsSystem2D, EPhysics2DDrawFlags, Vec2, debug } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('PhysicsManager')
+export class PhysicsManager extends Component {
+    @property
+    debugDraw: boolean = false;
+
+    start() {
+        // 启用物理系统
+        PhysicsSystem2D.instance.enable = true;
+        
+        // 设置物理系统的重力为零(因为是2D平面游戏)
+        PhysicsSystem2D.instance.gravity = new Vec2(0, 0);
+        
+        // 如果启用了调试绘制,显示碰撞体
+        if (this.debugDraw) {
+            PhysicsSystem2D.instance.debugDrawFlags = 
+                EPhysics2DDrawFlags.Aabb | 
+                EPhysics2DDrawFlags.Pair | 
+                EPhysics2DDrawFlags.CenterOfMass | 
+                EPhysics2DDrawFlags.Joint | 
+                EPhysics2DDrawFlags.Shape;
+        } else {
+            PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.None;
+        }
+    }
+} 

+ 9 - 0
assets/scripts/PhysicsManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1f8336a0-621e-433f-96c8-5055204fd36e",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 65 - 0
assets/scripts/README.md

@@ -0,0 +1,65 @@
+# 弹球游戏实现说明
+
+## 脚本概述
+
+1. **GameManager.ts**
+   - 游戏主控制器
+   - 负责游戏流程控制、生成小球、管理游戏状态
+
+2. **BallController.ts**
+   - 小球控制器
+   - 控制小球的移动、碰撞检测和反弹逻辑
+
+3. **PhysicsManager.ts**
+   - 物理系统管理器
+   - 初始化和配置物理系统
+
+4. **SceneSetup.ts**
+   - 场景初始化脚本
+   - 自动添加必要的组件和设置引用
+
+5. **BallSetup.ts**
+   - 小球预制体初始化脚本
+   - 确保小球预制体有必要的组件
+
+## 使用方法
+
+### 1. 场景设置
+
+1. 在场景根节点(Canvas)上添加 `SceneSetup` 组件
+
+### 2. 预制体设置
+
+1. 确保 Ball 预制体位于 `assets/assets/Prefabs/Ball.prefab`
+2. 在 Ball 预制体上添加 `BallSetup` 组件
+
+### 3. UI设置
+
+1. 确保场景中有以下节点结构:
+   - Canvas
+     - GameLevelUI
+       - BlockSelectionUI
+         - diban
+           - confirm (按钮)
+           - kuang
+             - db01
+             - db02
+             - db03
+       - GameArea (用于放置游戏元素)
+       - CoinNode
+         - CoinLabel
+
+## 游戏流程
+
+1. 点击 `confirm` 按钮后,会隐藏 BlockSelectionUI 并开始游戏
+2. 游戏开始时,会在 GameArea 内随机位置生成一个小球,并赋予随机初始方向
+3. 小球会按照弹球游戏的规则运动:
+   - 碰到屏幕边界会反弹
+   - 碰到方块也会反弹
+   - 碰到方块时会模拟发射子弹(目前只是在控制台输出)
+
+## 注意事项
+
+1. 确保方块有正确的碰撞组件设置
+2. 确保 GameArea 节点的大小正确设置,以便正确计算游戏边界
+3. 如果需要显示物理调试信息,可以在 PhysicsManager 组件中勾选 debugDraw 选项 

+ 11 - 0
assets/scripts/README.md.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "1.0.1",
+  "importer": "text",
+  "imported": true,
+  "uuid": "3312b654-d4b3-47f9-9ffa-39e400cc53b2",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 39 - 0
assets/scripts/SceneSetup.ts

@@ -0,0 +1,39 @@
+import { _decorator, Component, Node, director, find, resources, Prefab } from 'cc';
+import { GameManager } from './GameManager';
+import { PhysicsManager } from './PhysicsManager';
+const { ccclass, property } = _decorator;
+
+@ccclass('SceneSetup')
+export class SceneSetup extends Component {
+    start() {
+        // 添加物理管理器
+        if (!this.node.getComponent(PhysicsManager)) {
+            this.node.addComponent(PhysicsManager);
+        }
+
+        // 添加游戏管理器
+        if (!this.node.getComponent(GameManager)) {
+            const gameManager = this.node.addComponent(GameManager);
+            
+            // 查找并设置必要的引用
+            // 加载Ball预制体
+            resources.load('assets/Prefabs/Ball', Prefab, (err, prefab) => {
+                if (err) {
+                    console.error('加载Ball预制体失败:', err);
+                } else {
+                    gameManager.ballPrefab = prefab;
+                }
+            });
+            
+            const blockSelectionUI = find('Canvas/GameLevelUI/BlockSelectionUI');
+            if (blockSelectionUI) {
+                gameManager.blockSelectionUI = blockSelectionUI;
+            }
+            
+            const gameArea = find('Canvas/GameLevelUI/GameArea');
+            if (gameArea) {
+                gameManager.gameArea = gameArea;
+            }
+        }
+    }
+} 

+ 9 - 0
assets/scripts/SceneSetup.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2e1c2434-493a-4a3a-be60-3994dbfb9d7d",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов