错误 - 当兄弟姐妹重叠时,命中测试没有按预期工作:
场景中有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是否具有普遍性。
额外的细节:我做了一些额外的调试(对上面的复制有轻微的复杂性),并确定之后将命中测试发送到父,因此不一定是场景。
答案 0 :(得分:3)
我怀疑这是一个错误或文档不正确。无论哪种方式,这是一个可能正是您正在寻找的解决方法。
听起来您想要与可能
的节点进行交互userInteractionEnabled
属性设置为false
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
。
答案 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)
}