BlockManager.ts 37 KB

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