Ios &引用;暂停“;斯威夫特的比赛

Ios &引用;暂停“;斯威夫特的比赛,ios,swift,swift3,grand-central-dispatch,Ios,Swift,Swift3,Grand Central Dispatch,我用Swift制作了一个游戏,里面有怪物出现。怪物的出现和消失都是基于计时器使用的,如下所示: func RunAfterDelay(_ delay: TimeInterval, block: @escaping ()->()) { let time = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) DispatchQueue.main.as

我用Swift制作了一个游戏,里面有怪物出现。怪物的出现和消失都是基于计时器使用的,如下所示:

func RunAfterDelay(_ delay: TimeInterval, block: @escaping ()->()) 
{
    let time = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)

    DispatchQueue.main.asyncAfter(deadline: time, execute: block)
}
addTimer(delay: 4, repeating: true)
        { [unowned self] in
            self.spawnMonster()
        }
然后我会这样称呼它(例如在2秒后产卵):

然后我做了类似的隐藏动作(x秒后,我将怪物击溃)

因此,我在屏幕顶部创建了一个设置图标,当你点击它时,一个巨大的矩形窗口会出现以更改游戏设置,但问题自然是怪物仍然会在背景中繁殖。如果我把球员挥舞到另一个屏幕上,我相信我将失去我所有的比赛状态,并且不能在没有开始的情况下回到比赛中(球员可能在比赛的中间)。 有没有办法告诉我在上面创建的所有游戏计时器,即

DispatchQueue.main.asyncAfter(deadline: time, execute: block)
当我这么说的时候停下来继续?我想用所有的定时器都可以(如果没有办法标记和暂停某些定时器的话)


谢谢

