带有overlaySKScene的SceneKit应用程序在macOS上绘制具有错误纹理的SKSpriteNode

问题描述 投票:2回答:1

我目前正在移植到macOS的iOS和tvOS App Store中有一个功能游戏。这是一个SceneKit应用程序,大量使用了overlaySKScene来提供详细的HUD和2D播放区域。

[在iOS和tvOS上,一切正常,但是在macOS上,我看到了这个奇怪的间歇性问题,其中以错误的纹理为1或2帧绘制了属于overlaySKScene子级的SKSpriteNodes。

但是,当我在应用程序中启动游戏时,并没有发生更多的节点添加到overlaySKScene中,绘制良好的节点开始闪烁。而且更有趣的是,当它们闪烁时,它们似乎是从其他节点绘制纹理,并拉伸以适合其帧。

我已经尝试了macOS故事板上的各种设置,并更改了在节点上启动动画的方式。我已对该应用程序进行了概要分析,以防动画消耗资源,但看不到任何迹象。

所有艺术资产都在标准Xcode资产目录中,并具有Mac大小的资产。

这一切都是在基于Catalina的全新Mac上发生的。它发生在Xcode环境之内和之外。

最后一个观察结果是,仅当我使应用程序最大化以使用整个屏幕时,才出现问题。

此图像显示了应该在屏幕上显示的内容。

This is what it should look like

此图像显示了短暂发生的情况,导致闪烁。闪烁后,将显示正确的内容。因此,在我看来,SpriteKit管理节点的某个方式已损坏,但仅限于macOS。

This is what it looks like when the glitch occurs

关于在MacOS上引起SceneKit / SpriteKit这种问题的任何建议,将不胜感激。

[UPDATE 1]:

嗯,问题似乎与该应用处于全屏模式直接相关。经过一周的尝试来弄清楚我可能做错了什么,新的搜索将我带到了另一个another SO post

我在那篇文章中使用了一些技术,特别是:

self.view.window!.styleMask = NSBorderlessWindowMask  
self.view.window!.level = Int(CGWindowLevelForKey(Int32(kCGMainMenuWindowLevelKey))) + 1  
self.view.window!.opaque = true  
self.view.window!.hidesOnDeactivate = true  
let size = NSScreen.mainScreen()!.frame.size  
NSMenu.setMenuBarVisible(false)  
self.view.window!.setFrame(CGRect(x: 0, y: 0, width: size.width, height: size.height+1), display:true)  

这肯定有很大的不同,纹理的毛刺在99.9%的时间内停止了,但是我在屏幕底部剩下大约20个未使用的像素。

鉴于该帖子是在2016年问到的,有评论表明该问题已在2016年得到纠正,我想知道为什么我在2020年仍会看到它。我注意到我的旧版Macbook Pro上也出现了同样的问题(2013,Core i5,2.3GHz)运行Mojave。

我不接受仅仅是因为硬件不能胜任。如果该应用程序可以在iOS模拟器中顺利运行,从而为该应用程序添加了一层额外的OS级代码,则无论窗口大小如何,它都应该能够在计算机上本地运行。

我注意到在上面的解决方案中,如果我将这一行注释掉:

NSMenu.setMenuBarVisible(false)  

然后问题浮出水面。 macOS围绕(靠近)全屏应用程序进行了一些奇怪的优化,以及它们使用菜单栏屏幕房地产的方式削弱了SpriteKit应用程序(可能还有其他)。

Apple Arcade上的游戏似乎运行正常,因此必须有一种方法可以使所有这些正常工作。不过,最好不要破解解决方法。

以下是显示此问题的更多屏幕截图。

首先,使用上述解决方案并将菜单栏设置为不可见,所有菜单均运行良好,但是我无法使窗口自行定位,从而使其实际上覆盖了整个屏幕。您仍然可以看到Xcode在其后运行:

Full FPS in near fullscreen with menubar not visible

[接下来,下面的图像显示了我通过注释掉该行而得到的结果,因此即使菜单栏实际上是不可见的,也会导致帧速率严重下降,然后如最终图像所示,实际上,我在应用程序中做了一些触发SpriteKit动画的操作,使用错误的纹理绘制了SpriteKit叠加层内的SKSpriteNode几帧:

drop in FPS with the menubar visible (but not)

the texture glitching in virtually every SKSpriteNode

macos sprite-kit scenekit fullscreen
1个回答
0
投票

因此,该解决方案最终结合了我在另一篇SO帖子中发现的内容以及Apple Developer文档中有关如何使用全屏模式的更多说明。

  1. 我需要隐藏菜单栏
  2. 我需要将窗框的高度减少1个像素。
  3. 我需要将窗口的collectionBehaviour设置为NSWindowCollectionBehaviorFullScreenPrimary

下面是我在GameViewController类中使用的代码的一部分。这似乎工作得很好,尽管诚然我只能在MacBook Air 2020(酷睿i7)上进行尝试。

@implementation GameViewController

- (void) viewWillAppear {
    [super viewWillAppear];

    SCNView * skView = (SCNView *)self.view;

    if (skView.isInFullScreenMode == NO) {
        if (skView.window != nil) {
            NSWindow *main = skView.window;
            main.delegate = self;

            // This is a must, if you want the window to be positioned properly.
            main.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
            main.styleMask = NSWindowStyleMaskBorderless;
            main.level = (NSWindowLevel)(CGWindowLevelForKey((kCGMainMenuWindowLevelKey))) + 1;
            main.opaque = YES;
            main.hidesOnDeactivate = YES;

            // This is essential to the app working in full-screen mode.
            //
            // See: [https://stackoverflow.com/questions/62004009/scenekit-app-with-overlayskscene-drawing-skspritenode-with-incorrect-textures-on][1]
            //
            [NSMenu setMenuBarVisible:NO];

            // this triggers the change to full-screen mode.
            //
            [main toggleFullScreen:nil];
        }
    }
}

// Needed for SpriteKit apps that also use NSTextField.
//
- (BOOL) acceptsFirstResponder {
    return YES;
}

// Try to prevent changes away from full-screen mode.
//
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame {
    SCNView * skView = (SCNView *)self.view;

    if (skView.isInFullScreenMode == NO) {
        return YES;
    }

    return NO;
}

// NSWindow delegate method where we tell the OS what frame size we want to use.
// This seems to be ignored, however I've left this in, as an in-case.
//
- (NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize {
    return NSMakeSize(proposedSize.width, proposedSize.height - 1);
}

// This NSWindow delegate method handles the actual change to full-screen by
// telling the SKView and it's scene to resize.
//
- (void) windowWillEnterFullScreen:(NSNotification *)notification {
    SCNView * skView = (SCNView *)self.view;

    if (skView.window != nil) {
        NSWindow *main = skView.window;
        NSScreen *screen = main.screen;

        // Get the screen size (where the screen is sourced from the window
        // so that you get the correct screen dimensions in multi-screen
        // environments).
        //
        CGRect screenframe = screen.frame;

        // Lose that pixel; the core of the change highlighted by the other SO
        // post.
        //
        screenframe.size.height--;

        // Tell the window what size we want it to be.
        //
        [main setFrame:screenframe display:YES];

        // Tell the SKView the same thing.
        //
        [skView setFrameSize:screenframe.size];

        // Do your app specific scene setup here.
        //
    }
}

@end
© www.soinside.com 2019 - 2024. All rights reserved.