Block.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. import { _decorator, Component, Event, EventTouch, Node, SpriteFrame, UITransform, v3, Sprite, director, Color, tween, Prefab, instantiate, Vec3, Animation, Quat, sp, SkeletalAnimation, Skeleton, assetManager } from 'cc';
  2. import { BlockLink } from './BlockLink';
  3. import { LvData, LvDir } from '../../LvData/LvData';
  4. import { blockMap } from './blockMap';
  5. import { gui } from 'db://assets/core/ui/ui';
  6. import ch_audio from 'db://assets/ch/audio/audio';
  7. import { audioManager } from '../../../Audio/AudioManager';
  8. const { ccclass, property } = _decorator;
  9. export enum BlockState {
  10. Normal = "normal", // 默认
  11. Selected = "selected", // 被选中
  12. Matched = "matched", // 已匹配
  13. Unmatched = "unmatched" // 未匹配
  14. }
  15. @ccclass('Block')
  16. export class Block extends Component {
  17. @property(Node)
  18. icon: Node | null = null;
  19. @property(Prefab)
  20. BOOM: Prefab | null = null;
  21. @property(Node)
  22. XZ: Node;
  23. @property(Node)
  24. BoomNode: Node
  25. @property(Sprite)
  26. BlockSprite: Sprite;
  27. @property(sp.Skeleton)
  28. biu: sp.Skeleton;
  29. //当前方块状态
  30. public stateBlock: BlockState = BlockState.Normal;
  31. //第一次匹配的方块con
  32. private static firstSelectedBlock: Block | null = null;
  33. private isSpecialEffectHandled: boolean = false;
  34. mapConfig: blockMap = new blockMap();
  35. private blockLink: BlockLink | null = null;
  36. start() {
  37. this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
  38. this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
  39. this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
  40. this.biu = this.node.getChildByName('biu').getComponent(sp.Skeleton)
  41. this.biu.node.active = false;
  42. this.blockLink = this.node.parent.getComponent(BlockLink);
  43. }
  44. onTouchStart(event: EventTouch) {
  45. console.log('touch start');
  46. if (this.blockLink.isMatching || this.blockLink.isMoving) return; // 正在匹配时拒绝新点击||正在移动时拒绝新点击
  47. audioManager.playOneShot('sound/onblcok');
  48. gui.scale_anim(this.node, 0.2, 0.3, 1);
  49. gui.scale_shake_anim(this.node, 0.2, 0.3, 2, 1);
  50. this.blockLink.sleeBlock = this.node;
  51. // 允许在选中状态下再次点击自身
  52. if (this.stateBlock === BlockState.Selected && Block.firstSelectedBlock === this) {
  53. // 点击已选中的自身方块,取消选中
  54. this.stateBlock = BlockState.Normal;
  55. this.applyStateStyle();
  56. Block.firstSelectedBlock = null;
  57. this.blockLink.sleeBlock = null;
  58. return;
  59. }
  60. if (this.stateBlock
  61. !== BlockState.Normal) return;
  62. this.stateBlock
  63. = BlockState.Selected;
  64. this.applyStateStyle();
  65. if (Block.firstSelectedBlock) {
  66. this.attemptMatch(Block.firstSelectedBlock);
  67. } else {
  68. Block.firstSelectedBlock = this;
  69. }
  70. }
  71. onTouchEnd(event: EventTouch) {
  72. }
  73. onTouchCancel(event: EventTouch) {
  74. // 触摸取消,恢复状态
  75. if (this.stateBlock
  76. === BlockState.Selected) {
  77. this.stateBlock
  78. = BlockState.Normal;
  79. this.applyStateStyle();
  80. Block.firstSelectedBlock = null;
  81. // this.blockLink.isMatching=false
  82. }
  83. }
  84. attemptMatch(otherBlock: Block) {
  85. if (!this.isMatch(otherBlock)) {
  86. this.handleMatchFailed(otherBlock);
  87. return;
  88. }
  89. // 获取BlockLink组件
  90. const comp = this.node.parent.getComponent(BlockLink);
  91. if (!comp) {
  92. console.error('BlockLink component not found!');
  93. this.handleMatchFailed(otherBlock);
  94. return;
  95. }
  96. // 判断连接成功or失败
  97. if (comp.isConnected(this.node, otherBlock.node)) {
  98. this.handleMatchSuccess(otherBlock);
  99. } else {
  100. this.handleMatchFailed(otherBlock);
  101. }
  102. }
  103. //连接失败
  104. handleMatchFailed(otherBlock: Block) {
  105. // this.blockLink.isMatching = true;
  106. Block.firstSelectedBlock = this;
  107. this.stateBlock
  108. = BlockState.Selected;
  109. otherBlock.stateBlock
  110. = BlockState.Unmatched;
  111. // 0.5秒后恢复普通状态
  112. // this.scheduleOnce(() => {
  113. if (otherBlock.node && otherBlock.node.isValid) { // 检查 otherBlock.node 是否存在
  114. otherBlock.stateBlock
  115. = BlockState.Normal;
  116. otherBlock.applyStateStyle();
  117. }
  118. // }, 0.5);
  119. }
  120. onDestroy() {
  121. this.unscheduleAllCallbacks(); // 取消所有定时器
  122. }
  123. //连接成功
  124. handleMatchSuccess(otherBlock: Block) {
  125. // 检查两个方块是否都是特殊方块
  126. const isSpecialBlock = this.isSpecialBlock() && otherBlock.isSpecialBlock();
  127. if (isSpecialBlock) {
  128. // 执行特殊效果
  129. this.handleSpecialEffect(otherBlock);
  130. Block.firstSelectedBlock = null;
  131. this.blockLink.isMatching = false;
  132. return;
  133. }
  134. // 非特殊方块的匹配逻辑保持不变
  135. this.blockLink.isMatching = true;
  136. this.stateBlock = BlockState.Matched;
  137. otherBlock.stateBlock = BlockState.Matched;
  138. this.applyStateStyle();
  139. // 更新游戏数据
  140. const comp = this.node.parent.getComponent(BlockLink);
  141. const pos1 = comp.getRowCol(this.node.position);
  142. const pos2 = comp.getRowCol(otherBlock.node.position);
  143. BlockLink.blockArry[pos1.i][pos1.j] = null;
  144. BlockLink.blockArry[pos2.i][pos2.j] = null;
  145. // 延迟移除节点,等待动画完成
  146. this.scheduleOnce(() => {
  147. audioManager.playOneShot('sound/offblock');
  148. this.node.destroy();
  149. otherBlock.node.destroy();
  150. Block.firstSelectedBlock = null;
  151. this.blockLink.isMatching = false;
  152. const dir = LvData.instance.dir;
  153. if (dir !== LvDir.none) {
  154. comp.moveByLevelDirection(dir);
  155. }
  156. // 动画完成后重新开启全局触摸
  157. // Block.setGlobalTouchEnabled(true);
  158. }, 0.5);
  159. BlockLink.remainingBlocks--;
  160. }
  161. //选中方块效果
  162. applyStateStyle() {
  163. if (!this.biu || !this.biu.node) return;
  164. const spriteComp = this.node?.getComponent(Sprite);
  165. const biuanim = this.node.getChildByName('biu').getComponent(sp.Skeleton)
  166. if (!spriteComp) return;
  167. switch (this.stateBlock
  168. ) {
  169. case BlockState.Selected:
  170. this.biu.node.active = true;
  171. biuanim.setAnimation(0, "animation", true);
  172. // 选中效果
  173. // spriteComp.color = new Color(255, 255, 0); // 黄色高亮
  174. break;
  175. case BlockState.Unmatched:
  176. // 不匹配效果
  177. this.biu.node.active = false;
  178. biuanim.setAnimation(0, "animation", false);
  179. // spriteComp.color = new Color(255, 0, 0)
  180. break;
  181. case BlockState.Normal:
  182. if (this.biu && this.biu.node) {
  183. this.biu.node.active = false;
  184. biuanim.setAnimation(0, "animation", false);
  185. }
  186. break;
  187. }
  188. }
  189. //图片对比
  190. isMatch(otherBlock: Block): boolean {
  191. // 防御性检查
  192. if (!this.icon || !otherBlock.icon) return false;
  193. const currentSprite = this.icon.getComponent(Sprite);
  194. const otherSprite = otherBlock.icon.getComponent(Sprite);
  195. if (!currentSprite || !otherSprite) return false;
  196. return currentSprite.spriteFrame === otherSprite.spriteFrame;
  197. }
  198. // 重置方块状态
  199. public resetState() {
  200. this.stateBlock
  201. = BlockState.Normal;
  202. this.applyStateStyle();
  203. }
  204. // 判断特殊方块
  205. private isSpecialBlock(): boolean {
  206. if (!this.icon) return false;
  207. const spriteComp = this.icon.getComponent(Sprite);
  208. if (!spriteComp || !spriteComp.spriteFrame) return false;
  209. return spriteComp.spriteFrame.name.includes("zd");
  210. }
  211. //高亮显示
  212. highlight(node1: Node, node2: Node) {
  213. if (!node1 || !node1.isValid) return;
  214. node1.getComponent(Sprite).color = new Color(0, 255, 0);
  215. node2.getComponent(Sprite).color = new Color(0, 255, 0);
  216. }
  217. // 点击炸弹处理
  218. private handleSpecialEffect(otherBlock: Block) {
  219. if (!this.node.isValid) return;
  220. if (this.isSpecialEffectHandled) {
  221. return; // 如果已经处理过,则直接返回
  222. }
  223. this.isSpecialEffectHandled = true; // 标记为已处理
  224. const comp = this.node.parent.getComponent(BlockLink);
  225. if (!comp) return;
  226. console.log("处理炸弹特殊效果");
  227. this.handleBombEffect(otherBlock);
  228. // 延迟执行移动操作,确保当前动画完成
  229. this.scheduleOnce(() => {
  230. const dir = LvData.instance.dir;
  231. if (dir !== LvDir.none) {
  232. comp.moveByLevelDirection(dir);
  233. }
  234. }, 0.2); // 稍长的延迟确保所有动画完成
  235. }
  236. private findMatchPair(): { blockA: Block, blockB: Block } | null {
  237. const comp = this.node.parent.getComponent(BlockLink);
  238. if (!comp) return null;
  239. // 收集所有普通方块
  240. const normalBlocks: Block[] = [];
  241. BlockLink.blockArry.forEach(row => {
  242. row.forEach(blockNode => {
  243. if (blockNode && blockNode.isValid) {
  244. const block = blockNode.getComponent(Block);
  245. if (block && !block.isSpecialBlock()) {
  246. normalBlocks.push(block);
  247. }
  248. }
  249. });
  250. });
  251. // 寻找一对匹配的方块
  252. for (let i = 0; i < normalBlocks.length; i++) {
  253. for (let j = i + 1; j < normalBlocks.length; j++) {
  254. if (normalBlocks[i].isMatch(normalBlocks[j])) {
  255. return {
  256. blockA: normalBlocks[i],
  257. blockB: normalBlocks[j]
  258. };
  259. }
  260. }
  261. }
  262. return null;
  263. }
  264. // 消除炸弹方块
  265. private eliminateBombBlock(block: Block) {
  266. const comp = block.node.parent.getComponent(BlockLink);
  267. if (!comp) return;
  268. // 更新游戏数据
  269. const pos = comp.getRowCol(block.node.position);
  270. BlockLink.blockArry[pos.i][pos.j] = null;
  271. BlockLink.remainingBlocks--;
  272. // 方块消失动画
  273. tween(block.node)
  274. .to(0.2, { scale: v3(1.5, 1.5, 1) })
  275. .to(0.2, { scale: v3(0, 0, 1) })
  276. .call(() => {
  277. if (block.node && block.node.isValid) {
  278. block.node.destroy();
  279. }
  280. })
  281. .start();
  282. }
  283. // // 消除匹配的方块对
  284. private eliminateMatchPair(blockA: Block, blockB: Block) {
  285. debugger
  286. const comp = blockA.node.parent.getComponent(BlockLink);
  287. if (!comp) return;
  288. blockA.node.getChildByName("xuanz").active = true;
  289. blockB.node.getChildByName("xuanz").active = true;
  290. this.scheduleOnce(() => {
  291. // 更新游戏数据
  292. const posA = comp.getRowCol(blockA.node.position);
  293. const posB = comp.getRowCol(blockB.node.position);
  294. BlockLink.blockArry[posA.i][posA.j] = null;
  295. BlockLink.blockArry[posB.i][posB.j] = null;
  296. BlockLink.remainingBlocks -= 2;
  297. // 播放消除动画
  298. this.animateBlockDisappearance(blockA);
  299. this.animateBlockDisappearance(blockB);
  300. }, 0.2);
  301. if (comp) {
  302. const dir = LvData.instance.dir;
  303. if (dir !== LvDir.none) {
  304. comp.moveByLevelDirection(dir);
  305. }
  306. }
  307. }
  308. // 方块消失动画
  309. private animateBlockDisappearance(block: Block) {
  310. tween(block.node)
  311. .to(0.4, { scale: v3(1.2, 1.2, 1) })
  312. .to(0.4, { scale: v3(0, 0, 1) })
  313. .call(() => {
  314. if (block.node && block.node.isValid) {
  315. block.node.destroy();
  316. }
  317. })
  318. .start();
  319. }
  320. // 随机消除一对普通方块
  321. private eliminateRandomPair() {
  322. debugger
  323. const comp = this.node.parent.getComponent(BlockLink);
  324. if (!comp) return;
  325. // 收集所有普通方块
  326. const normalBlocks: Block[] = [];
  327. BlockLink.blockArry.forEach(row => {
  328. row.forEach(blockNode => {
  329. if (blockNode && blockNode.isValid) {
  330. const block = blockNode.getComponent(Block);
  331. if (block && !block.isSpecialBlock()) {
  332. normalBlocks.push(block);
  333. }
  334. }
  335. });
  336. });
  337. // 随机选择两个方块消除
  338. if (normalBlocks.length >= 2) {
  339. const index1 = Math.floor(Math.random() * normalBlocks.length);
  340. let index2;
  341. do {
  342. index2 = Math.floor(Math.random() * normalBlocks.length);
  343. } while (index2 === index1);
  344. this.eliminateMatchPair(normalBlocks[index1], normalBlocks[index2]);
  345. }
  346. // if (comp) {
  347. // const dir = LvData.instance.dir;
  348. // if (dir !== LvDir.none) {
  349. // comp.moveByLevelDirection(dir); // 或者调用 refreshLayout()
  350. // }
  351. // }
  352. }
  353. private handleBombEffect(otherBlock: Block) {
  354. const comp = this.node.parent.getComponent(BlockLink);
  355. if (!comp) return;
  356. let boomA = instantiate(this.BOOM);
  357. boomA.setPosition(this.node.position);
  358. this.node.parent.addChild(boomA);
  359. boomA.setSiblingIndex(999)
  360. const boomB = instantiate(this.BOOM);
  361. boomB.setPosition(otherBlock.node.position);
  362. this.node.parent.addChild(boomB);
  363. boomA.setSiblingIndex(998)
  364. const initialHeight = 100; // 向上飞行高度
  365. // 1. 消除两个炸弹方块
  366. this.eliminateBombBlock(this);
  367. this.eliminateBombBlock(otherBlock);
  368. // 2. 寻找并消除一对匹配的普通方块
  369. const matchPair = this.findMatchPair();
  370. if (matchPair) {
  371. matchPair.blockA.node.getChildByName("xuanz").active = true
  372. matchPair.blockB.node.getChildByName("xuanz").active = true
  373. const comp = this.node.parent.getComponent(BlockLink);
  374. const worldPosA = matchPair.blockA.node.getWorldPosition();
  375. const worldPosB = matchPair.blockB.node.getWorldPosition();
  376. // 2. 延迟方块消除(直到炸弹到达)
  377. this.scheduleOnce(() => {
  378. this.eliminateMatchPair(matchPair.blockA, matchPair.blockB);
  379. }, 0.8); // 等待飞行动画完成
  380. // 3. 使用正确的位置
  381. this.flyBoomInitialUp(boomA, initialHeight, () => {
  382. this.flyBoomToTarget(boomA, worldPosA, () => {
  383. this.playBoomAnimation(boomA); // 播放动画
  384. });
  385. });
  386. this.flyBoomInitialUp(boomB, initialHeight, () => {
  387. this.flyBoomToTarget(boomB, worldPosB, () => {
  388. this.playBoomAnimation(boomB); // 播放动画
  389. });
  390. });
  391. this.eliminateMatchPair(matchPair.blockA, matchPair.blockB);
  392. }
  393. else {
  394. // 如果没有找到匹配对,随机选择两个方块消除
  395. this.eliminateRandomPair();
  396. }
  397. // 3. 延迟执行移动操作
  398. this.scheduleOnce(() => {
  399. const dir = LvData.instance.dir;
  400. if (dir !== LvDir.none) {
  401. comp.moveByLevelDirection(dir);
  402. }
  403. }, 0.8); // 稍长的延迟确保所有动画完成
  404. }
  405. private playBoomAnimation(boom: Node) {
  406. audioManager.playOneShot("sound/zdBong")
  407. const boomChild = boom.getChildByName("boom");
  408. if (!boomChild) return;
  409. // 1. 启用动画组件
  410. boomChild.active = true;
  411. this.schedule(() => {
  412. boom.destroy();
  413. }, 0.3)
  414. }
  415. // 初始向上飞行动画
  416. private flyBoomInitialUp(boom: Node, height: number, onComplete: Function) {
  417. audioManager.playOneShot("sound/zdfle1")
  418. // 计算目标位置(当前位置上方)
  419. const currentPos = boom.position.clone();
  420. const targetPos = new Vec3(currentPos.x, currentPos.y + height, currentPos.z);
  421. // 向上飞行动画
  422. tween(boom)
  423. .to(0.2, {
  424. position: targetPos,
  425. scale: v3(1.2, 1.2, 1), // 飞行过程中变大
  426. }, {
  427. easing: "sineOut",
  428. onComplete: () => onComplete()
  429. })
  430. .start();
  431. }
  432. private flyBoomToTarget(boom: Node, worldPos: Vec3, onComplete: Function) {
  433. // 转换世界坐标到本地坐标
  434. const localPos = this.node.parent.inverseTransformPoint(new Vec3(), worldPos);
  435. tween(boom)
  436. .to(0.4, {
  437. position: localPos,
  438. scale: v3(1.5, 1.5, 1) // 添加缩放效果
  439. }, { easing: "quadOut" })
  440. .call(() => {
  441. onComplete();
  442. })
  443. .start();
  444. }
  445. }