Pausing is one of the pain points in cocos2d.
I got around this by adding some new features:
CCActionManager:
-(NSArray*) pauseAllRunningActions
{
NSMutableArray* idsWithActions = [NSMutableArray arrayWithCapacity:50];
for(int i=0; i< targets->size; i++) {
ccHashSetBin *bin;
for(bin = targets->table[i]; bin; ) {
tHashElement *elt = (tHashElement*)bin->elt;
if( !elt->paused )
{
elt->paused = YES;
[idsWithActions addObject:elt->target];
}
bin = bin->next;
}
}
return idsWithActions;
}
CCScheduler:
- (NSArray*) pauseAllScheduledTimersUpTo:(timer_priority_t) priority
{
NSMutableArray* timers = [NSMutableArray arrayWithCapacity:10];
for( CCTimer* timer in methodsToAdd )
{
if(timer.enabled && timer.priority <= priority)
{
timer.enabled = NO;
[timers addObject:timer];
}
}
for( CCTimer* timer in scheduledMethods )
{
if(timer.enabled && timer.priority <= priority)
{
timer.enabled = NO;
[timers addObject:timer];
}
}
return timers;
}
- (void) resumeTimers:(NSArray*) timers
{
for( CCTimer* timer in timers )
{
timer.enabled = YES;
}
}
I added priorities to the scheduler because one of the running timers is for CCActionManager itself, and you don't want to pause THAT timer!
CCscheduler.h:
typedef enum
{
kTimerPriorityLow = 10,
kTimerPriorityMedium = 20,
kTimerPriorityHigh = 30,
} timer_priority_t;
@interface CCTimer : NSObject
{
id target;
SEL selector;
TICK_IMP impMethod;
ccTime interval;
ccTime elapsed;
bool enabled;
timer_priority_t priority;
}
@property(readwrite,assign) bool enabled;
@property(readwrite,assign) timer_priority_t priority;
Parts of CCScheduler.m:
@implementation CCTimer
...
+(id) timerWithTarget:(id) t selector:(SEL)s interval:(ccTime) i priority:(timer_priority_t) p
{
return [[[self alloc] initWithTarget:t selector:s interval:i priority:p] autorelease];
}
-(id) initWithTarget:(id) t selector:(SEL)s
{
return [self initWithTarget:t selector:s interval:0 priority:kTimerPriorityLow];
}
-(id) initWithTarget:(id) t selector:(SEL)s interval:(ccTime) seconds priority:(timer_priority_t) pri
{
if( (self=[super init]) ) {
#if COCOS2D_DEBUG
NSMethodSignature *sig = [t methodSignatureForSelector:s];
NSAssert(sig !=0 , @"Signature not found for selector - does it have the following form? -(void) name: (ccTime) dt");
#endif
// target is being retained. Be careful with ciruclar references
target = [t retain];
selector = s;
impMethod = (TICK_IMP) [t methodForSelector:s];
elapsed = -1;
interval = seconds;
enabled = YES;
priority = pri;
}
return self;
}
...
@implementation CCScheduler
...
- (void) removeAllTimersUpTo:(timer_priority_t) priority fromArray:(NSMutableArray*) array
{
for(int i = 0; i < [array count]; i++)
{
CCTimer* timer = [array objectAtIndex:i];
if(timer.priority <= priority)
{
[array removeObjectAtIndex:i];
i--;
}
}
}
- (void) unscheduleAllTimersUpTo:(timer_priority_t) priority
{
[self removeAllTimersUpTo:priority fromArray:methodsToAdd];
[self removeAllTimersUpTo:priority fromArray:methodsToRemove];
[self removeAllTimersUpTo:priority fromArray:scheduledMethods];
}
- (NSArray*) pauseAllScheduledTimersUpTo:(timer_priority_t) priority
{
NSMutableArray* timers = [NSMutableArray arrayWithCapacity:10];
for( CCTimer* timer in methodsToAdd )
{
if(timer.enabled && timer.priority <= priority)
{
timer.enabled = NO;
[timers addObject:timer];
}
}
for( CCTimer* timer in scheduledMethods )
{
if(timer.enabled && timer.priority <= priority)
{
timer.enabled = NO;
[timers addObject:timer];
}
}
return timers;
}
- (void) resumeTimers:(NSArray*) timers
{
for( CCTimer* timer in timers )
{
timer.enabled = YES;
}
}
Some stuff needs to have different priorities. I made all node level schedules have low priority by default, but ActionManager must not be paused so I set its priority to high. I set CCTransition to have medium priority just to give some flexibility.
CCTransition.m:
-(void) finish
{
/* clean up */
[inScene setVisible:YES];
[inScene setPosition:ccp(0,0)];
[inScene setScale:1.0f];
[inScene setRotation:0.0f];
[inScene.camera restore];
[outScene setVisible:NO];
[outScene setPosition:ccp(0,0)];
[outScene setScale:1.0f];
[outScene setRotation:0.0f];
[outScene.camera restore];
[self schedule:@selector(setNewScene:) interval:0 priority:kTimerPriorityMedium];
}
CCNode.m:
-(void) schedule: (SEL) selector
{
[self schedule:selector interval:0 priority:kTimerPriorityLow];
}
-(void) schedule: (SEL) selector interval:(ccTime)interval priority:(timer_priority_t) priority
{
NSAssert( selector != nil, @"Argument must be non-nil");
NSAssert( interval >=0, @"Arguemnt must be positive");
if( !scheduledSelectors_ )
[self timerAlloc];
NSString *key = NSStringFromSelector(selector);
// already scheduled ?
if( [scheduledSelectors_ objectForKey:key ] ) {
return;
}
CCTimer *timer = [CCTimer timerWithTarget:self selector:selector interval:interval priority:priority];
if( isRunning_ )
[[CCScheduler sharedScheduler] scheduleTimer:timer];
[scheduledSelectors_ setObject:timer forKey:key ];
}
CCActionManager.m:
- (void) scheduleSelf
{
[[CCScheduler sharedScheduler] scheduleTimer: [CCTimer timerWithTarget:self selector:@selector(tick:) interval:0 priority:kTimerPriorityHigh]];
}
To pause everything, you do this:
[idsWithPausedActions release];
idsWithPausedActions = [[[CCActionManager sharedManager] pauseAllRunningActions] retain];
[pausedTimers release];
pausedTimers = [[[CCScheduler sharedScheduler] pauseAllScheduledTimersUpTo:kTimerPriorityLow] retain];
To resume:
for(id object in idsWithPausedActions)
{
[[CCActionManager sharedManager] resumeAllActionsForTarget:object];
}
[idsWithPausedActions release];
idsWithPausedActions = nil;
[[CCScheduler sharedScheduler] resumeTimers:pausedTimers];
[pausedTimers release];
pausedTimers = nil;