“暂停"斯威夫特游戏 [英] "Pausing" the Game in Swift

查看:182
本文介绍了“暂停"斯威夫特游戏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在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)
}

然后我就这样称呼它(例如2秒后生成):

///Spawn Monster
RunAfterDelay(2) { 
                [unowned self] in
                self.spawnMonster()
 }

然后我进行了类似的隐藏操作(x秒钟后,我将怪物消灭了.)

所以我在屏幕顶部创建了一个设置图标,当您点击它时,会出现一个巨大的矩形窗口以更改游戏设置,但是自然的问题是怪物仍然在背景中生成.如果我将玩家带到另一个屏幕,我相信我会失去我所有的游戏状态,并且必须重新开始才能重新回到游戏状态(玩家可能处于游戏的中间).

有没有办法告诉我以上创建的所有游戏计时器,即

DispatchQueue.main.asyncAfter(deadline: time, execute: block)

要暂停并继续说话吗?我想用所有计时器都可以(如果没有一种方法可以标记和暂停某些计时器).

谢谢!

解决方案

我已解决了这一问题,并希望在下面的结论中分享我的研究/编码工作时间.为了更简单地说明问题,我实际上想实现这一目标(不仅仅是简单地使用SpriteKit场景暂停,这很容易):

  1. 在Swift中启动一个或多个计时器
  2. 停止所有计时器(当用户按下暂停键时)
  3. 当用户取消暂停时,所有计时器都会在中断的位置
  4. 重新启动

有人向我提到,因为我正在使用DispatchQueue.main.asyncAfter,所以无法以我想要的方式暂停/停止(可以取消,但我离题了).毕竟我正在做asyncAfter,这才有意义.但是要真正启动计时器,您需要使用NSTimer(在Swift3中现在称为Timer).

研究后,我发现实际上不可能暂停/取消暂停,因此当您想重新启动暂停的计时器时,可以通过为每个计时器创建一个新计时器来作弊".我的结论如下:

  1. 每个计时器启动时,记录您需要的延迟(我们将访问后者),并记录该计时器 触发"的时间.因此,例如,如果它在3秒内启动并执行代码,则将时间记录为Date()+ 3秒.我使用以下代码实现了这一点:

//Take the delay you need (delay variable) and add this to the current time

let calendar = Calendar.current        
let YOUR_INITIAL_TIME_CAPTURED = calendar.date(byAdding: .nanosecond, value: Int(Int64(delay * Double(NSEC_PER_SEC))), to: Date())!

  1. 现在您已经记录了计时器将触发的时间,您可以等待用户按下Stop.完成后,您将使用.invalidate()使每个计时器无效,并立即记录停止的时间.实际上,在这一点上,您还可以完全计算出用户启动时所需的剩余延迟时间:

//Calculate the remaining delay when you start your timer back
let elapsedTime = YOUR_INITIAL_TIME_CAPTURED.timeIntervalSince(Date)
let remainingDelay = YOUR_INITIAL_TIMER_DELAY - elapsedTime

  1. 当用户点击开始"时,您可以通过简单地创建新的定时器来再次启动所有定时器,利用上述剩余时间(remainingDelay)和viola`,您将拥有新的定时器.

现在,因为我有多个计时器,所以我决定需要在AppDelegate(通过服务类访问)中创建一个词典,以保留所有活动计时器.每当计时器结束时,我都会将其从字典中删除.我最终制作了一个特殊的类,该类具有计时器,初始延迟及其启动时间的属性.从技术上讲,我可以使用数组,也可以将timer键放在该类上,但是我离题了.

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

  let timerKey = UUID().uuidString

let myTimer: Timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) {
            _ in
               block()
               self.timers.removeValue(forKey: timerKey)
            }

        }

注意:block()只是调用您包装在计时器中的任何块.例如,我做了一些很酷的事情:

addTimer(delay: 4, repeating: true)
        { [unowned self] in
            self.spawnMonster()
        }

因此addTimer将运行self.spawnMonster代码(作为block()),然后在完成后将其从字典中自动删除.

后来我变得更复杂了,做了一些事情,例如保持重复计时器的运行而不是自动删除,但是出于我的目的,这只是很多非常具体的代码,可能会花太多的时间来回复:)

