Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronize SKActions on two or more SKSpriteNode's?

I want to move two (or more) SKSpriteNodes in sync. Any difference will show. I tried to trigger the SKAction for each sprite in order and when the last one is finished it triggers a new move. But it turns out that the actions doesn't end in the same order they are started, which causes a slightly time difference that is noticeable.

Is there a way to run parallel SKActions on two or more sprites with a duration so that they end at exactly the same time or at least in the order they are started?

Here is an principle example of what is not working:

- (void)testMethod1{
SKSpriteNode *child_1=[arrayWithSprites objectAtIndex:1];
SKSpriteNode *child_2=[arrayWithSprites objectAtIndex:2];

//This doesn't work.
[child_1 runAction:[SKAction moveToX:20.0 duration:0.5]];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] 
    completion:^{[self testMethod1];}];
//Actions might not be finished in the order they are started.
}

And here is a way I haven't tried yet but wonder if it might solve my problem:

- (void)testMethod2{
SKSpriteNode *child_1=[arrayWithSprites objectAtIndex:1];
SKSpriteNode *child_2=[arrayWithSprites objectAtIndex:2];

//Will this guarantee total syncronisation?
[self runAction:[SKAction group:[NSArray arrayWithObjects:
                                 [SKAction runBlock:^{[child_1 runAction:[SKAction moveToX:20.0 duration:0.5]];}],
                                 [SKAction runBlock:^{[child_2 runAction:[SKAction moveToX:20.0 duration:0.5]];}],
                                 nil]]
      completion:^{[self testMethod2];}];
}

I hope my English and thoughts are understandable.

//Micke....

like image 462
user3032732 Avatar asked Feb 14 '23 05:02

user3032732


2 Answers

Just add a container SKNode, and then they will both move at once:

SKNode *containerNode = [[SKNode alloc] init];    
[containerNode addChild:node1];    
[containerNode addChild:node2]; //add as many as necessary    
[containerNode runAction:someAction]; //declare the action you want them both to perform
like image 89
Tyler Avatar answered Feb 16 '23 19:02

Tyler


The solution by Tyler works perfectly.

But if you can't or don't want to do that, you should know that very likely actions are multithreaded and thus they can finish in any order, but they should still finish in the same frame.

To ensure they both ran to completion before running testMethod, you should perform the code in didEvaluateActions. Then you need to find a way to figure out whether both actions have finished. One way is to use a key.

[child_1 runAction:[SKAction moveToX:20.0 duration:0.5] withKey:@"action1"];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] withKey:@"action2"]; 

Then check if both of these actions have run to completion by checking if they still exist:

-(void) didEvaluateActions
{
    if ([child_1 actionForKey:@"action1"] == nil && 
        [child_2 actionForKey:@"action2"] == nil)
    {
        // both actions have ended, start new ones here ...
    }
}

Alternatively you can use a completion block that both actions run. Increase a NSUInteger counter variable and in the block increase the counter by 1, then test if the counter's value is equal to (or greater) than the number of concurrent actions. If it is, you know that both actions have run to completion:

__block NSUInteger counter = 0;
void (^synchBlock)(void) = ^{
    counter++;
    if (counter == 2)
    {
        [self testMethod1];
    }
};

[child_1 runAction:[SKAction moveToX:20.0 duration:0.5] completion:synchBlock];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] completion:synchBlock]; 
like image 34
LearnCocos2D Avatar answered Feb 16 '23 18:02

LearnCocos2D