2

我正在尝试使用 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 场景文件中的自定义类。

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

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

1 回答 1

2

我遇到了同样的问题,经过调查,我相信这是所有 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 中的一个主要错误。

于 2018-09-28T23:41:36.723 回答