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

标签 sprite-kit skscene sktilemapnode

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

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

View Controller 中用于呈现场景的代码:

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

每次加载瓦片 map 时都会发生 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)
}

最佳答案

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

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

有趣的是,如果图 block map 全部为空白图 block (即使分配了 SKTileSet),则不会发生内存泄漏。

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

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

所以我的解决方案是将图 block 集提取到单独的 Sprite 中,然后“无效”图 block ​​ map 上的图 block 集。您可以使用以下代码来执行此操作:

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。这将简单地从每个图 block 创建 SKSpriteNode 并将它们放入场景中。然后SKTileSet将被“切换”为空白的。神奇的是,内存泄漏将会消失。

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

关于sprite-kit - SpriteKit 内存泄漏更改包含 SKTileMapNodes 的场景,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49616019/

相关文章:

ios - SKSpriteNode ScaleUpFrom?

iphone - 机芯不工作

ios - Sprite Kit - 使场景向上延伸,而不是向下延伸,或将初始节点位置设置为左下角

ios - 我怎样才能重新启动 SKScene?

ios - SpriteKit sktilemapnode垂直线故障

ios - SpriteKit 最大节点数

swift - 为什么不能使用 SKShapenode 的子类?

ios - 奇怪的 EXC_BAD_ACCESS SpriteKit removeSubsprite 崩溃

ios - 无法编辑瓷砖 map Xcode 8 GM

ios - 以编程方式创建 SpriteKit SKTileMap 的正确方法是什么?