Swift 3.0: SKSpriteNode as Button не работает, если задан родительский элемент, отличный от SKView?

Уже есть несколько вопросов о том, как создать кнопку в SpriteKit, например Swift Spritekit Adding Button. Программно и настройка кнопок в SKScene.

В любом случае решение состоит в том, чтобы создать SKSpriteNode с текстурой для кнопки, а затем в функции touchesEnded проверить, попадает ли касание в кнопку-SKSpriteNode.

Интересно, однако, что если сделать кнопку дочерней, скажем, SKCameraNode или другой SKSpriteNode, то этот метод больше не работает.

Мой вопрос: почему? и как преодолеть эту дилемму.

ОБНОВИТЬ:

Что касается сообщения ниже, здесь представлены две альтернативные версии простого файла GameScene.swift. Основное отличие состоит в том, что в первом случае sprite2 не является дочерним элементом камеры, а в версии 2 — им.

Примечание. Обратите внимание, что в GIF-файлах в версии 1 щелчок по фиолетовому спрайту (sprite2) приводит к выводу «Вы коснулись фиолетового спрайта», тогда как в версии 2 написано «синий спрайт». Таким образом, проблема проясняется:

Прикосновение к дочернему элементу другого узла SKNode регистрируется как касание самого верхнего родительского узла, а не как фактическое касание узла!

Новый вопрос: Как это исправить.

Дополнение: в версии 2 sprite2 немного изменил позицию из-за того, что стал дочерним элементом камеры — так как это M.W.E. и это не влияет на эту демонстрацию, я решил не исправлять это.

Версия 1

class GameScene: SKScene {
    var cam: SKCameraNode!

    var sprite = SKSpriteNode(imageNamed: "sprite")
    var sprite2 = SKSpriteNode(imageNamed: "sprite2")


    override func didMove(to view: SKView) {
        backgroundColor = SKColor.white

        sprite.position = CGPoint(x: size.width/4,y: size.height/2)
        sprite2.position = CGPoint(x: size.width/4 * 3,y: size.height/2)

        sprite.anchorPoint = CGPoint(x:0.5, y:0.5)

        // Set up the camera
        cam = SKCameraNode()
        self.camera = cam
        cam.setScale(3.0)

        cam.position = CGPoint(x: size.width/4, y: 0)
        sprite.addChild(cam)



        addChild(sprite); addChild(sprite2)


    }


    func touchDown(atPoint pos : CGPoint) {

    }

    func touchMoved(toPoint pos : CGPoint) {

    }

    func touchUp(atPoint pos : CGPoint) {

    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self)

        if sprite.contains(touchLocation) {
            print("You tapped the blue sprite")
        }

        if sprite2.contains(touchLocation) {
            print("You tapped the purple sprite")
        }

    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchUp(atPoint: t.location(in: self)) }
    }


    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
    }
}

введите здесь описание изображения

Версия 2

class GameScene: SKScene {
    var cam: SKCameraNode!

    var sprite = SKSpriteNode(imageNamed: "sprite")
    var sprite2 = SKSpriteNode(imageNamed: "sprite2")


    override func didMove(to view: SKView) {
        backgroundColor = SKColor.white

        // Scale Sprites
//        sprite.setScale(0.3)
        sprite2.setScale(0.3)

        sprite.position = CGPoint(x: size.width/4,y: size.height/2)
//        sprite2.position = CGPoint(x: size.width/4 * 3,y: size.height/2)

        sprite.anchorPoint = CGPoint(x:0.5, y:0.5)

        // Set up the camera
        cam = SKCameraNode()
        self.camera = cam
        cam.setScale(3.0)

        cam.position = CGPoint(x: size.width/4, y: 0)
        sprite.addChild(cam)



        addChild(sprite); //addChild(sprite2)
        cam.addChild(sprite2)


    }


    func touchDown(atPoint pos : CGPoint) {

    }

    func touchMoved(toPoint pos : CGPoint) {

    }

    func touchUp(atPoint pos : CGPoint) {

    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self)

        if sprite.contains(touchLocation) {
            print("You tapped the blue sprite")
        }

