Problem
当节点层次结构被编码时(这在应用程序状态保存或“游戏保存”期间很常见),运行的节点SKAction
带有代码块的动作必须特殊处理,因为代码块不能被编码。
示例1:动画后延迟回调
在这里,一名兽人被杀。它以动画方式淡出,然后将其自身从节点层次结构中删除:
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction ]]];
如果对orc节点进行编码然后解码,动画将正确恢复并按预期完成。
但现在该示例已修改为使用淡入淡出后运行的代码块。也许一旦兽人(最终)死了,代码就会清理一些游戏状态。
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
SKAction *cleanupAction = [SKAction runBlock:^{
[self orcDidFinishDying:orcNode];
}];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction, cleanupAction ]]];
不幸的是,代码块不会编码。在应用程序状态保存(或游戏保存)期间,如果此序列正在运行,则会发出警告:
SKAction:运行块动作无法正确编码,
Objective-C 块不支持 NSCoding。
解码后,兽人会消失并从父级中移除,但清理方法orcDidFinishDying:
不会被调用。
解决此限制的最佳方法是什么?
示例 2:补间
The SKAction
customActionWithDuration:actionBlock:
看起来非常适合补间。我的此类事情的样板代码是这样的:
SKAction *slideInAction = [SKAction customActionWithDuration:2.0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
CGFloat normalTime = (CGFloat)(elapsedTime / 2.0);
CGFloat normalValue = BackStandardEaseInOut(normalTime);
node.position = CGPointMake(node.position.x, slideStartPositionY * (1.0f - normalValue) + slideFinalPositionY * normalValue);
}];
很遗憾,customActionWithDuration:actionBlock:
无法编码。如果在动画播放期间保存游戏,则在游戏加载时将无法正确恢复。
再说一遍,解决此限制的最佳方法是什么?
不完美的解决方案
以下是我考虑过但不喜欢的解决方案。 (也就是说,我很想阅读成功支持其中一项的答案。)
不完美的解决方案:使用performSelector:onTarget:
而不是runBlock:
在动画中。这个解决方案并不完美,因为参数无法传递给调用的选择器;调用的上下文只能由目标和选择器的名称来表达。不是很好。
不完美的解决方案:编码时,去掉SKAction
从任何相关节点进行序列并推进程序状态,就像序列已完成一样。在第一个示例中,这意味着设置节点alpha
立即到0.0
,从父节点中删除 orc 节点,然后调用orcDidFinishDying:
。这是一个不幸的解决方案,至少有两个原因:1)在编码过程中需要特殊的处理代码; 2)从视觉上看,节点将没有机会完成其动画。
不完美的解决方案:编码时,去掉SKAction
来自任何相关节点的代码块,并在解码期间重新创建它们。这一点很重要。
不完美的解决方案:永远不要使用SKAction
代码块,尤其是在延迟之后。切勿依赖动画的完成来恢复良好的应用状态。 (如果您需要以可编码的方式安排未来的事件,请不使用代码块构建您自己的事件队列。)此解决方案并不完美,因为runBlock
and customActionWithDuration:actionBlock:
实在是太有用了,如果认为它们是邪恶的,那将是一种耻辱(对于新手来说也是一个反复出现的陷阱)。