1
votes

My Enemy class has several methods, e.g. walkAnimation and deathAnimation, that return instances of SKAction. Because these SKActions involve coordinating basic actions on many child nodes of the Enemy node, runAction:onChildWithName: seems to be the way to manage this.

However, according to the documentation (and to experimentation), SKAction -runAction:onChildWithName: is instantaneous. That means if such an action is present in a sequence, the next action will fire immediately, instead of waiting for that child action to complete. Similarly, if there is a completion handler, that would be invoked immediately. So effectively, runAction:onChildWithName: is nothing more than shortcut for a runBlock: action, just like removeFromParent is.

As a concrete example of the problem I face, say I have a composite death animation. The sprite backing my Enemy flashes red, and then the Enemy itself is removed from the scene. I can't use colorizeWithColor:colorBlendFactor:duration: on my Enemy directly, since that action only operates on SKSpriteNode instances. I can't use removeFromParent on Enemy's Sprite node, since that wouldn't completely remove this Enemy instance from the scene. So runAction:onChildWithName: is needed.

-(SKAction *)deathAnimation {
    SKAction *flashRed = [SKAction colorizeWithColor:[UIColor redColor] colorBlendFactor:1.0 duration:0.2];
    return [SKAction sequence:@[
                                [SKAction runAction:flashRed onChildWithName:@"Sprite"],
                                [SKAction removeFromParent],
                                ]];
}

When I run this SKAction, my Enemy node immediately disappears without flashing red. The reason is that runAction:onChildWithName: does not have a duration of 0.2. Its duration is instant. Similarly if my SKScene were to use [enemy runAction:[enemy deathAnimation] completion:^{ ... }];, that completion block would be invoked immediately.

I would hate to switch to a lesser design. My Enemy class returns SKAction instances to give the SKScene precise control over the coordination and composition of these actions. An important aspect to this is being notified of the completion of such actions. runAction:onChildWithName: is certainly not up to that standard.

One hacky solution would be to include a [SKAction waitForDuration:0.2] in the sequence between runAction:onChildWithName: and removeFromParent. It's not ideal though. Is there a chance that the waitForDuration and flashRed actions could finish on different iterations of the run loop? That doesn't really matter for this case, but as soon as you involve multiple actions with multiple completion handlers, I worry about the race conditions might cause.

Do I have other options besides waitForDuration:?

1
Wouldn't be better to run flashRed on Sprite node directly? It will have a duration then.Andrey Gordeev

1 Answers

0
votes

You're using the sequence wrong here, it actually gets in the way.

Get the child node named Sprite. Run the flash red action on it with a completion handler, which in turn removes the sprite (or sprite's parent) by using the removeFromParent method rather than the action with the same name.