MacOS透明窗口中Sprite点击检测与背景点击穿透实现问题
解决SKScene悬浮窗口仅响应Sprite点击、其余区域穿透的问题
我基于SKScene/GameScene开发了一款macOS悬浮时钟应用,目前窗口悬浮、文本Sprite显示及点击检测功能均正常,但遇到核心问题:需要让窗口非Sprite区域的点击直接穿透到底层应用,仅处理Sprite上的点击事件。
试过常规方案(重写mouseDown:、使用NSEvent本地监视器)都无效,原因在于SKScene的事件处理逻辑和WindowServer的事件分发机制冲突——本地监视器只是在应用内部拦截事件,返回nil或原事件都无法让WindowServer把事件传递给下层窗口。
核心解决方案:通过NSWindow的hitTest:方法控制事件接收
WindowServer通过hitTest:判断哪个窗口/视图接收鼠标事件,若该方法返回nil,则会跳过当前窗口,将事件传递给栈中的下一个窗口。我们可以重写这个方法,仅在点击到Sprite区域时返回窗口的内容视图,其余情况返回nil。
步骤1:创建NSWindow子类,重写hitTest:
@interface ClickThroughWindow : NSWindow @property (nonatomic, weak) SKScene *gameScene; // 关联你的GameScene @end @implementation ClickThroughWindow - (NSView *)hitTest:(NSPoint)point { NSView *contentView = self.contentView; // 转换窗口坐标到NSView坐标系 NSPoint viewPoint = [contentView convertPoint:point fromView:nil]; // 转换NSView坐标到SKScene坐标系(SKScene Y轴向上,NSView Y轴向下) CGPoint scenePoint = CGPointMake(viewPoint.x, contentView.bounds.size.height - viewPoint.y); // 判断点击位置是否命中Sprite SKNode *touchedNode = [self.gameScene nodeAtPoint:scenePoint]; if (touchedNode != self.gameScene) { // 命中Sprite,返回contentView让窗口处理事件 return contentView; } // 未命中,返回nil让事件穿透到底层应用 return nil; } @end
步骤2:在GameScene中直接处理点击/拖拽事件
不再使用NSEvent本地监视器,直接重写SKScene的事件方法:
- (void)mouseDown:(NSEvent *)event { CGPoint location = [event locationInNode:self]; SKNode *spriteTouched = [self nodeAtPoint:location]; if (spriteTouched != self) { mClickPt = location; // 这里添加你的Sprite点击处理逻辑 } } - (void)mouseDragged:(NSEvent *)event { CGPoint location = [event locationInNode:self]; SKNode *spriteTouched = [self nodeAtPoint:location]; if (spriteTouched != self) { [self moveWindowTo:location]; } }
步骤3:窗口基础配置(确保透明和悬浮)
确保你的窗口配置正确,避免影响穿透效果:
// 初始化窗口时设置 ClickThroughWindow *window = [[ClickThroughWindow alloc] initWithContentRect:NSMakeRect(100, 100, 200, 100) styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]; window.backgroundColor = [NSColor clearColor]; window.level = NSWindowLevelFloating; // 悬浮在其他窗口上方 window.ignoresMouseEvents = NO; // 必须设为NO,否则hitTest不会被调用 window.gameScene = self; // 关联你的GameScene实例 [window makeKeyAndOrderFront:nil];
原理说明
hitTest:是WindowServer触发的方法,直接决定事件是否由当前窗口接收。返回nil时,WindowServer会认为当前窗口不需要处理该事件,转而将事件传递给下层窗口。- 坐标转换是关键:SKScene的Y轴方向与NSView相反,必须转换后才能正确判断点击位置是否命中Sprite。
内容的提问来源于stack exchange,提问作者Glenn Reid
相关产品推荐
相关产品推荐

