ios8 Swift SpriteKit - пауза и возобновление NSTimers в Swift

Я много раз искал в Интернете, но не мог найти ответ на этот вопрос. Я знаю, как приостанавливать и возобновлять NSTimers с помощью функций недействительности - timer.invalidate. и я знаю, как возобновить их. Но у меня есть игра SpriteKit. Когда я приостанавливаю игру, я останавливаю все и таймеры. Я знаю, что могу остановить их, используя .invalidate, но когда я аннулирую их:

Например, скажем, у меня есть 5-секундный таймер, который работает непрерывно и порождает один блок.

Когда таймер достигает секунды 3 цикла и когда я приостанавливаю игру, и аннулирую таймеры. Когда я возобновляю, теперь секунда таймера возвращается к 0, и я должен ждать еще 5 секунд. Я хочу, чтобы он продолжился с того места, где он остановился, 3 , и подождал 2 секунды, пока не появится блок.

            blockGenerator.generationTimer?.invalidate()

            self.isGamePaused = true
            self.addChild(self.pauseText)

            self.runAction(SKAction.runBlock(self.pauseGame))

e`

и когда я возобновляю его:

blockGenerator.generationTimer = ...

Мне нужно подождать еще 5 секунд, я хочу, чтобы таймер продолжался с того места, где он остановился

Если вы можете мне помочь, я ценю это, спасибо.


person Entitize    schedule 11.08.2015    source источник


Ответы (3)


Существует способ приостановить/возобновить Timer экземпляров, потому что с помощью повторяющихся таймеров мы знаем следующие fire date.

Это простой класс SRTimer и протокол SRTimerDelegate


Протокол SRTimerDelegate

protocol SRTimerDelegate : AnyObject {
    func timerWillStart(_ timer : SRTimer)
    func timerDidFire(_ timer : SRTimer)
    func timerDidPause(_ timer : SRTimer)
    func timerWillResume(_ timer : SRTimer)
    func timerDidStop(_ timer : SRTimer)
}

Класс STimer

class SRTimer : NSObject {
    
    var timer : Timer?
    var interval : TimeInterval
    var difference : TimeInterval = 0.0
    var delegate : SRTimerDelegate?
    
    init(interval: TimeInterval, delegate: SRTimerDelegate?)
    {
        self.interval = interval
        self.delegate = delegate
    }

    @objc func start(_ aTimer : Timer?)
    {
        if aTimer != nil { fire(self) }
        if timer == nil {
            delegate?.timerWillStart(self)
            timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(fire), userInfo: nil, repeats: true)
        }
    }

    func pause()
    {
        if timer != nil {
            difference = timer!.fireDate.timeIntervalSince(Date())
            timer!.invalidate()
            timer = nil
            delegate?.timerDidPause(self)
        }
    }

    func resume()
    {
        if timer == nil {
            delegate?.timerWillResume(self)
            if difference == 0.0 {
                start(nil)
            } else {
                Timer.scheduledTimer(timeInterval: difference, target: self, selector: #selector(start), userInfo: nil, repeats: false)
                difference = 0.0
            }
        }
    }

    func stop()
    {
        if timer != nil {
            difference = 0.0
            timer!.invalidate()
            timer = nil
            delegate?.timerDidStop(self)
        }
    }
    
    @objc func fire(_ sender : SRTimer)
    {
        delegate?.timerDidFire(self)
    }

}

Приведите свой класс в соответствие с протоколом SRTimerDelegate и инициализируйте экземпляр SRTimer с помощью

var timer : SRTimer!
timer = SRTimer(interval: 5.0, delegate: self)

Методы

start() вызывает метод делегата timerWillStart и запускает таймер.

pause() сохраняет разницу между текущей датой и датой следующего срабатывания, делает недействительным таймер и вызывает метод делегата timerDidPause.

resume() вызывает метод делегата timerWillResume, создает временный одноразовый таймер с сохраненным временным интервалом difference. Когда этот таймер сработает, основной таймер будет перезапущен.

stop() вызывает метод делегата timerDidStop и делает таймер недействительным.

Когда таймер срабатывает, вызывается метод делегата timerDidFire.

person vadian    schedule 11.08.2015
comment
@vadian, может быть, я этого не вижу, но когда start() вызывается изначально, каково значение interval? ... Я не вижу, чтобы interval инициализировалось каким-либо значением. - person Pangu; 07.03.2016
comment
@Pangu Вы должны инициализировать экземпляр с помощью interval и delegate - person vadian; 07.03.2016
comment
@vadian, прошу прощения за невежество, я все еще немного новичок в использовании NSTimers ... можете ли вы привести быстрый пример? - person Pangu; 07.03.2016
comment
На самом деле пример использования класса Timer находится в ответе над абзацем Methods. Просто объявите свойство Timer и создайте экземпляр. Затем реализуйте методы делегата и вызовите методы экземпляра. - person vadian; 07.03.2016
comment
@Vadian Так, например, я хочу повторно запускать событие, чтобы оно запускалось каждую 1 секунду (интервал = 1). но я вызываю pause, чтобы приостановить событие через 1,5 секунды. Когда я хочу снова возобновить это событие, я вызываю resume. Гарантирует ли это, что событие срабатывает через 0,5 секунды (оставшееся время) и продолжает срабатывать каждую 1 секунду после этого? Или событие сработает через 1 целую секунду? - person Pangu; 07.03.2016
comment
Предполагается, что он срабатывает через 0,5 секунды, а затем каждую секунду. - person vadian; 07.03.2016

Во-первых, позвольте мне сказать следующее: это невозможно сделать только с NSTimer, для этого нет встроенной функции (вы можете построить логику вокруг этого, как предлагает ответ от Vadian). НО.

Почему NSTimer — плохая идея

Давайте остановимся и немного подумаем. Для игровых объектов и точного нереста вы никогда не должны использовать NSTimer в первую очередь. Проблема заключается в реализации NSTimer (цитируя документы):

Из-за различных источников входных данных, которыми управляет типичный цикл выполнения, эффективное разрешение временного интервала для таймера ограничено порядка 50-100 миллисекунд. Если время срабатывания таймера происходит во время длинного вызова или когда цикл выполнения находится в режиме, который не отслеживает таймер, таймер не срабатывает до тех пор, пока цикл выполнения не проверит таймер в следующий раз. Следовательно, фактическое время потенциального срабатывания таймера может быть значительным периодом времени после запланированного времени срабатывания.

Есть и другие проблемы с NSTimer, но это выходит за рамки этого вопроса.

Решение

Что вы можете сделать вместо этого, вы должны слушать изменение времени дельты при каждом вызове обновления.

let delta = currentPreciseTime - previousPreciseTime

Теперь, когда у вас есть эта дельта, вы можете иметь counter : Double, и при каждом обновлении вы увеличиваете счетчик на дельту.

let counter : Double
counter += delta

Теперь, когда ваш «таймер» работает правильно, вы можете проверить с помощью простого условия, прошел ли ваш период времени, или сделать с ним все, что хотите:

let SPAWN_OBJECT_AFTER : Double = 5.0
if counter > SPAWN_OBJECT_AFTER {

    // Do something on fire event
    self.spawn()

    // This line effectively restarts timer
    counter -= SPAWN_OBJECT_AFTER
}

Вы можете легко создать свой собственный, очень простой класс таймера, чтобы сделать это. Также! Таким образом, вы можете контролировать то, что происходит в вашем вызове обновления, которому принадлежит логика обновления. Таймер ломает эту модель, разрешая выполнение метода за его пределами — это может быть задумано, но обычно это не так).

Я создавал игры, работающие каждый день, и я бы сказал, что это наиболее распространенное решение для периодических событий, поскольку оно экономит больше всего ресурсов при правильном использовании. Очевидно, что подходит не для всего, но определенно соответствует вашим потребностям.

Надеюсь, поможет!

person Jiri Trecak    schedule 11.08.2015

Я не верю, что есть способ приостановить/возобновить NSTimer так, как вы говорите. Вы должны использовать timer.invalidate() и timer.fire(). Однако, возможно, вы можете использовать int (который начинается с 5 и уменьшается каждую секунду), чтобы отслеживать, сколько секунд есть у начального таймера, прежде чем снова сработает, и как только время сработает снова, убедитесь, что новое значение int передается для запуска начальный таймер с правильного момента времени.

person TheCodeComposer    schedule 11.08.2015