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