在应用程序状态保存期间解决 SKAction 代码块编码限制的好方法是什么? [英] What are good ways to work around the encoding limitation of SKAction code blocks during application state preservation?

查看:11
本文介绍了在应用程序状态保存期间解决 SKAction 代码块编码限制的好方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在对节点层次结构进行编码时,如在应用程序状态保存或游戏保存"期间很常见,必须对运行带有代码块的 SKAction 动作的节点进行特殊处理,因为无法对代码块进行编码.

When the node hierarchy is encoded, as is common during application state preservation or a "game save", nodes running SKAction actions with code blocks must be handled specially, since the code blocks cannot be encoded.

在这里,一个兽人被杀了.它被动画淡出然后从节点层次结构中移除:

Here, an orc has been killed. It is animated to fade out and then remove itself from the node hierarchy:

SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction ]]];

如果对orc节点进行编码再解码,动画会正常恢复并按预期完成.

If the orc node is encoded and then decoded, the animation will restore properly and complete as expected.

但现在该示例被修改为使用在淡入淡出之后运行的代码块.一旦兽人(最终)死亡,代码可能会清理一些游戏状态.

But now the example is modified to use a code block that runs after the fade. Perhaps the code cleans up some game state once the orc is (finally) dead.

SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
SKAction *cleanupAction = [SKAction runBlock:^{
  [self orcDidFinishDying:orcNode];
}];
[orcNode runAction:[SKAction sequence:@[ fadeAction, removeAction, cleanupAction ]]];

很遗憾,代码块不会编码.在应用程序状态保存(或游戏保存)期间,如果此序列正在运行,则会发出警告:

Unfortunately, the code block will not encode. During application state preservation (or game save), if this sequence is running, a warning will be issued:

SKAction:运行块动作无法正确编码,Objective-C 块不支持 NSCoding.

SKAction: Run block actions can not be properly encoded, Objective-C blocks do not support NSCoding.

解码后orc会淡出并从parent中移除,但不会调用清理方法orcDidFinishDying:.

After decoding, the orc will fade and be removed from parent, but the cleanup method orcDidFinishDying: will not be called.

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

What is the best way to work around this limitation?

SKAction customActionWithDuration:actionBlock: 似乎非常适合补间.我对这种事情的样板代码是这样的:

The SKAction customActionWithDuration:actionBlock: seems a beautiful fit for tweening. My boilerplate code for this kind of thing is this:

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: 无法编码.如果在动画期间保存游戏,则在游戏加载时将无法正常恢复.

Unfortunately, customActionWithDuration:actionBlock: cannot be encoded. If the game is saved during the animation, it will not restore properly on game load.

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

Again, what is the best way to work around this limitation?

以下是我考虑过但不喜欢的解决方案.(也就是说,我很想阅读成功支持其中之一的答案.)

Here are solutions I have considered but don’t like. (That said, I’d love to read answers that successfully champion one of these.)

  • 不完善的解决方案:在动画中使用 performSelector:onTarget: 而不是 runBlock:.这个解决方案是不完美的,因为参数不能传递给被调用的选择器;调用的上下文只能由目标和选择器的名称来表示.不太好.

  • Imperfect Solution: Use performSelector:onTarget: rather than runBlock: in the animation. This solution is imperfect because arguments cannot be passed to the invoked selector; context for the call can only be expressed by the target and the name of the selector. Not great.

不完善的解决方案:在编码期间,从任何相关节点中删除 SKAction 序列,并推进程序状态,就好像序列已经完成一样.在第一个示例中,这意味着将节点 alpha 立即设置为 0.0,从父节点中移除 orc 节点,然后调用 orcDidFinishDying:.这是一个不幸的解决方案,至少有两个原因:1)在编码期间需要特殊的处理代码;2) 从视觉上看,节点没有机会完成其动画.

Imperfect Solution: During encoding, remove the SKAction sequence from any relevant nodes and advance the program state as if the sequence had completed. In the first example, that would mean setting the node alpha immediately to 0.0, removing the orc node from parent, and calling orcDidFinishDying:. This is an unfortunate solution for at least two reasons: 1) It requires special handling code during encoding; 2) Visually, the node won’t get a chance to finish its animation.

不完善的解决方案:在编码过程中,将SKAction代码块从任何相关节点中移除,并在解码过程中重新创建它们.这是不平凡的.

Imperfect Solution: During encoding, remove the SKAction code blocks from any relevant nodes, and recreate them during decoding. This is non-trivial.

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

Imperfect Solution: Never use SKAction code blocks, especially after a delay. Never rely on the completion of an animation in order to restore good app state. (If you need to schedule a future event in an encodable way, build your own event queue not using code blocks.) This solution is imperfect because runBlock and customActionWithDuration:actionBlock: are just so damn useful, and it would be a shame (and a recurring trap for newbies) to consider them evil.

推荐答案

可编码的轻量级对象可以对我们想要(但不能)使用的 SKAction 代码块类型进行建模.

Encodable lightweight objects can model the kinds of SKAction code blocks that we want to use (but can’t).

以下想法的代码是这里.

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

The first encodable lightweight object replaces runBlock. It can make an arbitrary callback with one or two arguments.

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

  • The caller instantiates the lightweight object and sets its properties: target, selector, and arguments.

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

The lightweight object is triggered in a runAction animation by the standard no-argument [SKAction performSelector:onTarget:]. For this triggering action, the target is the lightweight object and the selector is a designated "execute" method.

轻量级对象符合NSCoding.

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

As a bonus, the triggering SKAction retains a strong reference to the lightweight object, and so both will be encoded along with the node running the actions.

可以制作这个轻量级对象的一个​​版本,以弱保留目标,这可能是不错的和/或必要的.

A version of this lightweight object could be made that retains the target weakly, which might be nice and/or necessary.

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

Here is a draft of a possible interface:

@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

还有一个使用它的例子:

And an example of using it:

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:.然而,这并不是那么简单.

Replacement for customActionWithDuration:actionBlock:

A second encodable lightweight object replaces customActionWithDuration:actionBlock:. This one is not so simple, however.

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

  • Again, it is triggered by the no-argument [SKAction performSelector:onTarget:], invoking a designated execute method.

customActionWithDuration:actionBlock: 有一个持续时间.但触发 performSelector:onTarget: 没有.如果取决于持续时间,调用者必须将伴随的 waitForDuration: 动作插入到她的序列中.

A customActionWithDuration:actionBlock: has a duration. But the triggering performSelector:onTarget: does not. The caller must insert a companion waitForDuration: action into her sequence if it depends on duration.

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

The lightweight object is initialized with a target, selector, node, and duration.

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

When it is triggered, the lightweight object tracks its own elapsed time and periodically calls the selector on the target, passing it the node and the elapsed time.

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

The lightweight object conforms to NSCoding. On decoding, if already triggered, it resumes calling the selector for the remainder of its configured duration.

我已经实现了这些提议的类的一个版本.通过轻度使用,我已经发现了一个重要的限制:使用正在运行的 SKAction 序列编码的节点从解码开始.

I have implemented a version of these proposed classes. Through light use I've already found an important limitation: Nodes encoded with a running SKAction sequence restart the sequence from the beginning upon decoding.

这篇关于在应用程序状态保存期间解决 SKAction 代码块编码限制的好方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