41 lines
2.2 KiB
TypeScript
41 lines
2.2 KiB
TypeScript
import masks from "./SevenDayHitMasks";
|
||
|
||
/** 根据原图透明度判断可见 UI,不把贴图矩形内的空白当作弹窗。 */
|
||
export default class SevenDayHitTest {
|
||
// 从上层子节点往背景检查,透明处继续寻找下方的可见 UI。
|
||
public static findVisible(node: cc.Node, screenPoint: cc.Vec2): cc.Node {
|
||
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;
|
||
}
|
||
}
|