diff --git a/SpineImporter/SGG_Spine.h b/SpineImporter/SGG_Spine.h index dc32cfa..2b12d63 100644 --- a/SpineImporter/SGG_Spine.h +++ b/SpineImporter/SGG_Spine.h @@ -75,6 +75,13 @@ typedef enum { -(SGG_SpineBone*)findBoneNamed:(NSString*)boneName; +// these methods enqueue given animation(s) to be run after the current animation (if any) completes its run. any call to enqueue does not stop the existing animation +// if an animation is scheduled to run indefinitely, and another animation is enqueued; the indefinite animation continues to run after the new animation +-(void)enqueueAnimation:(NSString*)animationName; +-(void)enqueueAnimations:(NSArray*)animationNames; +-(void)enqueueIndefiniteAnimation:(NSString*)animationName; +-(void)enqueueAnimation:(NSString*)animationName forNumberOfRuns:(NSInteger)numberOfRuns; +-(void)cancelAllInstancesOfEnqueuedAnimationNamed:(NSString*)animationName; @end diff --git a/SpineImporter/SGG_Spine.m b/SpineImporter/SGG_Spine.m index a7326c9..473403f 100644 --- a/SpineImporter/SGG_Spine.m +++ b/SpineImporter/SGG_Spine.m @@ -20,13 +20,23 @@ @interface SGG_Spine () { NSInteger repeatAnimationCount; NSString* previousAnimation; - } +@property (strong, nonatomic) NSMutableArray *animationQueue; + @end @implementation SGG_Spine +-(NSArray *)animationQueue +{ + if (!_animationQueue) { + _animationQueue = [NSMutableArray array]; + } + + return _animationQueue; +} + -(id)init { if (self = [super init]) { @@ -72,6 +82,10 @@ -(void)skeletonFromFileNamed:(NSString*)name andAtlasNamed:(NSString*)atlasName #pragma mark PLAYBACK CONTROLS +-(void)runAnimation:(NSString*)animationName { + [self runAnimation:animationName andCount:1]; +} + -(void)runAnimation:(NSString*)animationName andCount:(NSInteger)count { @@ -334,6 +348,14 @@ -(void)jumpToPreviousFrame { } -(void)endOfAnimation { + NSString *topOfAnimationQueue = [self dequeueNextAnimation]; + if (topOfAnimationQueue) { + // If we have ant animation enqueued, run them and return without running legacy code + [self runAnimation:topOfAnimationQueue]; + return; + } + + if ([_currentAnimation isEqualToString:@"INTRO_ANIMATION"]) { //clear out intro animation after it's been used if (self.debugMode) { NSLog(@"finished intro"); @@ -1211,6 +1233,123 @@ -(void)setPlaybackSpeed:(CGFloat)playbackSpeed { } +#pragma mark - Animation enqueing +-(void)pushAnimationDictionaryToQueue:(NSDictionary*)dict +{ + @synchronized (self) { + NSString *name = dict[@"name"]; + NSInteger indefiniteAnimation = [dict[@"indefinitely"] boolValue]; + + // if the one and only waiting animation is indefinite animation, it can be there because of its rescheduling. + // lets not wait another turn of its + for (int i=0; i*)animationNames +{ + for (NSString *animationName in animationNames) + [self enqueueAnimation:animationName]; +} + +-(void)enqueueIndefiniteAnimation:(NSString*)animationName +{ + [self enqueueAnimation:animationName forNumberOfRuns:-1]; +} + + +-(void)enqueueAnimation:(NSString*)animationName forNumberOfRuns:(NSInteger)numberOfRuns +{ + BOOL indefiniteAnimation = (numberOfRuns < 0); + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:animationName, @"name", (indefiniteAnimation ? @YES : @NO), @"indefinitely", nil]; + + for (int i=0; i<(indefiniteAnimation ? 1 : numberOfRuns); i++) + [self pushAnimationDictionaryToQueue:dict]; + + // TODO: there is a risk of race case here, as the check for runningAnimation & starting one is not synchronized for parallel threads + if (![self isRunningAnimation]) + [self runAnimation:[self dequeueNextAnimation]]; +} + +-(NSString *)dequeueNextAnimation +{ + return [self popAnimationDictionaryFromQueue][@"name"]; +} + +-(void)cancelAllInstancesOfEnqueuedAnimationNamed:(NSString*)animationName +{ + @synchronized (self) { + NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet]; + NSUInteger index = 0; + + for (NSDictionary *dict in self.animationQueue) { + if ([dict[@"name"] isEqualToString:animationName]) + [discardedItems addIndex:index]; + index++; + } + + [self.animationQueue removeObjectsAtIndexes:discardedItems]; + } +} + + @end diff --git a/SpineTesting/SGG_MyScene.m b/SpineTesting/SGG_MyScene.m index ff165a4..5d1f4c5 100644 --- a/SpineTesting/SGG_MyScene.m +++ b/SpineTesting/SGG_MyScene.m @@ -29,21 +29,34 @@ @implementation SGG_MyScene { } --(id)initWithSize:(CGSize)size { +static const BOOL tryAlternativeQueuingMethods = YES; + +-(id)initWithSize:(CGSize)size { if (self = [super initWithSize:size]) { /* Setup your scene here */ - - - boy = [SGG_Spine node]; -// boy.debugMode = YES; -// boy.timeResolution = 1.0 / 1200.0; // this is typically overkill, 1/120 will normally be MORE than enough, but this demo can go to some VERY slow motion. 1/120 is also the default. - [boy skeletonFromFileNamed:@"spineboy" andAtlasNamed:@"spineboy" andUseSkinNamed:Nil]; - boy.position = CGPointMake(self.size.width/4, self.size.height/4); -// [boy runAnimationSequence:@[@"walk", @"jump", @"walk", @"walk", @"jump"] andUseQueue:NO]; //uncomment to see how a sequence works (commment the other animation calls) - boy.queuedAnimation = @"walk"; - boy.name = @"boy"; - boy.queueIntro = 0.1; - [boy runAnimation:@"walk" andCount:0 withIntroPeriodOf:0.1 andUseQueue:YES]; + + boy = [SGG_Spine node]; + [boy skeletonFromFileNamed:@"spineboy" andAtlasNamed:@"spineboy" andUseSkinNamed:Nil]; + boy.position = CGPointMake(self.size.width/4, self.size.height/4); + if (tryAlternativeQueuingMethods) { + [boy enqueueIndefiniteAnimation:@"walk"]; // walk indefinitely + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [boy enqueueAnimation:@"jump"]; // jump once, and then continue walking + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [boy enqueueAnimations:@[@"jump", @"walk", @"jump", @"jump"]]; // try a sequence of animations, and then continue walking + }); + } else { +// boy.debugMode = YES; +// boy.timeResolution = 1.0 / 1200.0; // this is typically overkill, 1/120 will normally be MORE than enough, but this demo can go to some VERY slow motion. 1/120 is also the default. +// [boy runAnimationSequence:@[@"walk", @"jump", @"walk", @"walk", @"jump"] andUseQueue:NO]; //uncomment to see how a sequence works (commment the other animation calls) + boy.queuedAnimation = @"walk"; + boy.name = @"boy"; + boy.queueIntro = 0.1; + [boy runAnimation:@"walk" andCount:0 withIntroPeriodOf:0.1 andUseQueue:YES]; + } boy.zPosition = 0; [self addChild:boy]; @@ -151,9 +164,13 @@ -(void)keyDown:(NSEvent *)theEvent { unichar character = [characters characterAtIndex:s]; switch (character) { case ' ':{ - if (![boy.currentAnimation isEqualToString:@"jump"]) { - [boy runAnimation:@"jump" andCount:0 withIntroPeriodOf:0.1 andUseQueue:YES]; - } + if (tryAlternativeQueuingMethods) { + [boy enqueueAnimation:@"jump"]; // jump once, and then continue walking + } else { + if (![boy.currentAnimation isEqualToString:@"jump"]) { + [boy runAnimation:@"jump" andCount:0 withIntroPeriodOf:0.1 andUseQueue:YES]; + } + } } break; @@ -263,32 +280,4 @@ -(void)update:(CFTimeInterval)currentTime { } - -//NSValue testing -/* - CGFloat xa = arc4random() % 100; - CGFloat xb = arc4random() % 100; - CGFloat ya = arc4random() % 100; - CGFloat yb = arc4random() % 100; - - xb = xb / 100; - yb = yb / 100; - - CGFloat x = xa + xb; - CGFloat y = ya + yb; - - bool xc = arc4random() % 2; - bool yc = arc4random() % 2; - - if (xc) { - x *= -1; - } - if (yc) { - y *= -1; - } - - [[[SGG_SpineBoneAction alloc] init] addTranslationAtTime:0 withPoint:CGPointMake(x, y) andCurveInfo:@[@"1.2, 2.1"]];*/ - - - @end