SpriteKit Memory leaks changing Scenes containing

2020-05-29 08:23发布

I am trying to create a simple 2d platform game using Swift, SpriteKit, and SKTileMaps. But every time i change between scenes containing SKTileMaps I see a lot of memory leaks in the Xcode Instruments.

I have recreated the problem as simple as I can. I am using a .sks file to create the scene and this file only contains 1 tileMap filled with some tiles.

The code in the view controller for presenting the scene:

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

The code for the 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)
    }
}

I am choosing GameScene as the custom class int the .sks scene file.

This will result in a lot of small memory leaks each time I change the scene:

picture of memory leak in Instruments

This is the leak from only one scene change. Am I doing something wrong or is this a SpriteKit bug?

Edit1

The SKCTileMapNode::_ensureChunkForTileIndex(unsigned int) leaks happen each time i load the tile map, while the rest only appear when changing a scene

Edit2

Changed the GameViewController to skip the LoadingScene and go straight to GameScene. The memory leak is still there:

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条回答
乱世女痞
2楼-- · 2020-05-29 09:15

I have come across this same issue and after looking into it, I believe it is a problem that is inherent to all SKTileMapNodes. It is a bug in SpriteKit.

When you use an SKTileMapNode with ANY tiles filled (not a blank tile map), then the memory for the tile map will persist even when loading subsequent scenes. If you keep loading levels with SKTileMapNodes, then the memory will keep increasing until the game ultimately crashes. I have tested this with different games I have coded and using the code of others as well.

Interestingly, if the tile map has all blank tiles (even if it has an SKTileSet assigned), then no memory leak will occur.

From the best that I can guess, when an SKTileMapNode has any tile in it besides blank tiles, the entire SKTileSet that it is using, is being kept in memory and never gets removed.

From my experiments, you can prevent this problem by swapping the SKTileSet with a blank tileset in didMove. You cannot do it anywhere else except within didMove (or a function called by didMove).

So my solution has been to extract the tile set into individual sprites and then "nullify" the tileset on the tilemap. You can do so with the following code:

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
}
}

Basically within didMove, you will need to call extractSprites on each SKTileMapNode. This will simply create SKSpriteNodes from each tile and put them into the scene. Then the SKTileSet will be "switched" to a blank one. Magically the memory leak will disappear.

This is a simplified solution which you will need to expand upon. This only puts the sprites there, but doesn't define how they behave. Sorry but this is the only solution I have found and I believe your problem is a major bug in SpriteKit.

查看更多
登录 后发表回答