        if sprite2.contains(touchLocation) {
            print("You tapped the purple sprite")
        }

    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchUp(atPoint: t.location(in: self)) }
    }


    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
    }
}

введите здесь описание изображения


person SumNeuron    schedule 15.01.2017    source источник
comment
вы используете touchesEnded в SKScene, удерживающем ваши кнопки и камеру, или в подклассе SKSpriteNode, который является вашим экземпляром кнопки?   -  person Confused    schedule 15.01.2017
comment
touchesEnded в SKScene удерживая кнопки и камеру.   -  person SumNeuron    schedule 15.01.2017


Ответы (1)


Я не думаю, что то, что вы говорите, правда. Вы можете обнаружить прикосновение от дочернего узла. Чтобы продемонстрировать, я просто запустил небольшой тестовый код в одном из своих проектов SpriteKit, где обнаружил прикосновение к узлу камеры.

var cameraNode = SKCameraNode()

Затем в didMove(to:):

addChild(cameraNode)
camera = cameraNode
camera?.position = CGPoint(x: size.width/2, y: size.height/2)

Обнаружение касания cameraNode с помощью touchesEnded:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }

    let location = touch.location(in: cameraNode)

    print("LocationX: \(location.x), LocationY: \(location.y)")
}

Вот моя распечатка:

LocationX: -13.9129028320312, LocationY: 134.493041992188

Для дальнейшего пояснения, если вы добавили кнопку в качестве дочернего элемента вашего cameraNode, вы должны сделать что-то вроде этого:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }

    let location = touch.location(in: cameraNode)

    // Check if tap was within button frame (assuming your buttonNode is named button and a child of cameraNode)
    if button.frame.contains(location) {
        // Tap was within the frame of the button
        // Do whatever is necessary
    }
}

Дальнейшее редактирование -

Ваша проблема связана с узлом, в котором вы запрашиваете местоположение касания. Как я уже упоминал в своем ответе, вам нужно экстраполировать местоположение касания на основе узла, в котором вы проверяете кадр спрайта. В вашем редактировании вы обнаруживаете прикосновение только к self, что затем даст вам координаты относительно вашего scene. Если вы хотите обнаружить прикосновение к подпредставлению, такому как узел камеры, вам необходимо запросить местоположение касания в узле камеры. Вот о чем я говорю на основе кода, который вы добавили:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }

    // This will give you touch location from the camera node 'cam' NOT 'self'
    let cameraLocation = touch.location(in: cam)

    // This will give you touch location from the scene itself
    let sceneLocation = touch.location(in: self)

    if sprite.contains(sceneLocation) {
        print("You tapped the blue sprite")
    }

    if sprite2.contains(cameraLocation) {
        print("You tapped the purple sprite")
    }

}

Я только что попробовал это, и это сработало для меня.

person Pierce    schedule 15.01.2017
comment
Да, это работает, я говорю, если у вас есть спрайт, который является потомком вашей камеры. - person SumNeuron; 15.01.2017
comment
@SamNeuron - именно об этом я и говорю. Когда вы добавляете спрайт в качестве дочернего элемента вашего cameraNode, вы определяете рамку кнопки, соответствующую рамке cameraNode. Он должен работать так же. - person Pierce; 15.01.2017
comment
даже если камера также является дочерней для другого спрайта? - person SumNeuron; 16.01.2017
comment
@SumNeuron - Да, это не проблема. touchesEnded обнаруживает прикосновение к экрану, а затем переводит эти координаты в координаты запрошенного узла. - person Pierce; 17.01.2017
comment
Пожалуйста, смотрите обновление. К сожалению, по какой-то причине курсор мыши не виден, но суть верна. - person SumNeuron; 17.01.2017
comment
@SumNeuron - Пожалуйста, посмотрите мое обновление еще раз. Вы все еще не обнаруживаете прикосновение к узлу камеры. Вам нужно указать узел, в котором вы обнаруживаете прикосновение. Вы говорите self, который не обязательно будет определять местоположение касания в подпредставлении, таком как узел вашей камеры. - person Pierce; 17.01.2017