gpt4 book ai didi

ios - 在应用程序状态保存期间解决 SKAction 代码块编码限制的好方法是什么?

转载 作者:行者123 更新时间:2023-11-29 12:05:19 25 4
gpt4 key购买 nike

问题

当节点层次结构被编码时,就像在应用程序状态保存或“游戏保存”期间常见的那样,运行带有代码块的 SKAction Action 的节点必须被特殊处理,因为代码块不能被编码.

示例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: Run block actions can not be properly encoded, Objective-C blocks do not support NSCoding.

解码后,orc 会淡出并从父对象中移除,但清理方法orcDidFinishDying: 不会被调用。

解决此限制的最佳方法是什么?

示例 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 代码块,尤其是在延迟之后。永远不要依赖动画的完成来恢复良好的应用程序状态。 (如果您需要以可编码的方式安排 future 事件,请构建您自己的事件队列而不是使用代码块。)此解决方案是不完美的,因为 runBlockcustomActionWithDuration:actionBlock:它们实在是太有用了,认为它们是邪恶的将是一种耻辱(对于新手来说也是一个反复出现的陷阱)。

最佳答案

可编码的轻量级对象可以模拟我们想要使用(但不能)的各种SKAction代码块。

以下想法的代码是 here .

替换 runBlock

第一个可编码的轻量级对象取代了 runBlock。它可以使用一个或两个参数进行任意回调。

  • 调用者实例化轻量级对象并设置其属性:目标、选择器和参数。

  • 轻量级对象由标准无参数 [SKAction performSelector:onTarget:]runAction 动画中触发。对于这个触发 Action ,目标是轻量级对象,选择器是指定的“执行”方法。

  • 轻量级对象符合NSCoding

  • 作为奖励,触发 SKAction 保留了对轻量级对象的强引用,因此两者都将与运行操作的节点一起编码。

  • 这个轻量级对象的一个​​版本可以弱保留目标,这可能是好的和/或必要的。

这是一个可能的界面草稿:

@interface HLPerformSelector : NSObject <NSCoding>

- (instancetype)initWithTarget:(id)target selector:(SEL)selector argument:(id)argument;

@property (nonatomic, strong) id target;

@property (nonatomic, assign) SEL selector;

@property (nonatomic, strong) id argument;

- (void)execute;

@end

以及伴随的实现:

@implementation HLPerformSelector

- (instancetype)initWithTarget:(id)target selector:(SEL)selector argument:(id)argument
{
self = [super init];
if (self) {
_target = target;
_selector = selector;
_argument = argument;
}
return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_target = [aDecoder decodeObjectForKey:@"target"];
_selector = NSSelectorFromString([aDecoder decodeObjectForKey:@"selector"]);
_argument = [aDecoder decodeObjectForKey:@"argument"];
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_target forKey:@"target"];
[aCoder encodeObject:NSStringFromSelector(_selector) forKey:@"selector"];
[aCoder encodeObject:_argument forKey:@"argument"];
}

- (void)execute
{
if (!_target) {
return;
}
IMP imp = [_target methodForSelector:_selector];
void (*func)(id, SEL, id) = (void (*)(id, SEL, id))imp;
func(_target, _selector, _argument);
}

@end

以及使用它的例子:

SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
HLPerformSelector *cleanupCaller = [[HLPerformSelector alloc] initWithTarget:self selector:@selector(orcDidFinishDying:) argument:orcNode];
SKAction *cleanupAction = [SKAction performSelector:@selector(execute) onTarget:cleanupCaller];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction, cleanupAction ]]];

替换 customActionWithDuration:actionBlock:

第二个可编码的轻量级对象替换了 customActionWithDuration:actionBlock:。然而,这并不是那么简单。

  • 同样,它由无参数 [SKAction performSelector:onTarget:] 触发,调用指定的 execute 方法。

    <
  • customActionWithDuration:actionBlock: 有持续时间。但是触发 performSelector:onTarget: 不会。调用者必须在她的序列中插入伴随的 waitForDuration: 操作(如果它取决于持续时间)。

  • 轻量级对象使用目标、选择器、节点和持续时间进行初始化。

  • 当它被触发时,轻量级对象会跟踪自己的耗时并定期调用目标上的选择器,将节点和耗时传递给它。

  • 轻量级对象符合NSCoding。在解码时,如果已被触发,它将在其配置的剩余时间内继续调用选择器。

限制

我已经实现了 a version of these proposed classes .通过轻度使用,我已经发现了一个重要的限制:Nodes encoded with a running SKAction sequence restart the sequence from the beginning upon decoding .

关于ios - 在应用程序状态保存期间解决 SKAction 代码块编码限制的好方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35249269/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com