问题
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
回答1:
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.
来源:https://stackoverflow.com/questions/22854492/add-skaction-to-sprite-queue-run-one-after-another