import masks from "./SevenDayHitMasks"; /** 根据原图透明度判断可见 UI,不把贴图矩形内的空白当作弹窗。 */ export default class SevenDayHitTest { // 从上层子节点往背景检查,透明处继续寻找下方的可见 UI。 public static findVisible(node: cc.Node, screenPoint: cc.Vec2): cc.Node { // 装饰光芒不扩大卡片的可点击范围。 if (node.name === "catGlow") return null; if (!node.activeInHierarchy || node.opacity === 0) return null; for (let i = node.children.length - 1; i >= 0; i--) { const hit = this.findVisible(node.children[i], screenPoint); if (hit) return hit; } const sprite = node.getComponent(cc.Sprite); const label = node.getComponent(cc.Label); if ((!sprite || !sprite.enabled || !sprite.spriteFrame) && (!label || !label.enabled)) return null; const camera = cc.Camera.findCamera(node); const world = camera ? camera.getScreenToWorldPoint(screenPoint) : screenPoint; const local = node.convertToNodeSpaceAR(cc.v2(world.x, world.y)); let u = local.x / node.width + node.anchorX; let v = 1 - (local.y / node.height + node.anchorY); if (!isFinite(u) || !isFinite(v) || u < 0 || u >= 1 || v < 0 || v >= 1) return null; if (!sprite || !sprite.enabled || !sprite.spriteFrame) return node; // SpriteFrame 的 UUID 不随动态图集打包而变化,命中数据无需读取 GPU。 const frame = sprite.spriteFrame; const mask = masks[(frame as any)._uuid]; if (!mask || sprite.type !== cc.Sprite.Type.SIMPLE) return node; if (!sprite.trim) { const size = frame.getOriginalSize(); const offset = frame.getOffset(); u = (u * size.width - (size.width - mask.width) / 2 - offset.x) / mask.width; v = (v * size.height - (size.height - mask.height) / 2 + offset.y) / mask.height; } if (u < 0 || u >= 1 || v < 0 || v >= 1) return null; const x = Math.floor(u * mask.width); const runs = mask.spans[mask.rows[Math.floor(v * mask.height)]]; for (let i = 0; i < runs.length; i += 2) { if (x >= runs[i] && x < runs[i + 1]) return node; } return null; } }