import { _decorator, Component, Event, EventTouch, Node, SpriteFrame, UITransform, v3, Sprite, director, Color, tween, Prefab, instantiate, Vec3, Animation, Quat, sp, SkeletalAnimation, Skeleton, assetManager } from 'cc'; import { BlockLink } from './BlockLink'; import { LvData, LvDir } from '../../LvData/LvData'; import { blockMap } from './blockMap'; import { gui } from 'db://assets/core/ui/ui'; import ch_audio from 'db://assets/ch/audio/audio'; import { audioManager } from '../../../Audio/AudioManager'; const { ccclass, property } = _decorator; export enum BlockState { Normal = "normal", // 默认 Selected = "selected", // 被选中 Matched = "matched", // 已匹配 Unmatched = "unmatched" // 未匹配 } @ccclass('Block') export class Block extends Component { @property(Node) icon: Node | null = null; @property(Prefab) BOOM: Prefab | null = null; @property(Node) XZ: Node; @property(Node) BoomNode: Node @property(Sprite) BlockSprite: Sprite; @property(sp.Skeleton) biu: sp.Skeleton; //当前方块状态 public stateBlock: BlockState = BlockState.Normal; //第一次匹配的方块con private static firstSelectedBlock: Block | null = null; private isSpecialEffectHandled: boolean = false; mapConfig: blockMap = new blockMap(); private blockLink: BlockLink | null = null; start() { this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this); this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this); this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this); this.biu = this.node.getChildByName('biu').getComponent(sp.Skeleton) this.biu.node.active = false; this.blockLink = this.node.parent.getComponent(BlockLink); } onTouchStart(event: EventTouch) { console.log('touch start'); if (this.blockLink.isMatching || this.blockLink.isMoving) return; // 正在匹配时拒绝新点击||正在移动时拒绝新点击 audioManager.playOneShot('sound/onblcok'); gui.scale_anim(this.node, 0.2, 0.3, 1); gui.scale_shake_anim(this.node, 0.2, 0.3, 2, 1); this.blockLink.sleeBlock = this.node; // 允许在选中状态下再次点击自身 if (this.stateBlock === BlockState.Selected && Block.firstSelectedBlock === this) { // 点击已选中的自身方块,取消选中 this.stateBlock = BlockState.Normal; this.applyStateStyle(); Block.firstSelectedBlock = null; this.blockLink.sleeBlock = null; return; } if (this.stateBlock !== BlockState.Normal) return; this.stateBlock = BlockState.Selected; this.applyStateStyle(); if (Block.firstSelectedBlock) { this.attemptMatch(Block.firstSelectedBlock); } else { Block.firstSelectedBlock = this; } } onTouchEnd(event: EventTouch) { } onTouchCancel(event: EventTouch) { // 触摸取消,恢复状态 if (this.stateBlock === BlockState.Selected) { this.stateBlock = BlockState.Normal; this.applyStateStyle(); Block.firstSelectedBlock = null; // this.blockLink.isMatching=false } } attemptMatch(otherBlock: Block) { if (!this.isMatch(otherBlock)) { this.handleMatchFailed(otherBlock); return; } // 获取BlockLink组件 const comp = this.node.parent.getComponent(BlockLink); if (!comp) { console.error('BlockLink component not found!'); this.handleMatchFailed(otherBlock); return; } // 判断连接成功or失败 if (comp.isConnected(this.node, otherBlock.node)) { this.handleMatchSuccess(otherBlock); } else { this.handleMatchFailed(otherBlock); } } //连接失败 handleMatchFailed(otherBlock: Block) { // this.blockLink.isMatching = true; Block.firstSelectedBlock = this; this.stateBlock = BlockState.Selected; otherBlock.stateBlock = BlockState.Unmatched; // 0.5秒后恢复普通状态 // this.scheduleOnce(() => { if (otherBlock.node && otherBlock.node.isValid) { // 检查 otherBlock.node 是否存在 otherBlock.stateBlock = BlockState.Normal; otherBlock.applyStateStyle(); } // }, 0.5); } onDestroy() { this.unscheduleAllCallbacks(); // 取消所有定时器 } //连接成功 handleMatchSuccess(otherBlock: Block) { // 检查两个方块是否都是特殊方块 const isSpecialBlock = this.isSpecialBlock() && otherBlock.isSpecialBlock(); if (isSpecialBlock) { // 执行特殊效果 this.handleSpecialEffect(otherBlock); Block.firstSelectedBlock = null; this.blockLink.isMatching = false; return; } // 非特殊方块的匹配逻辑保持不变 this.blockLink.isMatching = true; this.stateBlock = BlockState.Matched; otherBlock.stateBlock = BlockState.Matched; this.applyStateStyle(); // 更新游戏数据 const comp = this.node.parent.getComponent(BlockLink); const pos1 = comp.getRowCol(this.node.position); const pos2 = comp.getRowCol(otherBlock.node.position); BlockLink.blockArry[pos1.i][pos1.j] = null; BlockLink.blockArry[pos2.i][pos2.j] = null; // 延迟移除节点,等待动画完成 this.scheduleOnce(() => { audioManager.playOneShot('sound/offblock'); this.node.destroy(); otherBlock.node.destroy(); Block.firstSelectedBlock = null; this.blockLink.isMatching = false; const dir = LvData.instance.dir; if (dir !== LvDir.none) { comp.moveByLevelDirection(dir); } // 动画完成后重新开启全局触摸 // Block.setGlobalTouchEnabled(true); }, 0.5); BlockLink.remainingBlocks--; } //选中方块效果 applyStateStyle() { if (!this.biu || !this.biu.node) return; const spriteComp = this.node?.getComponent(Sprite); const biuanim = this.node.getChildByName('biu').getComponent(sp.Skeleton) if (!spriteComp) return; switch (this.stateBlock ) { case BlockState.Selected: this.biu.node.active = true; biuanim.setAnimation(0, "animation", true); // 选中效果 // spriteComp.color = new Color(255, 255, 0); // 黄色高亮 break; case BlockState.Unmatched: // 不匹配效果 this.biu.node.active = false; biuanim.setAnimation(0, "animation", false); // spriteComp.color = new Color(255, 0, 0) break; case BlockState.Normal: if (this.biu && this.biu.node) { this.biu.node.active = false; biuanim.setAnimation(0, "animation", false); } break; } } //图片对比 isMatch(otherBlock: Block): boolean { // 防御性检查 if (!this.icon || !otherBlock.icon) return false; const currentSprite = this.icon.getComponent(Sprite); const otherSprite = otherBlock.icon.getComponent(Sprite); if (!currentSprite || !otherSprite) return false; return currentSprite.spriteFrame === otherSprite.spriteFrame; } // 重置方块状态 public resetState() { this.stateBlock = BlockState.Normal; this.applyStateStyle(); } // 判断特殊方块 private isSpecialBlock(): boolean { if (!this.icon) return false; const spriteComp = this.icon.getComponent(Sprite); if (!spriteComp || !spriteComp.spriteFrame) return false; return spriteComp.spriteFrame.name.includes("zd"); } //高亮显示 highlight(node1: Node, node2: Node) { if (!node1 || !node1.isValid) return; node1.getComponent(Sprite).color = new Color(0, 255, 0); node2.getComponent(Sprite).color = new Color(0, 255, 0); } // 点击炸弹处理 private handleSpecialEffect(otherBlock: Block) { if (!this.node.isValid) return; if (this.isSpecialEffectHandled) { return; // 如果已经处理过,则直接返回 } this.isSpecialEffectHandled = true; // 标记为已处理 const comp = this.node.parent.getComponent(BlockLink); if (!comp) return; console.log("处理炸弹特殊效果"); this.handleBombEffect(otherBlock); // 延迟执行移动操作,确保当前动画完成 this.scheduleOnce(() => { const dir = LvData.instance.dir; if (dir !== LvDir.none) { comp.moveByLevelDirection(dir); } }, 0.2); // 稍长的延迟确保所有动画完成 } private findMatchPair(): { blockA: Block, blockB: Block } | null { const comp = this.node.parent.getComponent(BlockLink); if (!comp) return null; // 收集所有普通方块 const normalBlocks: Block[] = []; BlockLink.blockArry.forEach(row => { row.forEach(blockNode => { if (blockNode && blockNode.isValid) { const block = blockNode.getComponent(Block); if (block && !block.isSpecialBlock()) { normalBlocks.push(block); } } }); }); // 寻找一对匹配的方块 for (let i = 0; i < normalBlocks.length; i++) { for (let j = i + 1; j < normalBlocks.length; j++) { if (normalBlocks[i].isMatch(normalBlocks[j])) { return { blockA: normalBlocks[i], blockB: normalBlocks[j] }; } } } return null; } // 消除炸弹方块 private eliminateBombBlock(block: Block) { const comp = block.node.parent.getComponent(BlockLink); if (!comp) return; // 更新游戏数据 const pos = comp.getRowCol(block.node.position); BlockLink.blockArry[pos.i][pos.j] = null; BlockLink.remainingBlocks--; // 方块消失动画 tween(block.node) .to(0.2, { scale: v3(1.5, 1.5, 1) }) .to(0.2, { scale: v3(0, 0, 1) }) .call(() => { if (block.node && block.node.isValid) { block.node.destroy(); } }) .start(); } // // 消除匹配的方块对 private eliminateMatchPair(blockA: Block, blockB: Block) { debugger const comp = blockA.node.parent.getComponent(BlockLink); if (!comp) return; blockA.node.getChildByName("xuanz").active = true; blockB.node.getChildByName("xuanz").active = true; this.scheduleOnce(() => { // 更新游戏数据 const posA = comp.getRowCol(blockA.node.position); const posB = comp.getRowCol(blockB.node.position); BlockLink.blockArry[posA.i][posA.j] = null; BlockLink.blockArry[posB.i][posB.j] = null; BlockLink.remainingBlocks -= 2; // 播放消除动画 this.animateBlockDisappearance(blockA); this.animateBlockDisappearance(blockB); }, 0.2); if (comp) { const dir = LvData.instance.dir; if (dir !== LvDir.none) { comp.moveByLevelDirection(dir); } } } // 方块消失动画 private animateBlockDisappearance(block: Block) { tween(block.node) .to(0.4, { scale: v3(1.2, 1.2, 1) }) .to(0.4, { scale: v3(0, 0, 1) }) .call(() => { if (block.node && block.node.isValid) { block.node.destroy(); } }) .start(); } // 随机消除一对普通方块 private eliminateRandomPair() { debugger const comp = this.node.parent.getComponent(BlockLink); if (!comp) return; // 收集所有普通方块 const normalBlocks: Block[] = []; BlockLink.blockArry.forEach(row => { row.forEach(blockNode => { if (blockNode && blockNode.isValid) { const block = blockNode.getComponent(Block); if (block && !block.isSpecialBlock()) { normalBlocks.push(block); } } }); }); // 随机选择两个方块消除 if (normalBlocks.length >= 2) { const index1 = Math.floor(Math.random() * normalBlocks.length); let index2; do { index2 = Math.floor(Math.random() * normalBlocks.length); } while (index2 === index1); this.eliminateMatchPair(normalBlocks[index1], normalBlocks[index2]); } // if (comp) { // const dir = LvData.instance.dir; // if (dir !== LvDir.none) { // comp.moveByLevelDirection(dir); // 或者调用 refreshLayout() // } // } } private handleBombEffect(otherBlock: Block) { const comp = this.node.parent.getComponent(BlockLink); if (!comp) return; let boomA = instantiate(this.BOOM); boomA.setPosition(this.node.position); this.node.parent.addChild(boomA); boomA.setSiblingIndex(999) const boomB = instantiate(this.BOOM); boomB.setPosition(otherBlock.node.position); this.node.parent.addChild(boomB); boomA.setSiblingIndex(998) const initialHeight = 100; // 向上飞行高度 // 1. 消除两个炸弹方块 this.eliminateBombBlock(this); this.eliminateBombBlock(otherBlock); // 2. 寻找并消除一对匹配的普通方块 const matchPair = this.findMatchPair(); if (matchPair) { matchPair.blockA.node.getChildByName("xuanz").active = true matchPair.blockB.node.getChildByName("xuanz").active = true const comp = this.node.parent.getComponent(BlockLink); const worldPosA = matchPair.blockA.node.getWorldPosition(); const worldPosB = matchPair.blockB.node.getWorldPosition(); // 2. 延迟方块消除(直到炸弹到达) this.scheduleOnce(() => { this.eliminateMatchPair(matchPair.blockA, matchPair.blockB); }, 0.8); // 等待飞行动画完成 // 3. 使用正确的位置 this.flyBoomInitialUp(boomA, initialHeight, () => { this.flyBoomToTarget(boomA, worldPosA, () => { this.playBoomAnimation(boomA); // 播放动画 }); }); this.flyBoomInitialUp(boomB, initialHeight, () => { this.flyBoomToTarget(boomB, worldPosB, () => { this.playBoomAnimation(boomB); // 播放动画 }); }); this.eliminateMatchPair(matchPair.blockA, matchPair.blockB); } else { // 如果没有找到匹配对,随机选择两个方块消除 this.eliminateRandomPair(); } // 3. 延迟执行移动操作 this.scheduleOnce(() => { const dir = LvData.instance.dir; if (dir !== LvDir.none) { comp.moveByLevelDirection(dir); } }, 0.8); // 稍长的延迟确保所有动画完成 } private playBoomAnimation(boom: Node) { audioManager.playOneShot("sound/zdBong") const boomChild = boom.getChildByName("boom"); if (!boomChild) return; // 1. 启用动画组件 boomChild.active = true; this.schedule(() => { boom.destroy(); }, 0.3) } // 初始向上飞行动画 private flyBoomInitialUp(boom: Node, height: number, onComplete: Function) { audioManager.playOneShot("sound/zdfle1") // 计算目标位置(当前位置上方) const currentPos = boom.position.clone(); const targetPos = new Vec3(currentPos.x, currentPos.y + height, currentPos.z); // 向上飞行动画 tween(boom) .to(0.2, { position: targetPos, scale: v3(1.2, 1.2, 1), // 飞行过程中变大 }, { easing: "sineOut", onComplete: () => onComplete() }) .start(); } private flyBoomToTarget(boom: Node, worldPos: Vec3, onComplete: Function) { // 转换世界坐标到本地坐标 const localPos = this.node.parent.inverseTransformPoint(new Vec3(), worldPos); tween(boom) .to(0.4, { position: localPos, scale: v3(1.5, 1.5, 1) // 添加缩放效果 }, { easing: "quadOut" }) .call(() => { onComplete(); }) .start(); } }