SpriteKit内存泄漏更改包含SKTileMapNodes的场景

时间:2018-04-02 17:43:48

标签: sprite-kit skscene sktilemapnode

我正在尝试使用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错误?

EDIT1

每次加载图块地图时都会发生SKCTileMapNode :: _ ensureChunkForTileIndex(unsigned int)泄漏,而其余部分仅在更改场景时出现

EDIT2

更改了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)
}

1 个答案:

答案 0 :(得分:0)

我也遇到过同样的问题,在研究之后,我相信这是所有SKTileMapNodes固有的问题。这是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中的主要错误。