如何在解码后阻止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应该显示与其他动作一致的解码行为是有道理的。 但是,做一个例外是有用的,这样任何已经完成的动作都不会再次运行。 如何在解码后阻止序列重启?

我能想到完成你想要实现的目标的唯一方法是:

  1. 当您启动操作时,将时间存储在变量中。 请记住,您将需要使用更新function中传递的“currentTime”值。
  2. 当您需要编码时,计算从创建操作到编码的时间。

从那里你有两个选择,除了剩余多少时间,当你重新创建动作时,将其用于计算或根据剩余时间创建新动作并对其进行编码。

我不认为SKActions真的打算以这种方式使用,但这可能至少是一种解决方法。 我认为开发人员更常见的是将游戏的“状态”存储为持久性,而不是试图存储实际的精灵和动作。 它与UIKit的东西是一样的。 您不会存储UIViews的持久性,而是会有一些其他对象包含根据用户进度重新创建的信息。 希望其中一些至少有点帮助。 祝你好运。

编辑

为了提供更多关于“在理论上”我将如何做到这一点的信息,你是对的,这是一个麻烦。

  1. 子类SKSpriteNode
  2. 创建一个新方法来对该Sprite运行操作(如 – (void)startAction:withKey:duration :),最终将使用key调用run action。
  3. 当调用startAction时,你将它存储到某种MutableArray中,其中包含一个存储该动作的字典,它的键,持续时间和startTime(默认为0)。 您甚至可能不必实际存储该操作,只需存储密钥,持续时间和开始时间。
  4. 在此SKSpriteNode子类上添加update:方法。 每个更新循环都会调用其更新并检查1是否有任何操作没有开始时间,如果这些操作仍在运行,则检查2。 如果没有开始时间,则将当前时间添加为开始时间,如果没有运行,则将其从arrays中删除。
  5. 当你去编码/保存那个精灵时,你使用该数组中的信息来确定那些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] ]]]; 

无论何时对节点进行编码,方法doXdoYdoZ只会运行一次。

但是,根据动画的不同,等待的持续时间可能看起来很奇怪。 例如,假设在doX之前的1秒延迟期间,在doXdoY运行之后应用程序被保留。 然后,在恢复时,应用程序将不再运行doXdoY ,但它将在运行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]]; } 

使用此实现,如果在doXdoY运行之后保留序列,则在恢复时, doZ之前的延迟将仅为1秒。 当然,它是一整秒(即使它在编码之前已经过了一半),但结果是可以理解的:编码时序列中正在进行的任何操作都会重新启动,但一旦完成,它就完成了。

当然,制作一堆像这样的方法是令人讨厌的。 相反,创建一个序列管理器对象,当触发这样做时,将序列分解为子序列,并以有状态的方式运行它们。