精灵套件中的计时器

时间:2014-02-15 22:20:58

标签: sprite-kit

我没有使用Sprite Kit的经验。我想知道Sprite Kit中有类似于Cocos2D 调度程序的东西吗?如果不是,应该使用什么 NSTimer 是唯一的选择?我想如果唯一的选择是使用 NSTimer ,我们手动需要处理应用程序在后台的情况。谢谢。

4 个答案:

答案 0 :(得分:21)

要实现类似于cocos调度程序的功能,您可以使用SKAction。

例如,为了实现这样的目标

[self schedule:@selector(fireMethod:) interval:0.5];

使用SKAction你会写这个

SKAction *wait = [SKAction waitForDuration:0.5];
SKAction *performSelector = [SKAction performSelector:@selector(fireMethod:) onTarget:self];
SKAction *sequence = [SKAction sequence:@[performSelector, wait]];
SKAction *repeat   = [SKAction repeatActionForever:sequence];
[self runAction:repeat]; 

这不是最好看的,并且缺乏CCScheduler的一些灵活性,但它会暂停背景,暂停场景/视图等。+就像玩乐高积木一样:)

答案 1 :(得分:2)

我为 Swift 中的Sprite Kit使用了一个简单的调度程序demo

由于应用程序的后台前景周期,NSTimers很难管理,而SKActions可能并不适合这一点(对于一个 - 创建预定事件,因为SKAction是一个痛苦而且在长期内不易阅读运行+它不关心SKScene的暂停状态。)

我采用的方法是推出一个自定义调度程序,它允许您编写如下代码:

安排定期活动

scheduler
  .every(1.0) // every one second
  .perform( self=>GameScene.updateElapsedTimeLabel ) // update the elapsed time label
  .end()

安排特定时间的活动

scheduler
  .at(10.0) // ten seconds after game starts
  .perform( self=>GameScene.createRandomSprite ) // randomly place a sprite on the scene
  .end()

安排活动以供日后使用,并重复5次

scheduler
  .after(10.0) // ten seconds from now
  .perform( self=>GameScene.createRandomSprite ) // randomly place a sprite on the scene
  .repeat(5) // repeat 5 times
  .end()

它是如何运作的?

简而言之,调度程序是一个包含调度程序事件优先级队列的类。调度程序维护一个表示游戏中已用时间的变量。在每一帧更新:

  • 调度程序更新其已用时间参考
  • 如果队列中的任何项目需要运行,则检查优先级队列;如果找到它们,则将它们弹出队列并运行相应的操作。如果这是一个重复发生的事件,它的下一个触发时间将被更新并推回队列
  • 除非您明确停止,否则调度程序将永远运行

由于调度程序通过维护经过时间计数器来工作,因此它使用另一个滚动您自己的Timer组件。 Sprite Kit的update方法中的默认时间值在后台/前景化应用程序时没有用,因此我们还需要推出一个Timer组件 - 这将允许我们计算一个正确的时间步长我们的游戏循环。

我在解释陷阱时花了一些时间,在blog article中进一步找到了你的时间步。

<强>摘要

  • 基于NSTimer / GCD的调度异步方法不符合您游戏的经过时间的概念,并且不能很好地与Sprite Kit集成。它在所有情况下都无法正常工作(基于您的游戏逻辑),并且会导致难以识别的时间错误。

  • Sprite Kit的SKAction非常适合运行预定义的操作,例如在节点上应用转换,因为它内置并尊重场景的暂停状态。但是对于调度块/闭包保持对其执行的控制,这是一个艰难的调用。表达你的意图很困难。当场景状态为SKAction.runBlock

  • 时,paused将暂停您的正在运行的阻止
  • 自己动手/使用图书馆。这种方法可以让您获得最大的控制权,并允许您与场景的暂停状态和游戏的经过时间概念进行整合。这一开始可能看起来令人生畏,但如果您已经有了计算游戏时间步长的机制,那么在此基础上制定调度程序非常简单。我共享的演示项目应提供一些有关如何实现此目的的信息,如果您使用Swift,则可以直接使用这些组件。

答案 2 :(得分:0)

受到各种方法的启发,我为 Swift 3 提供扩展

// © timer
// SCHEDULETIMERWITHINTERVAL maked with SKAction
class func scheduledTimerWith(timeInterval:TimeInterval, selector: Selector,withObject: AnyObject = SKNode(), repeats:Bool)->SKAction {
    // instead of NSTimer use skactions
    // now starting to search the selector: is in node, node parent or node childs?
    let call = SKAction.customAction(withDuration: 0.0) { node, _ in
        if node.responds(to: selector) {
            node.performSelector(onMainThread: selector, with: withObject, waitUntilDone: false)
        } else // check for the direct parent
            if let p = node.parent, p.responds(to: selector) {
                p.performSelector(onMainThread: selector, with: withObject, waitUntilDone: false)
            } else { // check for childs
                let nodes = node.children.filter { $0.responds(to: selector)}
                if nodes.count>0 {
                    let child = nodes[0]
                    child.performSelector(onMainThread: selector, with: withObject, waitUntilDone: false)
                } else {
                    assertionFailure("node parent or childs don't are valid or don't have the selector \(selector)")
                }
            }
    }
    let wait = SKAction.wait(forDuration: timeInterval)
    let seq = SKAction.sequence([wait,call])
    let callSelector = repeats ? SKAction.repeatForever(seq) : seq
    return callSelector
}

<强>用法

let generateIdleTimer = SKAction.scheduleTimerWith(timeInterval:20, selector: #selector(PlayerNode.makeRandomIdle), repeats: true)
self.run(generateIdleTimer,withKey: "generateIdleTimer")

从父母发起的计时器:

if parent = self.parent {
     let dic = ["hello":"timer"]
     let generateIdleTimer = SKAction.scheduleTimerWith(timeInterval:20, selector: #selector(PlayerNode.makeRandomIdle),withObject:dict, repeats: true)
     parent.run(generateIdleTimer,withKey: "generateIdleTimer")
}

为什么我应该使用这种方法?

它只是一种替代方案,但如果您需要调用具有输入属性的方法,它还具有withObject输入参数。

使用此方法,您还可以从节点父节点启动计时器并且它可以正常工作(因为方法搜索到父节点和子节点以查找选择器..),如果要从子节点启动计时器,则同样如此没有这个选择器,所以该方法总是搜索它的父或子(如果你想经常启动removeAllActions而不丢失计时器,这很有用。)

答案 3 :(得分:0)

最简单的方法:

var _: Timer = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(objcFunc), userInfo: nil, repeats: false)