3

我的应用程序是一个具有应用程序状态保存和恢复的 SpriteKit 游戏。当应用程序状态被保留时,我当前的大多数节点SKScene都被编码了。

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

4

2 回答 2

2

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

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

从那里您有两个选择,以节省剩余时间,当您重新创建操作时,将其用于您的计算或根据剩余时间创建新操作并对其进行编码。

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

编辑

要提供有关“理论上”如何解决此问题的更多信息,您是对的,这很麻烦。

  1. 子类 SKSpriteNode
  2. 创建一个新方法以在该 Sprite 上运行操作(如 -(void)startAction:withKey:duration:),最终将使用键调用运行操作。
  3. 当 startAction 被调用时,您将其存储到某种 MutableArray 中,并使用 Dictionary 存储该操作、其键、持续时间和 startTime(默认为 0)。您甚至可能不必实际存储该操作,只需存储键、持续时间和开始时间。
  4. 在这个 SKSpriteNode 子类上添加一个 update: 方法。您调用其更新的每个更新循环并检查 1 是否有任何操作没有开始时间,以及 2 是否这些操作仍在运行。如果没有开始时间,则将当前时间添加为开始时间,如果未运行,则将其从数组中删除。
  5. 当您去编码/保存该精灵时,您使用该数组中的信息来确定这些 SKAction 的状态。

重要的是,在本例中,每个 SKSpriteNode 都保持并跟踪自己的 SKAction。抱歉,我没有时间用 Objective-C 编写代码。我也绝不是声称或试图暗示这比您的答案更好或更差,而是解决如果我决心按照您的问题要求保存 SKActions 的状态,我将如何处理这个问题。=)

于 2016-04-12T01:01:11.340 回答
1

序列可以分解为多个子序列,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都只会运行一次。

但是,根据动画的不同,等待的持续时间可能看起来很奇怪。例如,假设应用程序在 1 秒延迟之前被保留doX并已运行。然后,在恢复时,应用程序将不会运行或再次运行,但会等待 11 秒后再运行。doYdoZdoXdoYdoZ

为了避免可能出现的奇怪延迟,请将序列拆分为一系列相关子序列,每个子序列都会触发下一个子序列。例如,拆分可能如下所示:

- (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 秒。当然,它是一整秒(即使在编码之前已经过去了一半),但结果是相当可以理解的:编码时序列中正在进行的任何操作都将重新启动,但一旦完成,它就完成了。

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

于 2016-04-12T02:46:53.327 回答