【问题标题】:SpriteKit Memory leaks changing Scenes containing SKTileMapNodesSpriteKit 内存泄漏更改包含 SKTileMapNodes 的场景
【发布时间】:2018-04-02 17:43:47
【问题描述】:

我正在尝试使用 Swift、SpriteKit 和 SKTileMaps 创建一个简单的 2d 平台游戏。但是每次我在包含 SKTileMaps 的场景之间切换时,我都会在 Xcode Instruments 中看到很多内存泄漏。

我已经尽可能简单地重现了这个问题。我正在使用 .sks 文件创建场景,该文件仅包含 1 个填充了一些图块的 tileMap。

视图控制器中用于呈现场景的代码:

if let view = self.view as! SKView? {
        let scene = LoadingScene(size: CGSize(width: 2048, height: 1536))
        scene.scaleMode = .aspectFill
        view.presentScene(scene)

场景代码:

import SpriteKit
import GameplayKit

class GameScene: SKScene {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let scene = GameScene(fileNamed: "WorldScene") else{fatalError("Could not open world scene")}
        view?.presentScene(scene)
    }
}

我选择 GameScene 作为 .sks 场景文件中的自定义类。

这会导致我每次更改场景时都会出现很多小的内存泄漏:

picture of memory leak in Instruments

这只是一个场景变化的泄漏。我做错了什么还是这是一个 SpriteKit 错误?

编辑1

每次加载瓦片地图时都会发生 SKCTileMapNode::_ensureChunkForTileIndex(unsigned int) 泄漏,而其余的仅在更改场景时出现

编辑2

更改了 GameViewController 以跳过 LoadingScene 并直接进入 GameScene。内存泄漏依然存在:

if let view = self.view as! SKView? {
    guard let scene = GameScene(fileNamed: "WorldScene") else{fatalError("Could not open world scene")}
    scene.scaleMode = .aspectFill
    view.presentScene(scene)
}

【问题讨论】:

  • 没有代码很难说,但你的场景中可能有太多强引用,GC 可能无法打破一些引用循环......
  • 第二个代码块是 all 用于 GameScene 的代码。每次触摸屏幕时,它本质上都会呈现一个新的自身实例。它不包含直接引用和子类 SKScene。每次触摸屏幕并呈现新场景时,我仍然会出现内存泄漏。

标签: sprite-kit skscene sktilemapnode


【解决方案1】:

我也遇到过同样的问题,经过调查,我相信这是所有 SKTileMapNode 固有的问题。这是 SpriteKit 中的一个错误。

当您使用填充了任何瓦片的 SKTileMapNode(不是空白瓦片地图)时,即使在加载后续场景时,瓦片地图的内存也会持续存在。如果你继续使用 SKTileMapNodes 加载关卡,那么内存将不断增加,直到游戏最终崩溃。我已经用我编写的不同游戏以及其他人的代码对此进行了测试。

有趣的是,如果瓦片地图的所有瓦片都是空白的(即使它分配了一个 SKTileSet),那么就不会发生内存泄漏。

据我所知,当 SKTileMapNode 中除了空白图块之外还有任何图块时,它正在使用的整个 SKTileSet 将保存在内存中并且永远不会被删除。

根据我的实验,您可以通过在 didMove 中将 SKTileSet 与空白图块集交换来防止此问题。除了在 didMove(或 didMove 调用的函数)内,您不能在其他任何地方执行此操作。

所以我的解决方案是将瓦片集提取到单独的精灵中,然后在瓦片地图上“取消”瓦片集。您可以使用以下代码:

extension SKTileMapNode {

func extractSprites(to curScene: SKScene) {

    for col in 0..<numberOfColumns {
        for row in 0..<numberOfRows {

            if tileDefinition(atColumn: col, row: row) != nil {

                let tileDef = tileDefinition(atColumn: col, row: row)
                let newSprite = SKSpriteNode(texture: tileDef!.textures.first!)
                curScene.addChild(newSprite)

                let tempPos = centerOfTile(atColumn: col, row: row)
                newSprite.position = convert(tempPos, to: curScene)
            }
        }
    }

    eraseTileSet()
}

func eraseTileSet() {
    let blankGroup: [SKTileGroup] = []
    let blankTileSet = SKTileSet(tileGroups: blankGroup)
    tileSet = blankTileSet
}
}

基本上在 didMove 中,您需要在每个 SKTileMapNode 上调用 extractSprites。这将简单地从每个图块创建 SKSpriteNodes 并将它们放入场景中。然后 SKTileSet 将“切换”为空白。神奇的是,内存泄漏会消失。

这是一个简化的解决方案,您需要对其进行扩展。这只是将精灵放在那里,但没有定义它们的行为方式。抱歉,这是我找到的唯一解决方案,我相信您的问题是 SpriteKit 中的一个主要错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-17
    • 1970-01-01
    • 1970-01-01
    • 2015-07-23
    • 1970-01-01
    • 1970-01-01
    • 2019-06-20
    • 1970-01-01
    相关资源
    最近更新 更多