0

Essentially I am trying to incorporate X2 gamescene buttons that do the following functions:

1) Tap to fly (this I have working) 2) Tap to shoot a projectile from Player position (I do not have working).

My problem is I currently have the fly func set when touched anywhere on the screen. I have tried the following :

This is in reference to my GameScene : I thought in order to split this out I would need a node on the screen to reference this function. This does not error in the console but does not appear in the GameScene.

// Button to trigger shooting :

    let btnTest = SKSpriteNode(imageNamed: "Crater")
    btnTest.setScale(0.2)
    btnTest.name = "Button"
    btnTest.zPosition = 10
    btnTest.position = CGPoint(x: 100, y: 200)
    self.addChild(btnTest)

Next in the Player class I have the following broken down:

var shooting = false

var shootAnimation = SKAction()
var noshootAnimation = SKAction()


   Init func: 

    self.run(noshootAnimation, withKey: "noshootAnimation")

    let projectile = SKSpriteNode(imageNamed: "Crater")
    projectile.position = CGPoint (x: 100, y: 50)
    projectile.zPosition = 20
    projectile.name = "projectile"


    // Assigning categories to Game objects:
    self.physicsBody?.categoryBitMask =
        PhysicsCategory.plane.rawValue
    self.physicsBody?.contactTestBitMask =
        PhysicsCategory.ground.rawValue 
    self.physicsBody?.collisionBitMask =
        PhysicsCategory.ground.rawValue

    self.physicsBody?.applyImpulse(CGVector(dx: 300, dy: 0))
    self.addChild(projectile)



      // Start the shoot animation, set shooting to true:
       func startShooting() {


       self.removeAction(forKey: "noshootAnimation")
       self.shooting = true

   }

   // Stop the shoot animation, set shooting to false:
   func stopShooting() {


       self.removeAction(forKey: "shootAnimation")
       self.shooting = false
   }

The node appears in the GameScene which looks promising, finally I move to the last bit of code in the GameScene as follows:

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

                    let location = touch.location(in: self)

                    let nodeTouched = atPoint(location)

                    if let gameSprite = nodeTouched as? GameSprite {

                    gameSprite.onTap()
                    }

                    // Check the HUD buttons which I have appearing when game is over…
                    if nodeTouched.name == "restartGame" {
                    // Transition to a new version of the GameScene
                    // To restart the Game
                        self.view?.presentScene(GameScene(size: self.size), transition: .crossFade(withDuration: 0.6))
                    }
                    else if nodeTouched.name == "returnToMenu"{
                    // Transition to the main menu scene
                        self.view?.presentScene(MenuScene(size: self.size), transition: . crossFade(withDuration: 0.6))

                    }
            }

                Player.startFly()
                player.startShooting()

                    }

            override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
                Player.stopFly()
                player.stopShooting()

            }

            override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
                Player.stopFly()
                player.stopShooting()

            }

            override func update(_ currentTime: TimeInterval) {

                player.update()         
}
}

Unfortunately nothing happens in the GameScene and the node doesn’t fire when the screen is pressed, with the code above is there anyway I can amend this to allow for both ‘tap to fly’ + ‘tap to shoot’ functions. I still can’t figure out how to get the button I had declared early on in the GameScene to appear in which my gesture / touch position can be localised to this node on the screen as oppose to the whole screen in which I have currently..

I can say this sounded more simple in my head to begin with than actually coding together.

halfer
  • 19,824
  • 17
  • 99
  • 186
AJ James
  • 11
  • 5

1 Answers1

0

Firstly, the above code does not have any calls to run the animation with the key "shootAnimation" and is missing the call to re-run the not-shooting animation when stopShooting() and the call to re-run the shooting animation in startShooting(). The methods should include these as shown below

func startShooting() {
    self.removeAction(forKey: "noshootAnimation")

    // Add this call
    self.run(shootAnimation)
    self.shooting = true
}

func stopShooting() {
    self.removeAction(forKey: "shootAnimation")

    // Add this call
    self.run(noshootAnimation)
    self.shooting = true
}

Secondly, the noshootAnimation and shootAnimation animations are empty actions: they will not do anything if initialized as SKAction(). Depending on what you are trying to do, there are a number of class methods of SKAction that will create actions for you here.

Thirdly, any SKNode (recall that a scene is an SKNode subclass) will not receive touch calls if the node's isUserInteractionEnabled property is set to false (to which it is defaulted); instead, it will be treated as if its parent received the call. However, should a node's isUserInteractionEnabled be true, it will be the one that receives the UIResponder calls, not its parent node. Make sure that this property is set to true for the scene and for any nodes that need to receive touches (you can do this in didMove(to:) in the scene or elsewhere).

I will now propose an improvement. I frequently use buttons in Spritekit, but they are all subclasses of a custom button class (itself a subclass of SKSpriteNode) that uses protocol delegation to alert members of touch events. The scheme looks like this:

class Button: SKSpriteNode {
    weak var responder: ButtonResponder?
    // Custom code

    // MARK: UIResponder
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)

        // Respond here
        responder?.respond(to: self)
    }
}

protocol ButtonResponder: AnyObject {
    func respond(to button: Button)
}

class MyScene: SKScene, ButtonResponder {
    func respond(to button: Button) {
        // Do something on touch, but typically check the name

        switch button.name {
        case "myButton":
            // Do something specific
        default:
            break
        }
    }

    override func didMove(to view: SKView) {
        // Set the scene as a responder
        let buttonInScene = childNode(withName: "myButton") as! Button
        buttonInScene.responder = self
    }
}

For more on delegation, look here. Hopefully this helped a little bit.

EDIT: Convert touch location

You can convert the touch to the node on the screen by passing a reference to the location(in:) method of UITouch instead of the scene as you did in your code

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

    let yourNode = childNode(withName: "yourNode")!
    for touch in touches {
        let touchLocationToYourNode = touch.location(in: yourNode)
        // Do something
    }
}

Alternatively, use SKNode's convert(_:from:) and convert(_:to:) methods

let nodeA = SKNode()
let nodeB = SKNode()

nodeA.xScale = 1.5
nodeA.yScale = 1.2
nodeA.position = .init(x: 100, y: 100)
nodeA.zRotation = .pi

let pointInNodeACoordinateSystem = CGPoint(x: 100, y: 100)
let thatSamePointInNodeBCoordinateSystem = nodeB.convert(pointInNodeACoordinateSystem, from: nodeA)
  • Hello! Thanks you for the time to review this post! I have implemented as per your proposal above in consideration, this is all working perfectly!! Hours of frustration solved! I finally have the button functions working during GameScene, thanks for the logical walk-through, this has helped my understanding for sure!! – AJ James May 26 '20 at 16:11