I listen to touch and add SKAction to a sprite. If existing actions are not complete yet, I want the action to be added to a queue so it will execute one after another. Any experienced similar design?
I did using Array and Block. If there is any easier approach?
@interface GAPMyScene()
@property(strong,nonatomic)SKSpriteNode*ufo;
@property(strong,nonatomic)NSMutableArray*animationQueue;
@property(copy,nonatomic)void(^completeMove)();
@end
@implementation GAPMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.ufo = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
self.animationQueue = [[NSMutableArray alloc] init];
__unsafe_unretained typeof(self) weakSelf = self;
self.completeMove = ^(void){
[weakSelf.ufo runAction:[SKAction sequence:[weakSelf.animationQueue copy]] completion:weakSelf.completeMove];
NSLog(@"removeing %@", weakSelf.animationQueue);
[weakSelf.animationQueue removeAllObjects];
};
[self addChild:self.ufo];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKAction*moveAnimation = [SKAction moveTo:location duration:2];
if (![self.ufo hasActions]) {
[self.ufo runAction:moveAnimation completion:self.completeMove];
} else {
[self.animationQueue addObject:moveAnimation];
NSLog(@"in queue %@", self.animationQueue);
}
}
}
@end
Generally, you can make SKActions
run concurrently using the group
method, and have them run sequentially using the sequence
method.
But if you need a queuing system, rather than building your own, use the native operation queue to do this for you. So you can create a serial operation queue and add operations to it. The issue is that you don't want an operation to complete until the SKAction
does.
So, you can wrap your SKAction
in a concurrent NSOperation
subclass that only completes when the SKAction
does. Then you can add your operations to a serial NSOperationQueue
, and then it will won't start the next one until it finishes the prior one.
So, first, create an ActionOperation
(subclassed from NSOperation
) that looks like:
// ActionOperation.h
#import <Foundation/Foundation.h>
@class SKNode;
@class SKAction;
@interface ActionOperation : NSOperation
- (instancetype)initWithNode:(SKNode *)node action:(SKAction *)action;
@end
and
// ActionOperation.m
#import "ActionOperation.h"
@import SpriteKit;
@interface ActionOperation ()
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, strong) SKNode *node;
@property (nonatomic, strong) SKAction *action;
@end
@implementation ActionOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (instancetype)initWithNode:(SKNode *)node action:(SKAction *)action
{
self = [super init];
if (self) {
_node = node;
_action = action;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.node runAction:self.action completion:^{
self.executing = NO;
self.finished = YES;
}];
}];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
@end
You could then, for example, create a serial queue during the initialization process:
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
You can then add the operations to it:
SKAction *move1 = [SKAction moveTo:point1 duration:2.0];
[self.queue addOperation:[[ActionOperation alloc] initWithNode:nodeToMove action:move1]];
and you can later add more actions:
SKAction *move2 = [SKAction moveTo:point2 duration:2.0];
[self.queue addOperation:[[ActionOperation alloc] initWithNode:nodeToMove action:move2]];
And because the queue is serial, you know that move2
will not be started until move1
is done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With