如何让 touch.locationInNode() 识别节点与其 child 之间的区别?

How to make touch.locationInNode() recognize the difference between a node and its child?

我首先声明了两个 SKSpriteNode,handle 和 blade,并将 handle 添加为 self 的 child,将 blade 添加为 handle[的 child =14=]

var handle = SKSpriteNode(imageNamed: "Handle.png")
var blade = SKSpriteNode(imageNamed: "Blade.png")

override func didMoveToView(view: SKView) {

    handle.position = CGPointMake(self.size.width / 2, self.size.height / 14)
    blade.position = CGPointMake(0, 124)

    self.addChild(Handle)
    Handle.addChild(Blade)
}

当我点击句柄时,它会打印到控制台 "Handle was clicked",但是当我点击 Blade 时,它也会打印 "Handle was clicked"。明明是blade是handle的child,但是点击blade的时候怎么弄,打印出来的是"Blade was clicked"?

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    for touch in (touches as! Set<UITouch>) {
        let location = touch.locationInNode(self)
        if (Handle.containsPoint(location)){
            NSLog("Handle was clicked")
        }
        else if (Blade.containsPoint(location)){
            NSLog("Blade was clicked")
        }

    }
}

这应该可以在不更改太多现有代码的情况下工作...

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    for touch in (touches as! Set<UITouch>) {

        let locationInScene = touch.locationInNode(self)
        let locationInHandel = touch.locationInNode(Handle)

        if (Blade.containsPoint(locationInHandel)){

            NSLog("Blade was clicked")

        }
        else if (Handle.containsPoint(locationInScene)){

            NSLog("Handle was clicked")

        }

    }
}

请注意,您首先要检查 blade,然后再检查句柄。另请注意,您必须转换接触点以从句柄内为您提供一个点。

话虽如此,这将在小范围内起作用,但您可能想看看为 SKSpriteNode 创建一个名为 Handle 或 Sword 的子类(这就是为什么您不对变量名使用首字母大写的原因,它们通常是仅对 类) 使用第一个大写字母,将其设置为 userInteractionEnabled,然后覆盖该子类中的 touchesBegan 并查看它是否触及 blade,如果没有触及手柄。

希望这对您有所帮助并且有意义。

确定用户是触摸了剑柄还是 blade 相当简单,但有一些注意事项。下面假设 1. zRotation = 0 时剑的图像向右, 2. 剑的 anchorPoint 为 (0, 0.5), 3. 剑 (blade和句柄)是单个精灵节点。当您将一个 sprite 添加到另一个 sprite 时,parent 的框架的大小会扩展以包含 child 节点。这就是为什么你的测试 Handle.containsPoint 无论你点击剑的哪个位置都是正确的。

下图是一把深灰色手柄(左边)和浅灰色的剑精灵blade。剑周围的黑色矩形代表精灵的框架,圆圈代表用户触摸的位置。标有a的线的长度是触摸点到剑底的距离。我们可以测试这个距离,看看用户是否触摸了手柄(如果 a <= handleLength)或 blade(如果 a > handleLength)。当zRotation = 0,a = x所以考的是x <= handleLength,其中剑底是x = 0.

下图中,剑旋转了90度(即zRotation = M_PI_2)。同样,如果 a <= handleLength,用户触摸了句柄,否则用户触摸了 blade。唯一的区别是 a 现在是 y 值而不是 x 由于剑的旋转。在这两种情况下,都可以使用框架的边界框来检测用户是否触摸了剑。

然而,当精灵旋转45度时,它的边框会自动扩展以包围精灵,如下图黑色矩形所示。因此,当用户触摸矩形中的任何地方时,测试 if sprite.frame.contains(location) 将为真。这可能导致用户在触摸的位置离剑相对较远时(即,当距离 b 较大时)拿起剑。如果我们希望最大触摸距离在所有旋转角度上都相同,则需要进行额外的测试。

好消息是 Sprite Kit 提供了一种从一个坐标系转换到另一个坐标系的方法。在这种情况下,我们需要从场景坐标转换为剑坐标。这极大地简化了问题,因为它还将点旋转到新的坐标系。从场景坐标转换为剑坐标后,转换后的触摸位置xy值与所有旋转角度的距离ab相同!现在我们知道 ab,我们可以确定触摸距离剑有多近以及用户是否触摸了手柄或 blade。

根据以上,我们可以实现如下代码:

    let location = touch.locationInNode(self)
    // Check if the user touched inside of the sword's frame (see Figure 1-3)
    if (sword.frame.contains(location)) {
        // Convert the touch location from scene to sword coordinates
        let point = sword.convertPoint(location, fromNode: self)
        // Check if the user touched any part of the sword. Note that a = point.x and b = point.y
         if (fabs(point.y) < sword.size.height/2 + touchTolerance) {
             // Check if the user touched the handle
             if (point.x <= handleLength) {
                 println("touched handle")
             }
             else {
                 println("touched blade")
             }
        }
    }