错误:使用兄弟节点和Sprite Kit

时间:2016-07-11 09:59:35

标签: swift sprite-kit touch hittest sknode

错误 - 当兄弟姐妹重叠时,命中测试没有按预期工作:

  

场景中有2个重叠节点具有相同的父节点(即兄弟姐妹)

     

最顶层节点有userInteractionEnabled = NO,而另一个节点有userInteractionEnabled = YES

     

如果触摸了重叠,则在最顶层节点经过命中测试并失败后(因为userInteractionEnabled = NO),而不是底层节点是下一个要进行命中测试的节点,它将被跳过并且是父节点2个兄弟姐妹受到了重创。

     

应该发生的事情是下一个兄弟(底部节点)是经过重击测试而不是命中测试跳到父母那里。

根据Sprite Kit文档:

  

"在场景中,当Sprite Kit处理触摸或鼠标事件时,它会遍历场景以找到最近的节点   想要接受这个活动。如果该节点不想要该事件,Sprite Kit将检查下一个最近的节点,等等   上。处理命中测试的顺序基本上与绘制顺序相反。   对于在命中测试期间要考虑的节点,其userInteractionEnabled属性必须设置为YES。   对于除场景节点之外的任何节点,默认值为NO。"

这是一个错误,因为节点的兄弟姐妹在他们的父母之前呈现 - 兄弟应该是下一个被测试的,而不是它的父母。此外,如果某个节点有userInteractionEnabled = NO,那么它肯定应该是“透明的”。关于命中测试 - 但这不是因为它会导致行为的改变,因为在测试中跳过了一个节点。

我在网上搜索过,但找不到任何人报告或发布此错误。我应该报告这个吗?

然后我在这里发布此内容的原因是因为我想要一个修复'修复'这个bug(即建议在某个地方实现某些代码,以便SpriteKit以“预期的方式”进行命中测试)

复制错误:

  

使用" Hello World"当你开始一个新的" Game" Xcode项目(它有" Hello World"点击时添加火箭精灵)。

     

可选:[我还将项目中的火箭精灵图像删除为带有X的矩形,当找不到图像时,可以更容易地进行调试,直观地进行调试

     

使用userInteractionEnabled = YES将SKSpriteNode添加到场景中(我将从现在开始将其称为节点A)。

     

运行代码。

     

您会注意到,当您点击节点A时,不会产生火箭精灵。 (自命中测试成功后应该停止的预期行为 - 它在节点A上成功时停止。)

     

但是,如果你在节点A旁边产生一些火箭,然后点击节点A和火箭重叠的地方,那么就可以在节点A上产生另一个火箭 - 但这不应该&# 39;是可能的。这意味着在最顶层节点(默认情况下具有userInteractionEnabled = NO的火箭)上的命中测试失败后,它不是测试下一个节点A,而是测试火箭的父节点,而不是场景。

注意:我使用的是Xcode 7.3.1,Swift,iOS - 我还没有测试过这个bug是否具有普遍性。

额外的细节:我做了一些额外的调试(对上面的复制有轻微的复杂性),并确定之后将命中测试发送到,因此不一定是场景。

3 个答案:

答案 0 :(得分:3)

我怀疑这是一个错误或文档不正确。无论哪种方式,这是一个可能正是您正在寻找的解决方法。

听起来您想要与可能

的节点进行交互
  1. userInteractionEnabled属性设置为false
  2. 的一个或多个节点遮挡
  3. “背景”节点的孩子
  4. 深入节点树
  5. nodesAtPoint是一个很好的起点。它返回与抽头点相交的节点数组。将其添加到场景的touchesBegan,并按

    过滤未将userInteractionEnabled设置为true的节点
    let nodes = nodesAtPoint(location).filter {
        $0.userInteractionEnabled
    }
    

    此时,您可以按zPosition 节点树深度对节点数组进行排序。您可以使用以下扩展名来确定节点的这些属性:

    extension SKNode {
        var depth:(level:Int,z:CGFloat) {
            var node = parent
            var level = 0
            var zLevel:CGFloat = zPosition
            while node != nil {
                zLevel += node!.zPosition
                node = node!.parent
                level += 1
            }
            return (level, zLevel)
        }
    }
    

    并使用

    对数组进行排序
    let nodes = nodesAtPoint(location)
        .filter {$0.userInteractionEnabled}
        .sort {$0.depth.z == $1.depth.z ? $0.depth.level > $1.depth.level : $0.depth.z > $1.depth.z}
    

    要测试上述代码,请定义允许用户交互的SKSpriteNode子类

    class Sprite:SKSpriteNode {
        var offset:CGPoint?
        // Save the node's relative location
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            if let touch = touches.first {
                let location = touch.locationInNode(self)
                offset = location
            }
        }
        // Allow the user to drag the node to a new location
        override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
            if let touch = touches.first, parentNode = parent, relativePosition = offset {
                let location = touch.locationInNode(parentNode)
                position = CGPointMake(location.x-relativePosition.x, location.y-relativePosition.y)
            }
        }
        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
            offset = nil
        }
    }
    

    并将以下触摸处理程序添加到SKScene子类

    var selectedNode:SKNode?
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let touch = touches.first {
            let location = touch.locationInNode(self)
            // Sort and filter nodes that intersect with location
            let nodes = nodesAtPoint(location)
                .filter {$0.userInteractionEnabled}
                .sort {$0.depth.z == $1.depth.z ? $0.depth.level > $1.depth.level : $0.depth.z > $1.depth.z}
            // Forward the touch events to the appropriate node
            if let first = nodes.first {
                first.touchesBegan(touches, withEvent: event)
                selectedNode = first
            }
        }
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let node = selectedNode {
            node.touchesMoved(touches, withEvent: event)
        }
    }
    
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let node = selectedNode {
            node.touchesEnded(touches, withEvent: event)
            selectedNode = nil
        }
    }
    

    以下电影展示了如何使用上述代码拖放其他精灵下的精灵(使用userInteractionEnabled = true)。请注意,即使精灵是覆盖整个场景的蓝色背景精灵的子项,当用户拖动精灵时也会调用场景touchesBegan

    enter image description here

