From 1fc242af083ecdac47d09a653f7148bd1015ff08 Mon Sep 17 00:00:00 2001 From: Rio Bah Date: Sun, 3 Sep 2017 22:21:17 +0300 Subject: [PATCH 1/2] Implementing an alternative animation enqueing mechanism allowing multiple animations to be queued and the actully running one not being cancelled with an enqueue. The legacy code is kept as it is to provide backward compability --- SpineImporter/SGG_Spine.h | 7 ++ SpineImporter/SGG_Spine.m | 141 +++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/SpineImporter/SGG_Spine.h b/SpineImporter/SGG_Spine.h index dc32cfa..470f02e 100644 --- a/SpineImporter/SGG_Spine.h +++ b/SpineImporter/SGG_Spine.h @@ -76,5 +76,12 @@ typedef enum { +-(void)enqueueAnimation:(NSString*)animationName; +-(void)enqueueAnimations:(NSArray*)animationNames; +-(void)enqueueIndefiniteAnimation:(NSString*)animationName; +-(void)enqueueAnimation:(NSString*)animationName forNumberOfRuns:(NSInteger)numberOfRuns; +-(NSString *)dequeueNextAnimation; +-(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 From f31daf6061734dd0e3cf25aca1434154c5f2c091 Mon Sep 17 00:00:00 2001 From: Rio Bah Date: Wed, 6 Sep 2017 11:18:55 +0300 Subject: [PATCH 2/2] Adding example to the test SKScene for the new enqueing methods --- SpineImporter/SGG_Spine.h | 4 +- SpineTesting/SGG_MyScene.m | 77 ++++++++++++++++---------------------- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/SpineImporter/SGG_Spine.h b/SpineImporter/SGG_Spine.h index 470f02e..2b12d63 100644 --- a/SpineImporter/SGG_Spine.h +++ b/SpineImporter/SGG_Spine.h @@ -75,12 +75,12 @@ 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; --(NSString *)dequeueNextAnimation; -(void)cancelAllInstancesOfEnqueuedAnimationNamed:(NSString*)animationName; 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