无论如何,我真的希望这对某人有帮助,并希望回答任何人遇到的任何问题.我花了很多时间!

谢谢!

I created a game in Swift that involves monsters appearing. Monsters appear, and disappear, based on timers using something like this:

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)
}

and then I'd just call it like this (for example to spawn after 2 seconds):

///Spawn Monster
RunAfterDelay(2) { 
                [unowned self] in
                self.spawnMonster()
 }

I then do something similar for hiding (after x seconds, I despawn the monster).

So I created a settings icon at the top of the screen, and when you tap it, a giant rectangular window appears to change game settings, but naturally the problem is the monsters still spawn in the background. If I whisk the player away to another screen, I believe i'll lose all my game state and can't come back to it without starting all over (the player might be in the middle of their game).

Is there a way to tell all game timers I've created in the above, i.e.

DispatchQueue.main.asyncAfter(deadline: time, execute: block)

To pause and resume when I say so? I guess it's fine to do it with all timers (if there isn't a way to label and pause certain timers).

Thanks!

解决方案

I have solved this and would like to share my hours worth of research/coding in the conclusion below. To restate the problem more simply, I actually wanted to achieve this (not simply using the SpriteKit scene pause, which is quite easy):

  1. Start one or more timers in Swift
  2. Stops all timers (when the user presses pause)
  3. When the user unpauses, all timers starts again, where they left off

Someone had mentioned to me that because I am using DispatchQueue.main.asyncAfter there is no way to pause/stop in the way I want (you can cancel but I digress). This makes sense, after all i'm doing an asyncAfter. But to actually get a timer going, you need to use NSTimer (now in Swift3 it is called Timer).

After researching, I see this actually not possible to pause/unpause so you "cheat" by creating a new timer (for each one) when you want to restart paused timers. My conclusion to do this is as follows:

  1. When each timer starts, record your delay you need (we access this latter) and also record the time that this timer will "fire". So for example if it starts in 3 seconds, and executes code, then record the time as Date() + 3 seconds. I achieve this using this code:

//Take the delay you need (delay variable) and add this to the current time

let calendar = Calendar.current        
let YOUR_INITIAL_TIME_CAPTURED = calendar.date(byAdding: .nanosecond, value: Int(Int64(delay * Double(NSEC_PER_SEC))), to: Date())!

  1. Now that you've recorded the time your timer will fire, you can wait for the user to press stop. When they do, you will invalidate each timer with .invalidate() and immediately record the stopped time. In fact, at this point, you can also completely calculate the remaining delay needed when the user starts as:

//Calculate the remaining delay when you start your timer back
let elapsedTime = YOUR_INITIAL_TIME_CAPTURED.timeIntervalSince(Date)
let remainingDelay = YOUR_INITIAL_TIMER_DELAY - elapsedTime

  1. When the user taps start, you can start all timers again by simply creating new ones, utilizing the aforementioned remainder (remainingDelay) and viola` you have your new timers.

Now because I had multiple timers, I decided I needed to create a dictionary in my AppDelegate (accessed via a service class) to retain all my active timers. Whenever a timer ended, I would remove it from the dictionary. I ended up making a special class that had properties for the timer, the initial delay, and the time it started. Technically I could've used an array and also put the timer key on that class, but I digress..

I created my own addTimer method that would create a unique key for each timer and then when the timer's code finished, it would self-remove as follows:

  let timerKey = UUID().uuidString

let myTimer: Timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) {
            _ in
               block()
               self.timers.removeValue(forKey: timerKey)
            }

        }

Note: block() is simply calling whatever block you wrap in your timer. For example I did something cool like this:

addTimer(delay: 4, repeating: true)
        { [unowned self] in
            self.spawnMonster()
        }

So addTimer would run the self.spawnMonster code (as block()) and then it would self-remove from the dictionary when done.

I got way more sophisticated later, and did things like keep repeating timers running and not self-removing, but it's just a lot of very specific code for my purposes and probably would consume way too much of this reply :)

Anyway I really hope this helps someone, and would love to answer any questions that anyone has. I spent a lot of time on this!

Thanks!

这篇关于“暂停"斯威夫特游戏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