答案 1 :(得分:2)

由于模板的SKView实例在ignoresSiblingOrder on {的实现中true属性设置为viewDidLoad,因此似乎出现了差异{1}}。默认情况下,此属性为GameViewController

来自文档:

  

一个布尔值,指示父子关系和兄弟关系是否影响场景中节点的呈现顺序。   默认值为false,这意味着当多个节点共享相同的z位置时,这些节点将按确定的顺序进行排序和呈现。父母在他们的孩子面前呈现,兄弟姐妹按照数组顺序呈现。当此属性设置为false时,在确定渲染顺序时将忽略树中节点的位置。同一z位置处的节点的渲染顺序是任意的,并且可以在每次渲染新帧时改变。当忽略兄弟和父顺序时,SpriteKit会应用其他优化来提高渲染性能。如果需要以特定和确定的顺序呈现节点,则必须设置这些节点的z位置。

所以在你的情况下你应该能够简单地删除这一行来获得通常的行为。正如@Fujia在评论中所指出的,这应该只影响渲染顺序,而不是命中测试。 / p>

与UIKit一样,SpriteKit的true的直接后代可能会实施其触摸处理方法,以便在响应者链中转发事件。因此,这种不一致可能是由UIResponder上的这些方法的重写实现引起的。如果你有理由确定问题在于这些继承的方法,你可以通过用自己的事件转发逻辑覆盖它们来解决这个问题。如果是这样的话,那么在您的测试项目中提交错误报告也很不错。

答案 2 :(得分:2)

您可以通过覆盖场景mouseDown(或等效的触摸事件)来解决此问题,如下所示。基本上,您检查该点的节点,找到具有最高zPosition和userInteractionEnabled的节点。当你没有这样的节点作为最开始的位置时,这可以作为后备。

override func mouseDown(theEvent: NSEvent) {
    /* Called when a mouse click occurs */
    let nodes = nodesAtPoint(theEvent.locationInNode(self))

    var actionNode : SKNode? = nil
    var highestZPosition = CGFloat(-1000)

    for n in nodes
    {
        if n.zPosition > highestZPosition && n.userInteractionEnabled
        {
            highestZPosition = n.zPosition
            actionNode = n
        }
    }

    actionNode?.mouseDown(theEvent)
}