1

問題

ノード階層がエンコードされると、アプリケーションの状態の保存や「ゲームの保存」中によく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: トゥイーン

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:。これは、少なくとも 2 つの理由から残念な解決策です。1) エンコード中に特別な処理コードが必要です。2) 視覚的には、ノードはアニメーションを終了する機会を得られません。

  • SKAction不完全な解決策: エンコード中に、関連するノードからコード ブロックを削除し、デコード中にそれらを再作成します。これは自明ではありません。

  • 不完全な解決策:SKAction特に遅延の後では、コード ブロックを使用しないでください。アプリの良好な状態を復元するために、アニメーションの完了に依存しないでください。(将来のイベントをエンコード可能な方法でスケジュールする必要がある場合は、コード ブロックを使用せずに独自のイベント キューを作成してください。) この解決策は不完全です。なぜならrunBlock、 とcustomActionWithDuration:actionBlock:は非常に便利であり、恥ずべきことです (そして、初心者にとっては繰り返し起こる罠です)。 )それらを悪と見なす。

4

1 に答える 1

1

エンコード可能な軽量オブジェクトは、SKAction使用したい (しかしできない) 種類のコード ブロックをモデル化できます。

以下のアイデアのコードはこちらです。

の交換runBlock

最初のエンコード可能な軽量オブジェクトがrunBlock. 1 つまたは 2 つの引数で任意のコールバックを作成できます。

  • 呼び出し元は、軽量オブジェクトをインスタンス化し、そのプロパティ (ターゲット、セレクター、および引数) を設定します。

  • runAction軽量オブジェクトは、標準の no-argument によってアニメーションでトリガーされます[SKAction performSelector:onTarget:]。このトリガー アクションの場合、ターゲットは軽量オブジェクトであり、セレクターは指定された「実行」メソッドです。

  • 軽量オブジェクトは に準拠してい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:

2 番目のエンコード可能な軽量オブジェクトが を置き換えcustomActionWithDuration:actionBlock:ます。ただし、これはそれほど単純ではありません。

  • [SKAction performSelector:onTarget:]繰り返しになりますが、指定されたメソッドを呼び出すno-argument によってトリガーされexecuteます。

  • AcustomActionWithDuration:actionBlock:には期間があります。しかし、トリガーperformSelector:onTarget:はそうではありません。waitForDuration:発信者は、デュレーションに依存する場合、シーケンスにコンパニオン アクションを挿入する必要があります。

  • 軽量オブジェクトは、ターゲット、セレクター、ノード、および期間で初期化されます。

  • トリガーされると、軽量オブジェクトは自身の経過時間を追跡し、ターゲットのセレクターを定期的に呼び出して、ノードと経過時間を渡します。

  • 軽量オブジェクトは に準拠していNSCodingます。デコード時に、すでにトリガーされている場合は、構成された期間の残りの期間、セレクターの呼び出しを再開します。

制限事項

これらの提案されたクラスのバージョンを実装しました。軽い使用を通じて、私はすでに重要な制限を発見しました:実行中のシーケンスでエンコードされたノードはSKAction、デコード時に最初からシーケンスを再開します

于 2016-02-07T03:13:40.027 に答える