1

I am brand new to XCode, SpriteKit, and Swift in general. I am trying to develop a simple mobile game where the player can shoot fireballs using the A button of the GCVirtualController. I want one fireball to shoot when the user taps the button and multiple fireballs to steadily shoot when the player holds the button.

The issue I'm having is figuring out how to distinguish a tap from a hold. Touching the button at all causes a ton of sprites to spawn instantly as if there's no way to just shoot once. Currently I am checking if the user presses the A button within the update function since I couldn't figure out how to make it work within a touchesBegan or touchesEnded function.

Below is update and the function shootFireball which creates and moves the fireball. I know the if statement in shootFireball is stupid, but I was messing around for a while for any way to just get one fireball to spawn and shoot at a time.

Any guidance is greatly appreciated.

override func update(_ currentTime: TimeInterval) {
        
        // Joystick Movement //
        playPosX = CGFloat((virtualController?.controller?.extendedGamepad?.leftThumbstick.xAxis.value)!)
        playPosY = CGFloat((virtualController?.controller?.extendedGamepad?.leftThumbstick.yAxis.value)!)
        
        
        if playPosX >= 0.5 {
            player.position.x += 3
        }
        if playPosX <= -0.5 {
            player.position.x -= 3
        }
        
        if playPosY >= 0.5 {
            player.position.y += 3
        }
        if playPosY <= -0.5 {
            player.position.y -= 3
        }
    
      
        //  Basic Attack: A Button  //
        let aPress = virtualController?.controller?.extendedGamepad?.buttonA.isPressed


        // Check if the A button is being pressed
        if(aPress == true){
            // Temporary value. Want to get the direction of fireball from the joystick's positioning
            let direct = CGVectorMake(player.position.x, player.position.x)

            // Call method to create and shoot fireball
            let fireBullet = SKAction.run(){
                self.shootFireball(direction: direct)
            }

            // Small delay before firing another fireball
            let wait = SKAction.wait(forDuration: 1.5)

            // Execute the attack
            let fire = SKAction.sequence([fireBullet, wait])
            run(fire)

        }
}

// Create and shoot fireball
func shootFireball(direction: CGVector) {
        let fireball = SKSpriteNode(imageNamed: "fireball.png")
        fireball.setScale(0.8)
        fireball.position = player.position
        fireball.zPosition = 1
        let waits = SKAction.wait(forDuration: 2.5)
       
        // Lazy validation 
        if(self.children.count == 1){
            
            // Add fireball node
            addChild(fireball)
            
            let moveFireball = SKAction.move(by: direction, duration: 1)
            let deleteFireball = SKAction.removeFromParent()
      
            let fireballSequence = SKAction.sequence([moveFireball, deleteFireball])
            fireball.run(fireballSequence)
        }
        
   
}

1 Answers1

0

Just a premise, I've never used GCVirtualController as I prefer to code my own joystick, plus I'm also still learning Swift, so this is a solution, it might not be the best one though.

The issue you are facing is because the update method is called once per frame, so usually 60 times per second unless you changed the preferredFramesPerSecond property. This means that, if the A button is pressed, you are trying to shoot a lot of fireballs.

A way to achieve what you want is to create an action which repeats itself forever, run it one time and keep track if the action is already running to avoid multiple shootings (a behaviour that would usually lead to your app crashing). Then you can stop the action from running by using removeAction once the user lift his finger from the button.

Here is a simplified version of your code, isPressed is equal to virtualController?.controller?.extendedGamepad?.buttonA.isPressed in your code, while isShooting is the boolean you can use to track if the infinite-shooting action is already running or not. You should declare var isShooting = false at first. I tested this and it works as you intended.

override func update(_ currentTime: TimeInterval) {

    // i think it's a good practice to call super
    super.update(currentTime)

    // moving player, etc...

    if isPressed, !isShooting {
        // user just pressed A but fireballs are not being shot, then setup fireball shooting

        // you set it to true, so that this block is not run again when update is called at the next frame
        isShooting = true

        let fireBullet = SKAction.run {
            self.shootFireball()
        }
        let wait = SKAction.wait(forDuration: 1.5)
        let fire = SKAction.sequence([fireBullet, wait])

        // you set up a single fire action to repeat itself forever, then you will remove this action once the user stops pressing A
        let fireForever = SKAction.repeatForever(fire)
        run(fireForever, withKey: "fireForever")

    } else if !isPressed, isShooting {
        // you just stop the fire when the user lift his finger from the button
        removeAction(forKey: "fireForever")
        // set the tracker variable to false 
        isShooting = false
    } else if isPressed, isShooting {
        // user is pressing A, fireballs are being shot, do nothing
    } else if !isPressed, !isShooting {
        // user is not pressing A, fireballs are not being shot, do nothing 
    }
}

The last two cases are there just to let you understand what happens in all 4 of them.

Another approach which does not involve CGVirtualController is to code your own A button, and, resorting to UIKit, using UILongPressGestureRecognizer and UITapGestureRecognizer. In this way you can handle the two different cases separately.

oneshot
  • 591
  • 1
  • 5
  • 27