You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:55:56