From b7388615ed2b8a89d35012ee4a605a0e9a45e349 Mon Sep 17 00:00:00 2001 From: panFD Date: Sat, 20 Jun 2026 21:59:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(map):=20=E4=B8=BA=E8=8B=B1=E9=9B=84?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=BC=B9=E7=AA=97=E5=92=8C=E5=8D=A1=E7=89=8C?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=B7=BB=E5=8A=A0=E7=82=B9=E5=87=BB=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E5=85=B3=E9=97=AD=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 全局添加触摸结束监听,实现点击弹窗/卡牌外区域自动关闭/隐藏控件 2. 通过包围盒检测避免误触内部元素,无需额外遮罩节点 3. 统一管理事件的绑定与解绑,防止内存泄漏 --- assets/script/game/map/CardComp.ts | 27 ++++++++++++++++++++++++++- assets/script/game/map/HInfoComp.ts | 26 +++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/assets/script/game/map/CardComp.ts b/assets/script/game/map/CardComp.ts index 85d80f93..a2f67bf8 100644 --- a/assets/script/game/map/CardComp.ts +++ b/assets/script/game/map/CardComp.ts @@ -20,7 +20,7 @@ * - smc.vmdata.mission_data —— 读写局内金币 */ import { mLogger } from "../common/Logger"; -import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, Light, UITransform, Widget } from "cc"; +import { _decorator, Animation, AnimationClip, EventTouch, Input, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, input, resources, Light, UITransform, Widget } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, CardPoolList } from "../common/config/CardSet"; @@ -485,6 +485,8 @@ export class CardComp extends CCComp { this.call_btn.off(NodeEventType.TOUCH_END, this.onCallBtnClick, this); } oops.message.off(GameEvent.CardSelected, this.onOtherCardSelected, this); + // 兜底注销全局触摸监听(showCallBtn 后组件销毁的场景) + input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this); } // ======================== 触摸交互 ======================== @@ -579,6 +581,8 @@ export class CardComp extends CCComp { private showCallBtn() { if (this.call_btn && this.call_btn.isValid) { this.call_btn.active = true; + // 按需监听全局触摸:点击本体以外区域时隐藏召唤按钮 + input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this); } } @@ -587,6 +591,27 @@ export class CardComp extends CCComp { if (this.call_btn && this.call_btn.isValid) { this.call_btn.active = false; } + // 注销全局监听(无论是否曾注册,off 均安全) + input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this); + } + + /** + * 全局触摸结束:点击落点不在召唤按钮 / 卡牌本体包围盒内时隐藏召唤按钮。 + * Why: 复用 HInfoComp 的"点击外部关闭"交互模式,避免 call_btn 长期滞留。 + * 排除卡牌本体是防止与 onCardTap 的"点击卡牌显示 call_btn"交互相互冲突。 + * @param event 全局触摸事件 + */ + private onGlobalTouchEnd(event: EventTouch) { + if (!this.call_btn || !this.call_btn.active) return; + const uiPos = event.getUILocation(); + // 命中召唤按钮本体 → 保留 + const btnTrans = this.call_btn.getComponent(UITransform); + if (btnTrans && btnTrans.getBoundingBoxToWorld().contains(uiPos)) return; + // 命中卡牌本体 → 保留(由 onCardTap 自行管理显隐) + const cardTrans = this.node.getComponent(UITransform); + if (cardTrans && cardTrans.getBoundingBoxToWorld().contains(uiPos)) return; + // 其余区域 → 隐藏 + this.hideCallBtn(); } /** 召唤按钮点击回调:阻止冒泡后触发使用卡牌 */ diff --git a/assets/script/game/map/HInfoComp.ts b/assets/script/game/map/HInfoComp.ts index 1faed59b..a1a1a944 100644 --- a/assets/script/game/map/HInfoComp.ts +++ b/assets/script/game/map/HInfoComp.ts @@ -19,7 +19,7 @@ * - Hero —— 英雄 ECS 实体类(用于出售删除) * - UIID.IBox —— 英雄详情弹窗 ID */ -import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, CCInteger, SpriteFrame } from "cc"; +import { _decorator, Animation, AnimationClip, Button, Event, EventTouch, Input, Label, Node, NodeEventType, Sprite, UITransform, input, resources, CCInteger, SpriteFrame } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { HeroInfo } from "../common/config/heroSet"; @@ -532,6 +532,8 @@ export class HInfoComp extends CCComp { this.sell_node?.on(Button.EventType.CLICK, this.onSellHero, this); this.close_node?.on(Button.EventType.CLICK, this.onClosePanel, this); // this.node.on(NodeEventType.TOUCH_END, this.onOpenIBox, this); + // 监听全局触摸结束:点击本体节点以外区域时关闭面板 + input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this); } private unbindEvents() { @@ -544,6 +546,28 @@ export class HInfoComp extends CCComp { // if (this.node && this.node.isValid) { // this.node.off(NodeEventType.TOUCH_END, this.onOpenIBox, this); // } + input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this); + } + + /** + * 全局触摸结束处理:点击落点不在本体节点包围盒内时关闭面板。 + * Why: 弹窗常见的"点击空白处关闭"交互,用全局 input + 包围盒命中检测实现, + * 无需额外遮罩节点,也不会误关面板内部(含子按钮)的点击。 + * @param event 全局触摸事件 + */ + private onGlobalTouchEnd(event: EventTouch) { + if (this.isClosing) return; + const transform = this.node.getComponent(UITransform); + if (!transform) { + // 缺少 UITransform 时保守处理为关闭 + this.onClosePanel(); + return; + } + const worldRect = transform.getBoundingBoxToWorld(); + const uiPos = event.getUILocation(); + if (!worldRect.contains(uiPos)) { + this.onClosePanel(); + } } /**