Luke Roberts
Luke Roberts

Reputation: 311

Swift 3 Bullet Firing Delay

In my game, you tap anywhere on the screen and a bullet goes in that direction. The only problem is that you can shoot as fast as you can tap. Is there any way to add a delay after each shot. So I would like to shoot, wait 1 second then shoot. Here is my code in touchesEnded:

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

    guard let touch = touches.first else {
        return
    }
    let touchLocation = touch.location(in: self)

    //Set up initial location of bullet and properties
    let bullet = SKSpriteNode(imageNamed: "bullet")
    bullet.name = "Bullet"
    bullet.position = player.position
    bullet.setScale(0.75)
    bullet.zPosition = 1
    bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
    bullet.physicsBody?.isDynamic = true
    bullet.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
    bullet.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
    bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
    bullet.physicsBody?.usesPreciseCollisionDetection = true

    //Determine offset of location to bullet
    let offset = touchLocation - bullet.position

    //Stops Bullet from shooting backwards
    if (offset.y < 0) { return }

    addChild(bullet)

    //Get the direction of where to shoot
    let direction = offset.normalized()

    //Make it shoot far enough to be guaranteed off screen
    let shootAmount = direction * 1000

    //Add the shoot amount to the current position
    let realDest = shootAmount + bullet.position

    //Create the actions

    if currentGameState == gameState.inGame {
    let actionMove = SKAction.move(to: realDest, duration: 1.0)
    let actionMoveDone = SKAction.removeFromParent()
    bullet.run(SKAction.sequence([actionMove, actionMoveDone]))
    }

}

Thanks for any help.

Upvotes: 3

Views: 572

Answers (3)

Whirlwind
Whirlwind

Reputation: 13675

You can do this using action keys. An action key is a string that makes an action identifiable.

How to use it in this case?

As I said already in comments, you will fire a bullet, then run an action with a key, on a specific node, which will last one second. A presence of this key/action means that weapon is locked. So every time you try to fire a bullet, you check if this key is present on a specific node. When action finishes, the key will be automatically removed as well. Here is the code:

import SpriteKit

let kLockWeaponActionKey = "kLockWeaponActionKey"

class GameScene: SKScene {


    func shoot(atPoint targetLocation:CGPoint){

        // 1 check if weapon is unlocked, or return
        guard self.action(forKey: kLockWeaponActionKey) == nil else {

            print("Weapon locked")

            return
        }

        let bullet = SKSpriteNode(color: .purple, size: CGSize(width: 20, height: 20))

        addChild(bullet)

        let shoot = SKAction.move(to: targetLocation, duration: 3)

        //2 shoot
        bullet.run(shoot)

        //3 lock weapon
        self.run(SKAction.wait(forDuration: 1), withKey: kLockWeaponActionKey)
    }

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

        if let touch = touches.first {

            let targetLocation = touch.location(in: self)

            self.shoot(atPoint:targetLocation)

        }
    }
}

If you try to spam bullets fast, you will see a log in the console which says : "weapon locked".

Upvotes: 0

sabi
sabi

Reputation: 423

This is a more simple approach, based on the use of Date:

var time = Date()

func shoot(after timeInterval: Double) {
    guard Date() - timeInterval > time else {
        print("WAIT")
        return
    }
    print("SHOOT")
    time = Date() // reset the timer
}

// CALL THIS INSIDE touchesEnded
shoot(after: 1)

Just modify for your needs :]

Upvotes: 1

schirrmacher
schirrmacher

Reputation: 2357

You could take a look at the Throttle implementation of RxSwift for one possible solution. Throttle is used to limit the number of events created in a defined time interval:

let timeIntervalSinceLast: RxTimeInterval

if let lastSendingTime = _lastSentTime {
   timeIntervalSinceLast = now.timeIntervalSince(lastSendingTime)
}
else {
   timeIntervalSinceLast = _parent._dueTime
}

let couldSendNow = timeIntervalSinceLast >= _parent._dueTime

if couldSendNow {
   self.sendNow(element: element)
   return
}

Upvotes: 0

Related Questions