错误:使用兄弟节点和Sprite Kit中的userInteractionEnabled属性进行命中测试

Shu*_*060 7 touch hittest sprite-kit sknode swift

当兄弟姐妹重叠时,错误 - 命中测试无法正常工作:

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

最顶层的节点具有userInteractionEnabled = NO另一个节点userInteractionEnabled = YES.

如果触摸了重叠,则在最顶层节点被命中测试并且失败(因为userInteractionEnabled = NO)之后,而不是底部节点是下一个被命中测试的节点,它被跳过并且2个兄弟节点的父节点被命中测试.

应该发生的是,下一个兄弟(底部节点)是经过重击测试而不是命中测试跳到父节点.

根据Sprite Kit文档:

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


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

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


然后我在这里发布这个的原因是因为我想要一个关于这个bug的'修复'的建议(即建议在某处实现某些代码,以便SpriteKit以'预期'方式工作-testing)


复制错误:

使用在Xcode中启动新的"游戏"项目时提供的"Hello World"模板(它具有"Hello World"并在您单击时添加火箭精灵).

可选:[我还从项目中删除了火箭精灵图像,因为X当找不到图像时出现的矩形更容易用于调试,可视化]

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

运行代码.

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

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


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


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

Eps*_*lon 3

我怀疑这要么是一个错误,要么是文档不正确。不管怎样,这可能是您正在寻找的解决方法。

听起来您想与可能是的节点进行交互

  1. 被一个或多个节点遮挡userInteractionEnabled属性设置为的一个或多个节点遮挡false
  2. “背景”节点的子节点
  3. 在节点树的深处

nodesAtPoint是一个很好的起点。它返回与抽头点相交的节点数组。将其添加到场景中touchesBegan并过滤未userInteractionEnabled设置为的true节点

let nodes = nodesAtPoint(location).filter {
    $0.userInteractionEnabled
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

并对数组进行排序

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}
Run Code Online (Sandbox Code Playgroud)

为了测试上面的代码,定义一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

并将以下触摸处理程序添加到子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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述