I have a problem with an action performed by a sprite. I have one CCSequence in a CCArray and I have an scheduled method (is called every 5 seconds) that make the sprite run the action. The action is performed correctly only the first time (the first 5 seconds), after that, it does not.

Here is the code:

In .h ->

@interface PowerUpLayer : CCLayer {
    PowerUp *powerUp;
    CCArray *trajectories;

@property (nonatomic, retain) CCArray *trajectories;

In .mm ->

@implementation PowerUpLayer

@synthesize trajectories;

    if((self = [super init]))
        [self createTrajectories];
        self.isTouchEnabled = YES;
        [self schedule:@selector(spawn:) interval:5];

    return self;

    self.trajectories = [CCArray arrayWithCapacity:1];

    //Wave trajectory
    ccBezierConfig firstWave, secondWave;

    firstWave.controlPoint_1 = CGPointMake([[CCDirector sharedDirector] winSize].width + 30, [[CCDirector sharedDirector] winSize].height / 2);//powerUp.sprite.position.x, powerUp.sprite.position.y);
    firstWave.controlPoint_2 = CGPointMake([[CCDirector sharedDirector] winSize].width - ([[CCDirector sharedDirector] winSize].width / 4), 0);
    firstWave.endPosition = CGPointMake([[CCDirector sharedDirector] winSize].width / 2, [[CCDirector sharedDirector] winSize].height / 2);

    secondWave.controlPoint_1 = CGPointMake([[CCDirector sharedDirector] winSize].width / 2, [[CCDirector sharedDirector] winSize].height / 2);
    secondWave.controlPoint_2 = CGPointMake([[CCDirector sharedDirector] winSize].width / 4, [[CCDirector sharedDirector] winSize].height);

    secondWave.endPosition = CGPointMake(-30, [[CCDirector sharedDirector] winSize].height / 2);

    id bezierWave1 = [CCBezierTo actionWithDuration:1 bezier:firstWave];
    id bezierWave2 = [CCBezierTo actionWithDuration:1 bezier:secondWave];

    id waveTrajectory = [CCSequence actions:bezierWave1, bezierWave2, [CCCallFuncN actionWithTarget:self selector:@selector(setInvisible:)], nil];

    [self.trajectories addObject:waveTrajectory];
    //[powerUp.sprite runAction:bezierForward];

    //        [CCMoveBy actionWithDuration:3 position:CGPointMake(-[[CCDirector sharedDirector] winSize].width - powerUp.sprite.contentSize.width, 0)]
    //[powerUp.sprite runAction:[CCSequence actions:bezierWave1, bezierWave2, [CCCallFuncN actionWithTarget:self selector:@selector(setInvisible:)], nil]];


    if(powerUp != nil)
        [self removeChild:sender cleanup:YES];
        powerUp = nil;

This is the scheduled method:

    if(powerUp == nil)
        powerUp = [[PowerUp alloc] initWithType:0];

        powerUp.sprite.position = CGPointMake([[CCDirector sharedDirector] winSize].width + powerUp.sprite.contentSize.width, [[CCDirector sharedDirector] winSize].height / 2);

        [self addChild:powerUp.sprite z:-1];

        [powerUp.sprite runAction:((CCSequence *)[self.trajectories objectAtIndex:0])];

I don't know what is happening; I never modify the content of the CCSequence after the first time.


You can safely copy and autorelease the action everytime you execute it, so that it doesn't get consumed.

Yes, a CCAction can't be used twice without duplicating it. Try with:

[powerUp.sprite runAction:((CCSequence *)[[[self.trajectories objectAtIndex:0] copy] autorelease])];
  • \$\begingroup\$ Hi! Thanks for your answer, I've tested it and it works! How safe is this solution? Thanks! \$\endgroup\$ – Axort Apr 9 '12 at 17:32
  • \$\begingroup\$ Absolutely safe, if you check CCAction implementations you see that init is used to duplicate the action. That's why copy will actually (as usually) increase the retain count and that's finally why you need to autorelease it. Even because it will be retained again by adding it as a running action of the CCNode. \$\endgroup\$ – Jack Apr 9 '12 at 17:51

As far as I know CCAction classes are designed to run only once. You cannot re-use them.

The only way I'm aware of is to call the init method (eg. initWithDuration) of the action again, but this is bad practice, because init should only be called once after construction of the object and might lead to memory-leaks or other issues if called multiple times.

In your case I suggest you modify your createTrajectories function to build and return a new CCSequence every time (so simply return waveTrajectory instead of adding it to the array). Then in your spawn function, you could do something like:

[powerUp.sprite runAction:[self createTrajectories]];
  • \$\begingroup\$ Hi! Thanks for your answer. That's indeed a good solution. What do you think about the solution given by Jack? \$\endgroup\$ – Axort Apr 9 '12 at 17:37
  • \$\begingroup\$ @Axort I think they are about equal. Both will allocate a new object. Personally I would rather use my proposal, because I think it is more readable and understandable where stuff gets allocated etc. \$\endgroup\$ – bummzack Apr 9 '12 at 20:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.