SpriteKit:如何添加/删除节点并避免枚举时出现突变异常?

Posted

技术标签:

【中文标题】SpriteKit:如何添加/删除节点并避免枚举时出现突变异常?【英文标题】:SpriteKit: how to add/remove node and avoid having mutation exception while enumerating? 【发布时间】:2016-03-02 09:34:18 【问题描述】:

我将 SKScene 渲染为一个节点网格,我在其中投影由更大的期刊网格构成的数据结构。

当要求在网格中的 (i,j) 位置渲染节点时,代码会查找媒体(纹理)可用性,并在这种情况下添加一个 SKSpriteNode。但如果不能立即使用,请从远程服务器请求纹理(当然是在线程中)。因此,由于它不是立即可用的,因此代码会创建一个备用 SKShapeNode,以便在下载纹理时使其对用户可见。

如果接收到纹理,则将备用 SKShapeNode 替换为 SKSpriteNode,并使用由接收到的纹理制成的新创建的 SKSpriteNode。

每个节点都根据它在网格中的位置来命名,也就是。 “((i),(j))”。

除此之外,我还处理了一个捏合手势,该手势负责根据捏合教育的比例重新排列节点位置(螺母不对场景应用比例)。

理论上一切正常,但不幸的是,我没有找到避免在枚举时改变精灵节点子列表的方法。

我该怎么办?

    for i in 0..<tilesh 
        for j in 0..<tilesw 
            let row = i
            let col = j + band * tilesw

            if let journal = Journal.journalForLevel(level, row: row, col: col) 

                // --- ask for the journal cover, but if not straght availble, fetch from cache or remote server
                let _ = journal.getCover 

                    // If completion block is invoked, that mean the cover has been fetch from a cache level
                    (journal: Journal) in

                    // If cover is now available, we simply creat the tile as a SKNode
                    createteOrUpdateJournalNode(panel, journal: journal, row: i, col: j)
                

                // --- Draw journal at each tile if cover already avail, just draw it
                createteOrUpdateJournalNode(panel, journal: journal, row: i, col: j)
            
            // --- No journal available
            else 
                createteOrUpdateJournalNode(panel, journal: nil, row: i, col: j)
                print("No journal avail for band=\(band) at row=\(row), col=\(col)")
            
           // each j
       // each i






   func createteOrUpdateJournalNode(parent: Panel, journal: Journal?, row: Int, col: Int) 

        var node : SKNode

        let alpha:CGFloat = 1 //0.5 + 0.5 * CGFloat((band+1)/panels.count)

        if let journal = journal 

            if let _ = journal.cover 

                let cover   = prepareCover(journal)
                let texture = SKTexture(image: cover)
                let n       = SKSpriteNode(texture: texture)
                node = n

                n.anchorPoint   = CGPoint(x: 0, y: 0)
            
            else 
                let n       = SKShapeNode(rect: CGRect(x: 0, y: 0, width: w, height: h))
                node = n

                let c = 0.5 + CGFloat(arc4random() % 127) / 127
                n.fillColor     = UIColor(red: c, green: c, blue: 1, alpha: alpha)
                n.strokeColor   = UIColor.blueColor()
                n.lineWidth     = 4
            
        
        else 
            let n       = SKShapeNode(rect: CGRect(x: 0, y: 0, width: w, height: h))
            node = n

            let c = 0.5 + CGFloat(arc4random() % 127) / 127
            n.fillColor     = UIColor(red: 1, green: c, blue: c, alpha: alpha)
            n.strokeColor   = UIColor.redColor()
            n.lineWidth     = 4
        

        let name = "\(band)-\(row)-\(col)"
        node.name = name
        node.position       = CGPoint(x: CGFloat(col) * w * timeScale, y: CGFloat(row) * h)

        if let n = parent.childNodeWithName(name) 
            //n.runAction(SKAction.removeFromParent())
            print ("---- should prune node from band \(band) at \(name)")
        

        print(">>>> Creating node \(name)")
        parent.addChild(node)
        print("<<<<")
    

【问题讨论】:

我还没有阅读所有问题,但这是大多数集合类和大多数语言都可能发生的经典错误。解决方案通常是在枚举时保留要添加/删除的单独的临时数组,然后在枚举完成后进行更改。希望这能让您顺利完成任务...... @***foe 同意,但我的问题是我应该在哪里交换我的“双缓冲”数据结构。我的意思是,如果我维护一个新的可变节点列表,我应该在何时何地应用所有这些以避免问题? 好的,我读了这个问题,但我不知道如何回答你的问题。但是,我没有看到任何改变子节点的代码? createteOrUpdateJournalNode() 改变我添加到问题中的节点父节点的子节点。 我在代码中看不到您枚举集合并对其进行变异的任何地方。哪一行给你一个错误?另一种方法是使用 SKTextureNode 作为占位符(使用适当的占位符纹理),然后您可以只换出现有节点上的纹理,或者您可以使用自己的 SKNode 子类,它同时具有形状节点和纹理节点作为孩子,您只需隐藏适当的子节点 【参考方案1】:

也许您可以通过使用 SKSpriteNode 作为临时占位符并仅在收到数据时分配新纹理来完全避免突变问题。这样,使用相同的对象/实例并且您的枚举不会受到影响

【讨论】:

【参考方案2】:

我通过发现可以安全地改变 SpriteKit update() 函数中的节点列表来解决这个问题,如下所示:

   // SpriteKits gameloop function
    override func update(currentTime: CFTimeInterval) 

        // Mutate list
        for p in panels 
            p.addNodes()
        
    

p 指的是 Panel 数组(请参阅答案后面的 Panel 类)。

我在稍微修改过的 createOrUpdateJounralNode() 版本中从渲染循环中准备了额外的节点,如下所示:

    let node = createOrUpdateJournalNode(nil, row: i, col: j)
    panel.addNode(node)

其中panel.addNode()简单的引用了sn-p的代码如下:

class Panel 
    var addNodesList = [SKNode]()

    func addNode(node: SKNode) 
        addNodesList.append(node)
    

    func addNodes() 

        if addNodesList.count > 0 
            for n in addNodesList 
                if let name = n.name 
                    if let n = childNodeWithName(name) 
                        n.removeFromParent()
                        print("---- removing node \(name)")
                    
                
                addChild(n)
                print("++++ adding node \(name)")
            

            addNodesList = [SKNode]()
        
    

createOrUpdateJournalNode() 稍作修改的版本如下:

    func createOrUpdateJournalNode(journal: Journal?, row: Int, col: Int) -> SKNode 
       // same code than before except we don't add child to parent, but simply return the node

       return node
    

【讨论】:

以上是关于SpriteKit:如何添加/删除节点并避免枚举时出现突变异常?的主要内容,如果未能解决你的问题,请参考以下文章

Spritekit 节点未从内存中删除

交互时删除节点 Spritekit

如何删除具有特定名称 spritekit 的所有节点

当在 Swift Spritekit 中触摸并按住屏幕时,我将如何向上移动我的节点?

从数组 spritekit swift 中删除节点

多次调用 SpriteKit 碰撞