“暂停"Swift 中的游戏 [英] "Pausing" the Game in Swift

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

问题描述

我用 Swift 创建了一个游戏,其中涉及怪物的出现.怪物的出现和消失,基于定时器使用如下:

func RunAfterDelay(_ delay: TimeInterval, block: @escaping()->()){让时间 = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC)))/Double(NSEC_PER_SEC)DispatchQueue.main.asyncAfter(deadline: time, execute: block)}

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

////生成怪物运行后延迟(2){[无主的自己]在self.spawnMonster()}

然后我做了一些类似的隐藏(x秒后,我使怪物消失).

所以我在屏幕顶部创建了一个设置图标,当你点击它时,会出现一个巨大的矩形窗口来改变游戏设置,但问题是怪物仍然在后台生成.如果我将玩家带到另一个屏幕,我相信我会失去我所有的游戏状态,并且如果不重新开始就无法回到它(玩家可能正在他们的游戏中).

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

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

在我说的时候暂停和继续?我想对所有计时器都可以这样做(如果没有办法标记和暂停某些计时器).

谢谢!

解决方案

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

  1. 在 Swift 中启动一个或多个计时器
  2. 停止所有计时器(当用户按下暂停键时)
  3. 当用户取消暂停时,所有计时器将重新开始,从他们停止的地方

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

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

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

<块引用>

//取你需要的延时(delay变量),把这个加到当前时间让日历 = Calendar.current让 YOUR_INITIAL_TIME_CAPTURED = calendar.date(byAdding: .nanosecond, value: Int(Int64(delay * Double(NSEC_PER_SEC))), to: Date())!

  1. 既然您已经记录了计时器触发的时间,您就可以等待用户按下停止键.当他们这样做时,您将使用 .invalidate() 使每个计时器无效并立即记录停止时间.其实此时也可以完全算出用户启动时所需的剩余延迟为:

<块引用>

//计算你启动定时器返回时的剩余延迟让 elapsedTime = YOUR_INITIAL_TIME_CAPTURED.timeIntervalSince(Date)让剩余延迟 = YOUR_INITIAL_TIMER_DELAY - elapsedTime

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

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

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

 let timerKey = UUID().uuidString让 myTimer: Timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) {_ 在堵塞()self.timers.removeValue(forKey: timerKey)}}

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

addTimer(delay: 4, repeating: true){ [无主的自己] 在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!

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

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