问题
当节点层次结构被编码时,正如在应用程序状态保存或“游戏保存”期间常见的那样,运行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。
解码后,orc 会淡出并从 parent 中移除,但orcDidFinishDying:
不会调用 cleanup 方法。
解决此限制的最佳方法是什么?
示例 2:补间
这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
陷阱customActionWithDuration:actionBlock:
) 认为他们是邪恶的。