我没有使用Sprite Kit 的经验。 我想知道Sprite Kit 中是否有类似于Cocos2D“调度器”(Schedulers)的东西? 如果没有,那么应该使用什么,NSTimer 是唯一的选择吗? 我猜如果唯一的选择是使用NSTimer,我们需要手动处理应用程序在后台时的情况。谢谢。
我没有使用Sprite Kit 的经验。 我想知道Sprite Kit 中是否有类似于Cocos2D“调度器”(Schedulers)的东西? 如果没有,那么应该使用什么,NSTimer 是唯一的选择吗? 我猜如果唯一的选择是使用NSTimer,我们需要手动处理应用程序在后台时的情况。谢谢。
你可以使用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的灵活性,但是它可以在后台暂停,暂停场景/视图等。此外,就像玩乐高积木一样 :)
self schedule:
吗?在iOS9中执行此操作会出现错误。@DobroćudniTapir - Reza.Abself schedule:
的?@NMunro 谢谢。 - Reza.AbSKScene
的暂停状态)。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,则我分享的演示项目应该提供了一些关于如何实现此目标的信息。
最简单的方法:
var _: Timer = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(objcFunc), userInfo: nil, repeats: false)
受到不同方法的启发,我制作了一个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
而不失去计时器,则这很有用..)