Sprite Kit中的计时器

11

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


根据使用情况,可能可以使用SKAction序列和waitForDuration动作来模拟调度程序。 - Dobroćudni Tapir
@DobroćudniTapir - [self schedule:@selector(fireMethod:) interval:0.5]; - Cocos2D。我想每隔X秒触发一次方法。Cocos2D调度器可以处理应用程序在后台、暂停等情况下的情况。 - User1234
场景每帧都会运行一个更新方法。在Kobold Kit中,您也可以注册节点以接收该事件。未来的版本将实现cocos2d调度选择器/块的方式。 - CodeSmile
4个回答

21

你可以使用SKAction来实现类似cocos scheduler的功能。

例如,要实现类似于此的功能:

[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的灵活性,但是它可以在后台暂停,暂停场景/视图等。此外,就像玩乐高积木一样 :)


2
谢谢,+1 就像玩乐高积木一样。 - User1234
谢谢,这很有帮助,比使用计时器好多了。 - NMunro
你是指在SKScene中执行 self schedule: 吗?在iOS9中执行此操作会出现错误。@DobroćudniTapir - Reza.Ab
你是如何在哪里使用 self schedule: 的?@NMunro 谢谢。 - Reza.Ab

2
我为使用Sprite Kit和Swift的简单调度程序制作了一个演示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()

它是如何工作的?

简而言之,调度器是一个类,它持有调度事件的优先级队列。调度器维护一个变量,表示游戏中经过的时间。在每个帧更新时:

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

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

我在博客文章中详细解释了找到时间步长的陷阱。

摘要

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

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

  • 推出自己的/使用库。此方法为您提供了最多的控制权,并允许您与场景的暂停状态和游戏的经过时间概念集成。起初可能看起来令人生畏,但如果您已经有一种计算游戏时间步长的机制,则在此之上创建调度程序是直接的。如果您使用Swift,则我分享的演示项目应该提供了一些关于如何实现此目标的信息。


0

最简单的方法:

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

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而不失去计时器,则这很有用..)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接