BlockManager.ts 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find, Rect, Label, Color, Size, Contact2DType, Collider2D, IPhysics2DContact } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. @ccclass('BlockManager')
  4. export class BlockManager extends Component {
  5. // 预制体数组,存储5个预制体
  6. @property([Prefab])
  7. public blockPrefabs: Prefab[] = [];
  8. // 网格容器节点
  9. @property({
  10. type: Node,
  11. tooltip: '拖拽GridContainer节点到这里'
  12. })
  13. public gridContainer: Node = null;
  14. // 方块容器节点(kuang)
  15. @property({
  16. type: Node,
  17. tooltip: '拖拽kuang节点到这里'
  18. })
  19. public kuangContainer: Node = null;
  20. // 金币标签节点
  21. @property({
  22. type: Node,
  23. tooltip: '拖拽CoinLabel节点到这里'
  24. })
  25. public coinLabelNode: Node = null;
  26. // 子弹预制体
  27. @property({
  28. type: Prefab,
  29. tooltip: '拖拽子弹预制体到这里'
  30. })
  31. public bulletPrefab: Prefab = null;
  32. // 游戏是否已开始
  33. public gameStarted: boolean = false;
  34. // 方块移动冷却时间(秒)
  35. @property({
  36. tooltip: '游戏开始后方块移动的冷却时间(秒)'
  37. })
  38. public blockMoveCooldown: number = 10;
  39. // 子弹发射冷却时间(秒)
  40. @property({
  41. tooltip: '方块发射子弹的冷却时间(秒)'
  42. })
  43. public bulletCooldown: number = 0.5;
  44. // 玩家金币数量
  45. private playerCoins: number = 699;
  46. // 方块价格标签映射
  47. private blockPriceMap: Map<Node, Node> = new Map();
  48. // 已经生成的块
  49. private blocks: Node[] = [];
  50. // 当前拖拽的块
  51. private currentDragBlock: Node | null = null;
  52. // 拖拽起始位置
  53. private startPos = new Vec2();
  54. // 块的起始位置
  55. private blockStartPos: Vec3 = new Vec3();
  56. // 网格占用情况,用于控制台输出
  57. private gridOccupationMap: number[][] = [];
  58. // 网格行数和列数
  59. private readonly GRID_ROWS = 6;
  60. private readonly GRID_COLS = 11;
  61. // 是否已初始化网格信息
  62. private gridInitialized = false;
  63. // 存储网格节点信息
  64. private gridNodes: Node[][] = [];
  65. // 网格间距
  66. private gridSpacing = 54;
  67. // 不参与占用的节点名称列表
  68. private readonly NON_BLOCK_NODES: string[] = ['Weapon', 'Price'];
  69. // 临时保存方块的原始占用格子
  70. private tempRemovedOccupiedGrids: { block: Node, occupiedGrids: { row: number, col: number }[] }[] = [];
  71. // 方块原始位置(在kuang中的位置)
  72. private originalPositions: Map<Node, Vec3> = new Map();
  73. // 方块当前所在的区域
  74. private blockLocations: Map<Node, string> = new Map();
  75. // 方块移动冷却状态管理
  76. private blockCooldowns: Map<Node, number> = new Map(); // 存储每个方块的冷却结束时间
  77. private globalCooldownEndTime: number = 0; // 全局冷却结束时间
  78. // 子弹发射冷却状态
  79. private canFireBullet: boolean = true;
  80. // 碰撞的方块ID记录,避免重复发射
  81. private collidedBlockIds: Set<string> = new Set();
  82. // 检查方块是否可以移动(冷却检查)
  83. private canMoveBlock(block: Node): boolean {
  84. if (!this.gameStarted) {
  85. // 游戏未开始(备战阶段),可以自由移动
  86. return true;
  87. }
  88. const currentTime = Date.now() / 1000; // 转换为秒
  89. // 检查全局冷却
  90. if (currentTime < this.globalCooldownEndTime) {
  91. const remainingTime = Math.ceil(this.globalCooldownEndTime - currentTime);
  92. return false;
  93. }
  94. return true;
  95. }
  96. // 设置方块移动冷却
  97. private setBlockCooldown(block: Node) {
  98. if (!this.gameStarted) {
  99. // 游戏未开始,不设置冷却
  100. return;
  101. }
  102. const currentTime = Date.now() / 1000; // 转换为秒
  103. const cooldownEndTime = currentTime + this.blockMoveCooldown;
  104. // 设置全局冷却
  105. this.globalCooldownEndTime = cooldownEndTime;
  106. }
  107. // 清除所有冷却(游戏重置时调用)
  108. public clearAllCooldowns() {
  109. this.blockCooldowns.clear();
  110. this.globalCooldownEndTime = 0;
  111. }
  112. // 递归查找Weapon节点
  113. private findWeaponNode(node: Node): Node | null {
  114. // 直接检查当前节点的子节点
  115. const weapon = node.getChildByName('Weapon');
  116. if (weapon) {
  117. return weapon;
  118. }
  119. // 递归查找所有子节点
  120. for (let i = 0; i < node.children.length; i++) {
  121. const child = node.children[i];
  122. const found = this.findWeaponNode(child);
  123. if (found) {
  124. return found;
  125. }
  126. }
  127. return null;
  128. }
  129. // 发射子弹
  130. public fireBullet(blockNode: Node) {
  131. console.log('发射子弹!击中方块:', blockNode.name);
  132. // 检查冷却状态
  133. if (!this.canFireBullet) {
  134. console.log('子弹冷却中,无法发射');
  135. return;
  136. }
  137. // 检查是否已经对这个方块发射过子弹
  138. const blockId = blockNode.uuid;
  139. if (this.collidedBlockIds.has(blockId)) {
  140. console.log('已经对此方块发射过子弹');
  141. return;
  142. }
  143. // 记录此方块ID
  144. this.collidedBlockIds.add(blockId);
  145. // 设置冷却
  146. this.canFireBullet = false;
  147. this.scheduleOnce(() => {
  148. this.canFireBullet = true;
  149. // 清理碰撞记录
  150. this.collidedBlockIds.clear();
  151. }, this.bulletCooldown);
  152. // 检查是否有子弹预制体
  153. if (!this.bulletPrefab) {
  154. console.error('未设置子弹预制体');
  155. return;
  156. } else {
  157. console.log('子弹预制体已设置:', this.bulletPrefab.name);
  158. }
  159. // 查找Weapon节点 - 递归查找,因为Weapon可能在B1子节点下
  160. let weaponNode = this.findWeaponNode(blockNode);
  161. if (!weaponNode) {
  162. console.warn(`方块 ${blockNode.name} 没有找到Weapon子节点,创建一个`);
  163. // 如果没有找到Weapon节点,在B1节点下创建一个
  164. const b1Node = blockNode.getChildByName('B1');
  165. if (b1Node) {
  166. weaponNode = new Node('Weapon');
  167. // 确保Weapon节点有UITransform组件
  168. if (!weaponNode.getComponent(UITransform)) {
  169. weaponNode.addComponent(UITransform);
  170. }
  171. // 将Weapon添加到B1节点中
  172. b1Node.addChild(weaponNode);
  173. // 放置在B1中心
  174. weaponNode.position = new Vec3(0, 0, 0);
  175. } else {
  176. // 如果连B1节点都没有,直接添加到方块根节点
  177. weaponNode = new Node('Weapon');
  178. // 确保Weapon节点有UITransform组件
  179. if (!weaponNode.getComponent(UITransform)) {
  180. weaponNode.addComponent(UITransform);
  181. }
  182. // 将Weapon添加到方块中
  183. blockNode.addChild(weaponNode);
  184. // 放置在方块中心
  185. weaponNode.position = new Vec3(0, 0, 0);
  186. }
  187. } else {
  188. console.log(`找到方块 ${blockNode.name} 的Weapon子节点:`, weaponNode.name);
  189. }
  190. // 实例化子弹
  191. const bullet = instantiate(this.bulletPrefab);
  192. // 将子弹添加到GameArea中
  193. const gameArea = find('Canvas/GameLevelUI/GameArea');
  194. if (gameArea) {
  195. gameArea.addChild(bullet);
  196. // 设置子弹初始位置为Weapon节点的位置
  197. const weaponWorldPos = weaponNode.worldPosition;
  198. bullet.worldPosition = weaponWorldPos;
  199. // 子弹已经有BulletController组件,不需要再添加
  200. // 让子弹自己找到最近的敌人并攻击
  201. console.log('子弹已创建,将自动寻找并攻击最近的敌人');
  202. }
  203. }
  204. start() {
  205. // 如果没有指定GridContainer,尝试找到它
  206. if (!this.gridContainer) {
  207. this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
  208. if (!this.gridContainer) {
  209. console.error('找不到GridContainer节点');
  210. return;
  211. }
  212. }
  213. // 如果没有指定kuangContainer,尝试找到它
  214. if (!this.kuangContainer) {
  215. this.kuangContainer = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
  216. if (!this.kuangContainer) {
  217. console.error('找不到kuang节点');
  218. return;
  219. }
  220. }
  221. // 如果没有指定coinLabelNode,尝试找到它
  222. if (!this.coinLabelNode) {
  223. this.coinLabelNode = find('Canvas/GameLevelUI/CoinNode/CoinLabel');
  224. if (!this.coinLabelNode) {
  225. console.error('找不到CoinLabel节点');
  226. return;
  227. }
  228. }
  229. // 确保有PlacedBlocks节点用于存放已放置的方块
  230. this.ensurePlacedBlocksNode();
  231. // 初始化玩家金币显示
  232. this.updateCoinDisplay();
  233. // 初始化网格信息
  234. this.initGridInfo();
  235. // 初始化网格占用情况
  236. this.initGridOccupationMap();
  237. // 在kuang下随机生成三个方块
  238. this.generateRandomBlocksInKuang();
  239. // 注册碰撞回调
  240. const collider = this.getComponent(Collider2D);
  241. if (collider) {
  242. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  243. console.log('方块碰撞监听器已注册');
  244. }
  245. }
  246. // 确保有PlacedBlocks节点
  247. ensurePlacedBlocksNode() {
  248. const canvas = find('Canvas');
  249. if (!canvas) {
  250. console.error('找不到Canvas节点');
  251. return;
  252. }
  253. let placedBlocksNode = find('Canvas/PlacedBlocks');
  254. if (!placedBlocksNode) {
  255. placedBlocksNode = new Node('PlacedBlocks');
  256. canvas.addChild(placedBlocksNode);
  257. if (!placedBlocksNode.getComponent(UITransform)) {
  258. placedBlocksNode.addComponent(UITransform);
  259. }
  260. console.log('已创建PlacedBlocks节点');
  261. }
  262. }
  263. // 初始化网格信息
  264. initGridInfo() {
  265. if (!this.gridContainer || this.gridInitialized) return;
  266. this.gridNodes = [];
  267. for (let row = 0; row < this.GRID_ROWS; row++) {
  268. this.gridNodes[row] = [];
  269. }
  270. for (let i = 0; i < this.gridContainer.children.length; i++) {
  271. const grid = this.gridContainer.children[i];
  272. if (grid.name.startsWith('Grid_')) {
  273. const parts = grid.name.split('_');
  274. if (parts.length === 3) {
  275. const row = parseInt(parts[1]);
  276. const col = parseInt(parts[2]);
  277. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  278. this.gridNodes[row][col] = grid;
  279. }
  280. }
  281. }
  282. }
  283. if (this.GRID_ROWS > 1 && this.GRID_COLS > 0) {
  284. if (this.gridNodes[0][0] && this.gridNodes[1][0]) {
  285. const pos1 = this.gridNodes[0][0].position;
  286. const pos2 = this.gridNodes[1][0].position;
  287. this.gridSpacing = Math.abs(pos2.y - pos1.y);
  288. }
  289. }
  290. this.gridInitialized = true;
  291. }
  292. // 初始化网格占用情况
  293. initGridOccupationMap() {
  294. this.gridOccupationMap = [];
  295. for (let row = 0; row < this.GRID_ROWS; row++) {
  296. const rowArray: number[] = [];
  297. for (let col = 0; col < this.GRID_COLS; col++) {
  298. rowArray.push(0);
  299. }
  300. this.gridOccupationMap.push(rowArray);
  301. }
  302. }
  303. // 在kuang下随机生成三个方块
  304. private generateRandomBlocksInKuang() {
  305. this.clearBlocks();
  306. if (this.blockPrefabs.length === 0) {
  307. console.error('没有可用的预制体');
  308. return;
  309. }
  310. const kuangNode = this.kuangContainer;
  311. if (!kuangNode) {
  312. console.error('找不到kuang节点');
  313. return;
  314. }
  315. const offsets = [
  316. new Vec3(-200, 0, 0),
  317. new Vec3(0, 0, 0),
  318. new Vec3(200, 0, 0)
  319. ];
  320. const dbNodes = [
  321. kuangNode.getChildByName('db01'),
  322. kuangNode.getChildByName('db02'),
  323. kuangNode.getChildByName('db03')
  324. ];
  325. console.log('开始在kuang容器中生成随机方块');
  326. for (let i = 0; i < 3; i++) {
  327. const randomIndex = Math.floor(Math.random() * this.blockPrefabs.length);
  328. const prefab = this.blockPrefabs[randomIndex];
  329. if (!prefab) {
  330. console.error(`方块预制体索引 ${randomIndex} 无效`);
  331. continue;
  332. }
  333. const block = instantiate(prefab);
  334. kuangNode.addChild(block);
  335. block.position = offsets[i];
  336. this.originalPositions.set(block, offsets[i].clone());
  337. this.blockLocations.set(block, 'kuang');
  338. this.blocks.push(block);
  339. if (dbNodes[i]) {
  340. const priceNode = dbNodes[i].getChildByName('Price');
  341. if (priceNode) {
  342. this.blockPriceMap.set(block, priceNode);
  343. priceNode.active = true;
  344. }
  345. this.associateDbNodeWithBlock(block, dbNodes[i]);
  346. }
  347. this.setupDragEvents(block);
  348. console.log(`生成方块 ${i + 1}/3: ${prefab.name} 在位置 (${offsets[i].x.toFixed(2)}, ${offsets[i].y.toFixed(2)})`);
  349. }
  350. console.log(`成功在kuang容器中生成了 ${this.blocks.length} 个方块`);
  351. this.updateCoinDisplay();
  352. }
  353. // 将db节点与方块关联
  354. associateDbNodeWithBlock(block: Node, dbNode: Node) {
  355. block['dbNode'] = dbNode;
  356. block.on(Node.EventType.TRANSFORM_CHANGED, () => {
  357. if (dbNode && block.parent) {
  358. const location = this.blockLocations.get(block);
  359. if (location === 'grid') {
  360. dbNode.active = false;
  361. return;
  362. }
  363. dbNode.active = true;
  364. const worldPos = block.parent.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  365. const localPos = dbNode.parent.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
  366. dbNode.position = new Vec3(localPos.x, localPos.y - 80, localPos.z);
  367. }
  368. });
  369. }
  370. // 更新金币显示
  371. updateCoinDisplay() {
  372. if (this.coinLabelNode) {
  373. const label = this.coinLabelNode.getComponent(Label);
  374. if (label) {
  375. label.string = this.playerCoins.toString();
  376. }
  377. }
  378. }
  379. // 获取方块价格
  380. getBlockPrice(block: Node): number {
  381. const priceNode = this.blockPriceMap.get(block);
  382. if (priceNode) {
  383. const label = priceNode.getComponent(Label);
  384. if (label) {
  385. const price = parseInt(label.string);
  386. if (!isNaN(price)) {
  387. return price;
  388. }
  389. }
  390. }
  391. return 50;
  392. }
  393. // 隐藏价格标签
  394. hidePriceLabel(block: Node) {
  395. const priceNode = this.blockPriceMap.get(block);
  396. if (priceNode) {
  397. priceNode.active = false;
  398. }
  399. }
  400. // 显示价格标签
  401. showPriceLabel(block: Node) {
  402. const priceNode = this.blockPriceMap.get(block);
  403. if (priceNode) {
  404. priceNode.active = true;
  405. }
  406. }
  407. // 扣除玩家金币
  408. deductPlayerCoins(amount: number): boolean {
  409. if (this.playerCoins >= amount) {
  410. this.playerCoins -= amount;
  411. this.updateCoinDisplay();
  412. return true;
  413. }
  414. return false;
  415. }
  416. // 归还玩家金币
  417. refundPlayerCoins(amount: number) {
  418. this.playerCoins += amount;
  419. this.updateCoinDisplay();
  420. }
  421. // 设置拖拽事件
  422. setupDragEvents(block: Node) {
  423. block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  424. if (this.gameStarted && this.blockLocations.get(block) === 'grid') {
  425. if (!this.canMoveBlock(block)) {
  426. return;
  427. }
  428. }
  429. this.currentDragBlock = block;
  430. this.startPos = event.getUILocation();
  431. this.blockStartPos.set(block.position);
  432. this.currentDragBlock['startLocation'] = this.blockLocations.get(block);
  433. block.setSiblingIndex(block.parent.children.length - 1);
  434. this.tempStoreBlockOccupiedGrids(block);
  435. }, this);
  436. block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  437. if (this.gameStarted && this.blockLocations.get(block) === 'grid') {
  438. if (!this.canMoveBlock(block)) {
  439. return;
  440. }
  441. }
  442. if (!this.currentDragBlock) return;
  443. const location = event.getUILocation();
  444. const deltaX = location.x - this.startPos.x;
  445. const deltaY = location.y - this.startPos.y;
  446. this.currentDragBlock.position = new Vec3(
  447. this.blockStartPos.x + deltaX,
  448. this.blockStartPos.y + deltaY,
  449. this.blockStartPos.z
  450. );
  451. }, this);
  452. block.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
  453. if (this.gameStarted && this.blockLocations.get(block) === 'grid') {
  454. if (!this.canMoveBlock(block)) {
  455. return;
  456. }
  457. }
  458. if (this.currentDragBlock) {
  459. this.handleBlockDrop(event);
  460. // 如果成功移动且游戏已开始,设置冷却
  461. if (this.gameStarted && this.blockLocations.get(this.currentDragBlock) === 'grid') {
  462. this.setBlockCooldown(this.currentDragBlock);
  463. }
  464. this.currentDragBlock = null;
  465. }
  466. }, this);
  467. block.on(Node.EventType.TOUCH_CANCEL, () => {
  468. if (this.currentDragBlock) {
  469. this.returnBlockToOriginalPosition();
  470. this.currentDragBlock = null;
  471. }
  472. }, this);
  473. }
  474. // 处理方块放下
  475. handleBlockDrop(event: EventTouch) {
  476. const touchPos = event.getUILocation();
  477. const startLocation = this.currentDragBlock['startLocation'];
  478. if (this.isInKuangArea(touchPos)) {
  479. this.returnBlockToKuang(startLocation);
  480. } else if (this.tryPlaceBlockToGrid(this.currentDragBlock)) {
  481. this.handleSuccessfulPlacement(startLocation);
  482. console.log('成功放置,移动到网格');
  483. } else {
  484. this.returnBlockToOriginalPosition();
  485. }
  486. }
  487. // 返回方块到kuang区域
  488. returnBlockToKuang(startLocation: string) {
  489. const originalPos = this.originalPositions.get(this.currentDragBlock);
  490. if (originalPos) {
  491. const kuangNode = this.kuangContainer;
  492. if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
  493. this.currentDragBlock.removeFromParent();
  494. kuangNode.addChild(this.currentDragBlock);
  495. }
  496. this.currentDragBlock.position = originalPos.clone();
  497. }
  498. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  499. this.blockLocations.set(this.currentDragBlock, 'kuang');
  500. this.showPriceLabel(this.currentDragBlock);
  501. if (startLocation === 'grid') {
  502. const price = this.getBlockPrice(this.currentDragBlock);
  503. this.refundPlayerCoins(price);
  504. this.currentDragBlock['placedBefore'] = false;
  505. }
  506. const dbNode = this.currentDragBlock['dbNode'];
  507. if (dbNode) {
  508. dbNode.active = true;
  509. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  510. }
  511. }
  512. // 处理成功放置
  513. handleSuccessfulPlacement(startLocation: string) {
  514. const price = this.getBlockPrice(this.currentDragBlock);
  515. if (startLocation === 'grid') {
  516. this.clearTempStoredOccupiedGrids(this.currentDragBlock);
  517. this.blockLocations.set(this.currentDragBlock, 'grid');
  518. this.hidePriceLabel(this.currentDragBlock);
  519. const dbNode = this.currentDragBlock['dbNode'];
  520. if (dbNode) {
  521. dbNode.active = false;
  522. }
  523. // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
  524. this.moveBlockToPlacedBlocks(this.currentDragBlock);
  525. // 如果游戏已开始,添加锁定视觉提示
  526. if (this.gameStarted) {
  527. this.addLockedVisualHint(this.currentDragBlock);
  528. }
  529. } else {
  530. if (this.deductPlayerCoins(price)) {
  531. this.clearTempStoredOccupiedGrids(this.currentDragBlock);
  532. this.blockLocations.set(this.currentDragBlock, 'grid');
  533. this.hidePriceLabel(this.currentDragBlock);
  534. const dbNode = this.currentDragBlock['dbNode'];
  535. if (dbNode) {
  536. dbNode.active = false;
  537. }
  538. this.currentDragBlock['placedBefore'] = true;
  539. // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
  540. this.moveBlockToPlacedBlocks(this.currentDragBlock);
  541. // 如果游戏已开始,添加锁定视觉提示
  542. if (this.gameStarted) {
  543. this.addLockedVisualHint(this.currentDragBlock);
  544. }
  545. } else {
  546. this.returnBlockToOriginalPosition();
  547. }
  548. }
  549. }
  550. // 返回方块到原位置
  551. returnBlockToOriginalPosition() {
  552. const currentLocation = this.blockLocations.get(this.currentDragBlock);
  553. if (currentLocation === 'kuang') {
  554. const originalPos = this.originalPositions.get(this.currentDragBlock);
  555. if (originalPos) {
  556. this.currentDragBlock.position = originalPos.clone();
  557. }
  558. } else {
  559. this.currentDragBlock.position = this.blockStartPos.clone();
  560. }
  561. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  562. this.showPriceLabel(this.currentDragBlock);
  563. const dbNode = this.currentDragBlock['dbNode'];
  564. if (dbNode) {
  565. dbNode.active = true;
  566. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  567. }
  568. }
  569. // 检查是否在kuang区域内
  570. isInKuangArea(touchPos: Vec2): boolean {
  571. if (!this.kuangContainer) return false;
  572. const kuangTransform = this.kuangContainer.getComponent(UITransform);
  573. if (!kuangTransform) return false;
  574. const kuangBoundingBox = new Rect(
  575. this.kuangContainer.worldPosition.x - kuangTransform.width * kuangTransform.anchorX,
  576. this.kuangContainer.worldPosition.y - kuangTransform.height * kuangTransform.anchorY,
  577. kuangTransform.width,
  578. kuangTransform.height
  579. );
  580. return kuangBoundingBox.contains(new Vec2(touchPos.x, touchPos.y));
  581. }
  582. // 临时保存方块占用的网格
  583. tempStoreBlockOccupiedGrids(block: Node) {
  584. const occupiedGrids = block['occupiedGrids'];
  585. if (!occupiedGrids || occupiedGrids.length === 0) return;
  586. this.tempRemovedOccupiedGrids.push({
  587. block: block,
  588. occupiedGrids: [...occupiedGrids]
  589. });
  590. for (const grid of occupiedGrids) {
  591. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  592. grid.col >= 0 && grid.col < this.GRID_COLS) {
  593. this.gridOccupationMap[grid.row][grid.col] = 0;
  594. }
  595. }
  596. block['occupiedGrids'] = [];
  597. }
  598. // 恢复方块原来的占用状态
  599. restoreBlockOccupiedGrids(block: Node) {
  600. const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block);
  601. if (index === -1) return;
  602. const savedItem = this.tempRemovedOccupiedGrids[index];
  603. for (const grid of savedItem.occupiedGrids) {
  604. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  605. grid.col >= 0 && grid.col < this.GRID_COLS) {
  606. this.gridOccupationMap[grid.row][grid.col] = 1;
  607. }
  608. }
  609. block['occupiedGrids'] = [...savedItem.occupiedGrids];
  610. this.tempRemovedOccupiedGrids.splice(index, 1);
  611. }
  612. // 清除临时保存的占用状态
  613. clearTempStoredOccupiedGrids(block: Node) {
  614. const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block);
  615. if (index === -1) return;
  616. this.tempRemovedOccupiedGrids.splice(index, 1);
  617. }
  618. // 尝试将方块放置到网格中
  619. tryPlaceBlockToGrid(block: Node): boolean {
  620. if (!this.gridContainer || !this.gridInitialized) return false;
  621. let b1Node = block;
  622. if (block.name !== 'B1') {
  623. b1Node = block.getChildByName('B1');
  624. if (!b1Node) {
  625. return false;
  626. }
  627. }
  628. const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position);
  629. const gridPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos);
  630. const gridSize = this.gridContainer.getComponent(UITransform).contentSize;
  631. const halfWidth = gridSize.width / 2;
  632. const halfHeight = gridSize.height / 2;
  633. const tolerance = this.gridSpacing * 0.5;
  634. if (gridPos.x < -halfWidth - tolerance || gridPos.x > halfWidth + tolerance ||
  635. gridPos.y < -halfHeight - tolerance || gridPos.y > halfHeight + tolerance) {
  636. return false;
  637. }
  638. const nearestGrid = this.findNearestGridNode(gridPos);
  639. if (!nearestGrid) {
  640. return false;
  641. }
  642. return this.tryPlaceBlockToSpecificGrid(block, nearestGrid);
  643. }
  644. // 找到最近的网格节点
  645. findNearestGridNode(position: Vec3): Node {
  646. if (!this.gridContainer || !this.gridInitialized) return null;
  647. let nearestNode: Node = null;
  648. let minDistance = Number.MAX_VALUE;
  649. for (let row = 0; row < this.GRID_ROWS; row++) {
  650. for (let col = 0; col < this.GRID_COLS; col++) {
  651. const grid = this.gridNodes[row][col];
  652. if (grid) {
  653. const distance = Vec3.distance(position, grid.position);
  654. if (distance < minDistance) {
  655. minDistance = distance;
  656. nearestNode = grid;
  657. }
  658. }
  659. }
  660. }
  661. if (minDistance > this.gridSpacing * 2) {
  662. return null;
  663. }
  664. return nearestNode;
  665. }
  666. // 尝试将方块放置到指定的网格节点
  667. tryPlaceBlockToSpecificGrid(block: Node, targetGrid: Node): boolean {
  668. let b1Node = block;
  669. if (block.name !== 'B1') {
  670. b1Node = block.getChildByName('B1');
  671. if (!b1Node) {
  672. return false;
  673. }
  674. }
  675. if (!this.canPlaceBlockAt(block, targetGrid)) {
  676. return false;
  677. }
  678. const gridCenterWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(targetGrid.position);
  679. const targetWorldPos = gridCenterWorldPos.clone();
  680. const b1LocalPos = b1Node.position.clone();
  681. let rootTargetWorldPos;
  682. if (b1Node === block) {
  683. rootTargetWorldPos = targetWorldPos.clone();
  684. } else {
  685. rootTargetWorldPos = new Vec3(
  686. targetWorldPos.x - b1LocalPos.x,
  687. targetWorldPos.y - b1LocalPos.y,
  688. targetWorldPos.z
  689. );
  690. }
  691. const rootTargetLocalPos = block.parent.getComponent(UITransform).convertToNodeSpaceAR(rootTargetWorldPos);
  692. block.position = rootTargetLocalPos;
  693. this.markOccupiedPositions(block, targetGrid);
  694. return true;
  695. }
  696. // 检查方块是否可以放置在指定位置
  697. canPlaceBlockAt(block: Node, targetGrid: Node): boolean {
  698. if (!this.gridInitialized) return false;
  699. const targetRowCol = this.getGridRowCol(targetGrid);
  700. if (!targetRowCol) return false;
  701. const parts = this.getBlockParts(block);
  702. for (const part of parts) {
  703. const row = targetRowCol.row - part.y;
  704. const col = targetRowCol.col + part.x;
  705. if (row < 0 || row >= this.GRID_ROWS || col < 0 || col >= this.GRID_COLS) {
  706. return false;
  707. }
  708. if (this.gridOccupationMap[row][col] === 1) {
  709. return false;
  710. }
  711. }
  712. return true;
  713. }
  714. // 获取网格行列索引
  715. getGridRowCol(gridNode: Node): { row: number, col: number } | null {
  716. if (!gridNode || !gridNode.name.startsWith('Grid_')) return null;
  717. const parts = gridNode.name.split('_');
  718. if (parts.length === 3) {
  719. const row = parseInt(parts[1]);
  720. const col = parseInt(parts[2]);
  721. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  722. return { row, col };
  723. }
  724. }
  725. return null;
  726. }
  727. // 获取方块的所有部分节点及其相对坐标
  728. getBlockParts(block: Node): { node: Node, x: number, y: number }[] {
  729. const parts: { node: Node, x: number, y: number }[] = [];
  730. parts.push({ node: block, x: 0, y: 0 });
  731. this.findBlockParts(block, parts, 0, 0);
  732. return parts;
  733. }
  734. // 递归查找方块的所有部分
  735. findBlockParts(node: Node, result: { node: Node, x: number, y: number }[], parentX: number, parentY: number) {
  736. for (let i = 0; i < node.children.length; i++) {
  737. const child = node.children[i];
  738. if (this.NON_BLOCK_NODES.indexOf(child.name) !== -1) {
  739. continue;
  740. }
  741. let x = parentX;
  742. let y = parentY;
  743. const match = child.name.match(/^\((-?\d+),(-?\d+)\)$/);
  744. if (match) {
  745. x = parseInt(match[1]);
  746. y = parseInt(match[2]);
  747. result.push({ node: child, x, y });
  748. } else if (child.name.startsWith('B')) {
  749. const relativeX = Math.round(child.position.x / this.gridSpacing);
  750. const relativeY = -Math.round(child.position.y / this.gridSpacing);
  751. x = parentX + relativeX;
  752. y = parentY + relativeY;
  753. result.push({ node: child, x, y });
  754. }
  755. this.findBlockParts(child, result, x, y);
  756. }
  757. }
  758. // 标记方块占用的格子
  759. markOccupiedPositions(block: Node, targetGrid: Node) {
  760. if (!this.gridInitialized) return;
  761. const targetRowCol = this.getGridRowCol(targetGrid);
  762. if (!targetRowCol) return;
  763. const parts = this.getBlockParts(block);
  764. block['occupiedGrids'] = [];
  765. for (const part of parts) {
  766. const row = targetRowCol.row - part.y;
  767. const col = targetRowCol.col + part.x;
  768. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  769. this.gridOccupationMap[row][col] = 1;
  770. block['occupiedGrids'] = block['occupiedGrids'] || [];
  771. block['occupiedGrids'].push({ row, col });
  772. }
  773. }
  774. }
  775. // 清除方块
  776. clearBlocks() {
  777. const blocksToRemove = [];
  778. for (const block of this.blocks) {
  779. if (block.isValid) {
  780. const location = this.blockLocations.get(block);
  781. if (location === 'kuang') {
  782. blocksToRemove.push(block);
  783. }
  784. }
  785. }
  786. for (const block of blocksToRemove) {
  787. const dbNode = block['dbNode'];
  788. if (dbNode && dbNode.isValid) {
  789. block.off(Node.EventType.TRANSFORM_CHANGED);
  790. const kuangNode = this.kuangContainer;
  791. if (kuangNode) {
  792. const dbName = dbNode.name;
  793. if (!kuangNode.getChildByName(dbName)) {
  794. dbNode.parent = kuangNode;
  795. }
  796. }
  797. }
  798. const index = this.blocks.indexOf(block);
  799. if (index !== -1) {
  800. this.blocks.splice(index, 1);
  801. }
  802. this.originalPositions.delete(block);
  803. this.blockLocations.delete(block);
  804. this.blockPriceMap.delete(block);
  805. block.destroy();
  806. }
  807. }
  808. // 游戏开始时调用
  809. onGameStart() {
  810. this.gameStarted = true;
  811. console.log('游戏已开始,已放置的方块将有移动冷却时间');
  812. for (const block of this.blocks) {
  813. if (block.isValid) {
  814. const location = this.blockLocations.get(block);
  815. if (location === 'grid') {
  816. this.hidePriceLabel(block);
  817. const dbNode = block['dbNode'];
  818. if (dbNode) {
  819. dbNode.active = false;
  820. }
  821. this.moveBlockToPlacedBlocks(block);
  822. this.addLockedVisualHint(block);
  823. }
  824. }
  825. }
  826. }
  827. // 游戏重置时调用
  828. onGameReset() {
  829. this.gameStarted = false;
  830. this.clearAllCooldowns();
  831. console.log('游戏已重置,方块可以自由移动');
  832. }
  833. // 添加视觉提示,表明方块已锁定
  834. addLockedVisualHint(block: Node) {
  835. const children = block.children;
  836. for (let i = 0; i < children.length; i++) {
  837. const child = children[i];
  838. if (this.NON_BLOCK_NODES.indexOf(child.name) !== -1) {
  839. continue;
  840. }
  841. child.setScale(new Vec3(0.95, 0.95, 1));
  842. }
  843. }
  844. // 将方块移动到PlacedBlocks节点下
  845. moveBlockToPlacedBlocks(block: Node) {
  846. const placedBlocksNode = find('Canvas/PlacedBlocks');
  847. if (!placedBlocksNode) {
  848. console.error('找不到PlacedBlocks节点');
  849. return;
  850. }
  851. const worldPosition = new Vec3();
  852. block.getWorldPosition(worldPosition);
  853. block.removeFromParent();
  854. placedBlocksNode.addChild(block);
  855. block.setWorldPosition(worldPosition);
  856. }
  857. // 碰撞回调
  858. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  859. console.log('方块碰到了:', otherCollider.node.name);
  860. // 如果碰到球,发射子弹
  861. if (otherCollider.node.name === 'Ball') {
  862. console.log('方块被球击中!');
  863. // 获取被击中的方块节点
  864. const hitBlock = selfCollider.node;
  865. // 检查是否是已放置的方块(在网格中的方块)
  866. const blockLocation = this.blockLocations.get(hitBlock);
  867. if (blockLocation === 'grid') {
  868. // 发射子弹
  869. this.fireBullet(hitBlock);
  870. } else {
  871. console.log('只有已放置在网格中的方块才能发射子弹');
  872. }
  873. }
  874. }
  875. }