How to animate multiple SKSpriteNode together?

2019-07-23 16:32发布

问题:

I am pretty new to SpriteKit. I have a set of nodes that need to move together to a different point for each node and after that animation completed for all of them I would like to do something else.

I was making this with UIView components before. A [UIView animateWithDuration:completion:] block was providing the thing that I needed. But in SpriteKit each node has its own action and animate by itself. I could not find any block animation for sprite nodes. So I cannot control the animation completion.

Hope, I am clear. Thanks in advance.

回答1:

There are a couple of SKAction class methods that may be of interest. The first being runBlock: and the second being group: (this allows actions to be run in parallel).

You can run actions on nodes with a completion handler using runAction: completion:

The following code puts each 'animation action', along with completion block in a 'block action'. The block actions are then put into a 'group action'. Finally the group action is told to run. This causes each block action to start at the same time. The completion handler of each block action calls a method named checkCompletion. This method checks for nodes (with a specific name property value) still animating by calling hasActions on the node. Because the node that called the checkCompletion method will return YES, if only one node returns YES then all animations are done.

The following demonstrates this using two nodes, however it will work for more than two.

// assuming two SKNode subclasses named 'node1' and 'node2' AND 'self' is an SKScene subclass


// assign each animating node the same name (used in checkCompletion method)
node1.name = @"animating";
node2.name = @"animating";
// in this example both nodes will animate to the origin
CGPoint origin = CGPointMake(0.0, 0.0);
// make move actions for nodes
SKAction *shortMove = [SKAction moveTo:origin duration:1.2];
SKAction *longMove = [SKAction moveTo:origin duration:4.8];
// wrap nodes in their own separate block action
SKAction *moveNode1 = [SKAction runBlock:^{
    [node1 runAction:shortMove completion:^{
        NSLog(@"shortMove complete");
        [self checkCompletion];
    }];
}];
SKAction *moveNode2 = [SKAction runBlock:^{
    [node2 runAction:longMove completion:^{
        NSLog(@"longMove complete");
        [self checkCompletion];
    }];
}];
// put block actions in a group action
SKAction *groupAction = [SKAction group:@[moveNode1, moveNode2]];
// execute group action
[self runAction:groupAction];

checkCompletion method -

- (void)checkCompletion {

     __block int currentlyHasActions = 0;
     // check for all actions complete on animating nodes
     [self enumerateChildNodesWithName:@"animating" usingBlock:^(SKNode *node, BOOL *stop){
         if ([node hasActions]) {
             currentlyHasActions++;
             // prevent unnecessary enumeration
             if (currentlyHasActions > 1) { *stop = YES; }
         }
     }];
     if (currentlyHasActions == 1) {
         NSLog(@"Actions Finished!!!");

         // execute completion code here...

     }
}

Unfortunately the following will not work as a block actions duration is instantaneous. Consequently the duration of the group action in this case is also instantaneous. This is the reason for the checkCompletion method.

[self runAction:groupAction completion:^{
     // completion code here... :(
}];