我已经解决了这个问题,希望在下面的结论中分享我的研究/编码时间。为了更简单地重申这个问题,我实际上想实现这一点(而不是简单地使用SpriteKit场景暂停,这非常简单):

  • 在Swift中启动一个或多个计时器
  • 停止所有计时器(当用户按暂停时)
  • 当用户取消暂停时,所有计时器将在其停止的位置重新启动
  • 有人向我提到,因为我使用的是DispatchQueue.main.asyncAfter,无法以我想要的方式暂停/停止(您可以取消,但我离题了)。这是有道理的,毕竟我在做一个异步后。但要真正启动计时器,需要使用NSTimer(现在在Swift3中称为timer)

    在研究之后,我发现暂停/取消暂停实际上是不可能的,所以当你想重新启动暂停的计时器时,你可以通过创建一个新的计时器来“作弊”。我的结论如下:

      let timerKey = UUID().uuidString
    
    let myTimer: Timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) {
                _ in
                   block()
                   self.timers.removeValue(forKey: timerKey)
                }
    
            }
    
  • 当每个计时器启动时,记录您需要的延迟时间(我们访问后者),并记录此计时器触发的时间。例如,如果它在3秒内启动并执行代码,则将时间记录为Date()+3秒。我使用以下代码实现这一点:
  • 现在您已经记录了计时器将触发的时间,您可以等待用户按stop。当它们执行此操作时,您将使用.invalidate()使每个计时器无效,并立即记录停止的时间。事实上,此时,您还可以完全计算用户启动时所需的剩余延迟,如下所示:
  • 当用户点击开始时,您可以通过简单地创建新的计时器,利用前面提到的余数(remaingdelay)和viola‘您有了新的计时器’再次启动所有计时器
  • 现在因为我有多个计时器,我决定需要在AppDelegate中创建一个字典(通过服务类访问),以保留所有活动计时器。每当计时器结束时,我都会将其从字典中删除。最后我制作了一个特殊的类,它具有计时器、初始延迟和启动时间的属性。从技术上讲,我可以使用数组,也可以在该类上设置计时器键,但我跑题了

    我创建了自己的addTimer方法,该方法将为每个计时器创建一个唯一的键,然后当计时器的代码完成时,它将按如下方式自动删除:

      let timerKey = UUID().uuidString
    
    let myTimer: Timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) {
                _ in
                   block()
                   self.timers.removeValue(forKey: timerKey)
                }
    
            }
    
    注意:block()只是调用您在计时器中封装的任何块。例如,我做了一些很酷的事情,比如:

    func RunAfterDelay(_ delay: TimeInterval, block: @escaping ()->()) 
    {
        let time = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
    
        DispatchQueue.main.asyncAfter(deadline: time, execute: block)
    }
    
    addTimer(delay: 4, repeating: true)
            { [unowned self] in
                self.spawnMonster()
            }
    
    因此,addTimer将运行self.spawnmoster代码(作为block()),然后在完成后从字典中自动删除

    后来我变得更复杂了,我做了一些事情,比如让计时器不断重复运行,而不是自动删除,但对于我来说,这只是很多非常具体的代码,可能会占用太多的时间:)

    无论如何,我真的希望这能帮助一些人,并且愿意回答任何人提出的任何问题。我在这上面花了很多时间


    谢谢

    我将在这里为您展示一些东西,并为未来的读者展示更多东西,这样他们就可以通过复制粘贴这些代码来获得一个可行的示例。接下来是以下几件事:

    1.使用
    SKAction

    2.暂停操作

    3.暂停节点本身

    4.正如我所说,还有几件事:)

    请注意,所有这些都可以用不同的方式完成,甚至比这更简单(当涉及到暂停动作和节点时),但我将向您展示详细的方式,以便您可以选择最适合您的方式

    初始设置

    我们有一个英雄节点和一个敌人节点。敌人节点将在屏幕顶部每5秒产卵一次,并向下移动,向玩家下毒

    正如我所说,我们将只使用
    SKActions
    ,不使用
    NSTimer
    ,甚至不使用
    update:
    方法。纯粹的行动。因此,在这里,玩家将静止在屏幕底部(紫色方块),敌人(红色方块),如前所述,将向玩家移动并毒害他

    让我们看一些代码。我们需要定义所有这些工作的常规内容,比如设置物理类别、初始化和定位节点。我们还将设置敌人产卵延迟(8秒)和中毒持续时间(3秒):

    还有一个名为
    isGamePaused
    的变量,稍后我会对其进行更多的评论,但正如您所想象的,它的目的是跟踪游戏是否暂停,以及当用户点击黄色的大方形按钮时,它的值是否会发生变化

    辅助方法

    我已经为节点创建创建了一些辅助方法。我有一种感觉,这不是你个人需要的,因为你看起来对编程有很好的理解,但我会为完整性和未来的读者做这件事。所以这里是设置节点名称或其p的地方
    //Inside of a GameScene.swift
    
        let hero = SKSpriteNode(color: .purple , size: CGSize(width: 50, height: 50))
        let button = SKSpriteNode(color: .yellow, size: CGSize(width: 120, height:120))
        var isGamePaused = false
        let kPoisonDuration = 3.0
    
        override func didMove(to view: SKView) {
            super.didMove(to: view)
    
            self.physicsWorld.contactDelegate = self
    
            hero.position = CGPoint(x: frame.midX,  y:-frame.size.height / 2.0 + hero.size.height)
            hero.name = "hero"
            hero.physicsBody = SKPhysicsBody(rectangleOf: hero.frame.size)
            hero.physicsBody?.categoryBitMask = ColliderType.Hero.rawValue
            hero.physicsBody?.collisionBitMask = 0
            hero.physicsBody?.contactTestBitMask = ColliderType.Enemy.rawValue
            hero.physicsBody?.isDynamic = false
    
            button.position = CGPoint(x: frame.maxX - hero.size.width, y: -frame.size.height / 2.0 + hero.size.height)
            button.name = "button"
    
            addChild(button)
            addChild(hero)
    
            startSpawningEnemies()
    
        }
    
     func getEnemy()->SKSpriteNode{
    
                let enemy = SKSpriteNode(color: .red , size: CGSize(width: 50, height: 50))
                enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.frame.size)
                enemy.physicsBody?.categoryBitMask = ColliderType.Enemy.rawValue
                enemy.physicsBody?.collisionBitMask = 0
                enemy.physicsBody?.contactTestBitMask = ColliderType.Hero.rawValue
                enemy.physicsBody?.isDynamic = true
                enemy.physicsBody?.affectedByGravity = false
                enemy.name = "enemy"
    
                return enemy
            }
    
    func spawnEnemy(atPoint spawnPoint:CGPoint){
    
            let enemy = getEnemy()
    
            enemy.position = spawnPoint
    
            addChild(enemy)
    
            //moving action
    
            let move = SKAction.move(to: hero.position, duration: 5)
    
            enemy.run(move, withKey: "moving")
        }
    
    func startSpawningEnemies(){
    
            if action(forKey: "spawning") == nil {
    
                let spawnPoint = CGPoint(x: frame.midX, y: frame.size.height / 2.0 - hero.size.height)
    
                let wait = SKAction.wait(forDuration: 8)
    
                let spawn = SKAction.run({[unowned self] in
    
                    self.spawnEnemy(atPoint: spawnPoint)
                })
    
                let sequence = SKAction.sequence([spawn,wait])
    
                run(SKAction.repeatForever(sequence), withKey: "spawning")
            }
        }
    
    func didBegin(_ contact: SKPhysicsContact) {
    
            let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
    
            switch contactMask {
    
            case ColliderType.Hero.rawValue | ColliderType.Enemy.rawValue :
    
    
                if let projectile = contact.bodyA.categoryBitMask == ColliderType.Enemy.rawValue ? contact.bodyA.node : contact.bodyB.node{
    
                    projectile.removeAllActions()
                    projectile.removeFromParent()
                    addPoisionEffect(atPoint: hero.position)
    
                }
    
            // Handle more cases here
    
            default : break
                //Some other contact has occurred
            }
        }
    
    func addPoisionEffect(atPoint point:CGPoint){
    
            if let poisonEmitter = SKEmitterNode(fileNamed: "poison"){
    
                let wait = SKAction.wait(forDuration: kPoisonDuration)
    
                let remove = SKAction.removeFromParent()
    
                let sequence = SKAction.sequence([wait, remove])
    
                poisonEmitter.run(sequence, withKey: "emitAndRemove")
                poisonEmitter.name = "emitter"
                poisonEmitter.position = point
    
                poisonEmitter.zPosition = hero.zPosition + 1
    
                addChild(poisonEmitter)
    
            }  
        }
    
    func togglePaused(){
    
            let newSpeed:CGFloat = isGamePaused ? 1.0 : 0.0
    
            isGamePaused = !isGamePaused
    
            //pause spawning action
            if let spawningAction = action(forKey: "spawning"){
    
                spawningAction.speed = newSpeed
            }
    
            //pause moving enemy action
            enumerateChildNodes(withName: "enemy") {
                node, stop in
                if let movingAction = node.action(forKey: "moving"){
    
                    movingAction.speed = newSpeed
                }
    
            }
    
            //pause emitters by pausing the emitter node itself
            enumerateChildNodes(withName: "emitter") {
                node, stop in
    
                node.isPaused = newSpeed > 0.0 ? false : true
    
            }
        }