如何在解码后阻止SKAction序列重启?
我的应用程序是一个SpriteKit游戏,具有应用程序状态保存和恢复。 保留应用程序状态时,我当前SKScene
中的大多数节点SKScene
被编码。
当对运行SKAction
的节点进行编码和解码时,操作将从头开始重新启动。 这似乎是标准的SpriteKit
行为。
对我来说,这种行为对SKAction sequence
最为明显。 在解码时,无论有多少组件操作已经完成,序列都会重新启动。 例如,假设运行序列的代码如下所示:
[self runAction:[SKAction sequence:@[ [SKAction fadeOutWithDuration:1.0], [SKAction fadeInWithDuration:1.0], [SKAction waitForDuration:10.0], [SKAction removeFromParent] ]]];
如果在10秒等待期间保留应用程序状态,然后恢复, SKAction
序列将从头开始,第二个可见淡入和淡出。
SKAction sequence
应该显示与其他动作一致的解码行为是有道理的。 但是,做一个例外是有用的,这样任何已经完成的动作都不会再次运行。 如何在解码后阻止序列重启?
我能想到完成你想要实现的目标的唯一方法是:
- 当您启动操作时,将时间存储在变量中。 请记住,您将需要使用更新function中传递的“currentTime”值。
- 当您需要编码时,计算从创建操作到编码的时间。
从那里你有两个选择,除了剩余多少时间,当你重新创建动作时,将其用于计算或根据剩余时间创建新动作并对其进行编码。
我不认为SKActions真的打算以这种方式使用,但这可能至少是一种解决方法。 我认为开发人员更常见的是将游戏的“状态”存储为持久性,而不是试图存储实际的精灵和动作。 它与UIKit的东西是一样的。 您不会存储UIViews的持久性,而是会有一些其他对象包含根据用户进度重新创建的信息。 希望其中一些至少有点帮助。 祝你好运。
编辑
为了提供更多关于“在理论上”我将如何做到这一点的信息,你是对的,这是一个麻烦。
- 子类SKSpriteNode
- 创建一个新方法来对该Sprite运行操作(如 – (void)startAction:withKey:duration :),最终将使用key调用run action。
- 当调用startAction时,你将它存储到某种MutableArray中,其中包含一个存储该动作的字典,它的键,持续时间和startTime(默认为0)。 您甚至可能不必实际存储该操作,只需存储密钥,持续时间和开始时间。
- 在此SKSpriteNode子类上添加update:方法。 每个更新循环都会调用其更新并检查1是否有任何操作没有开始时间,如果这些操作仍在运行,则检查2。 如果没有开始时间,则将当前时间添加为开始时间,如果没有运行,则将其从arrays中删除。
- 当你去编码/保存那个精灵时,你使用该数组中的信息来确定那些SKAction的状态。
最重要的是,每个SKSpriteNode在此示例中保留并跟踪其自己的SKAction。 抱歉,我没有时间在Objective-C中编写代码。 我也没有声称或试图暗示这比你的回答更好或更差,而是解决我如何处理这个问题,如果我决定在你的问题中保存SKActions的状态。 =)
SKAction
序列可以分解为多个子序列,这样一旦特定的子序列完成,它将不再运行,因此在解码时不会重新启动。
代码
制作一个轻量级,可编码的对象,可以管理序列,将其分解为子序列并记住(编码)已经运行的内容。 我在GitHub上的库中编写了一个实现。 这是gist中代码的当前状态 。
这是一个例子(使用与下面相同的顺序):
HLSequence *xyzSequence = [[HLSequence alloc] initWithNode:self actions:@[ [SKAction waitForDuration:10.0], [SKAction performSelector:@selector(doY) onTarget:self], [SKAction waitForDuration:1.0], [SKAction performSelector:@selector(doZ) onTarget:self] ]]; [self runAction:xyzSequence.action];
这个概念
第一个想法:将序列拆分成几个独立的子序列。 每个子序列完成后,它将不再运行,因此如果保留应用程序,则不会对其进行编码。 例如,像这样的原始序列:
[self runAction:[SKAction sequence:@[ [SKAction performSelector:@selector(doX) onTarget:self], [SKAction waitForDuration:10.0], [SKAction performSelector:@selector(doY) onTarget:self], [SKAction waitForDuration:1.0], [SKAction performSelector:@selector(doZ) onTarget:self] ]]];
可以像这样拆分:
[self runAction:[SKAction sequence:@[ [SKAction performSelector:@selector(doX) onTarget:self] ]]]; [self runAction:[SKAction sequence:@[ [SKAction waitForDuration:10.0], [SKAction performSelector:@selector(doY) onTarget:self] ]]]; [self runAction:[SKAction sequence:@[ [SKAction waitForDuration:11.0], [SKAction performSelector:@selector(doZ) onTarget:self] ]]];
无论何时对节点进行编码,方法doX
, doY
和doZ
只会运行一次。
但是,根据动画的不同,等待的持续时间可能看起来很奇怪。 例如,假设在doX
之前的1秒延迟期间,在doX
和doY
运行之后应用程序被保留。 然后,在恢复时,应用程序将不再运行doX
或doY
,但它将在运行doZ
之前等待11秒。
为了避免可能奇怪的延迟,将序列分成一系列从属子序列,每个子序列触发下一个子序列。 对于该示例,拆分可能如下所示:
- (void)doX { // do X... [self runAction:[SKAction sequence:@[ [SKAction waitForDuration:10.0], [SKAction performSelector:@selector(doY) onTarget:self] ]]]; } - (void)doY { // do Y... [self runAction:[SKAction sequence:@[ [SKAction waitForDuration:1.0], [SKAction performSelector:@selector(doZ) onTarget:self] ]]]; } - (void)doZ { // do Z... } - (void)runAnimationSequence { [self runAction:[SKAction performSelector:@selector(doX) onTarget:self]]; }
使用此实现,如果在doX
和doY
运行之后保留序列,则在恢复时, doZ
之前的延迟将仅为1秒。 当然,它是一整秒(即使它在编码之前已经过了一半),但结果是可以理解的:编码时序列中正在进行的任何操作都会重新启动,但一旦完成,它就完成了。
当然,制作一堆像这样的方法是令人讨厌的。 相反,创建一个序列管理器对象,当触发这样做时,将序列分解为子序列,并以有状态的方式运行它们。