Ari Seyhun
Ari Seyhun

Reputation: 12531

Pause/Resume Action/Animation on Sprite in Cocos2d

Using Cocos2d-x and C++, I'm trying to play and pause an animation for a sprite.

I'm using version 3.15.1 of Cocos2dx.

I have a class called PlayerSprite which is derrived from the cocos2d::Sprite class. Inside PlayerSprite initialization, I've setup my animation with the following code:

SpriteBatchNode* playerSpriteBatch = SpriteBatchNode::create("player.png");
SpriteFrameCache* spriteFrameCache = SpriteFrameCache::getInstance();
spriteFrameCache->addSpriteFramesWithFile("player.plist");

Vector<SpriteFrame*> animFrames(2);
char str[18] = { 0 };
for (int i = 1; i < 3; i++) {
    sprintf(str, "player_idle_%d.png", i);
    SpriteFrame* frame = spriteFrameCache->getSpriteFrameByName(str);
    animFrames.pushBack(frame);
}

Animation* idleAnim = Animation::createWithSpriteFrames(animFrames, 0.8f);
self->idleAction = self->runAction(RepeatForever::create(Animate::create(idleAnim)));
self->idleAction->setTag(0);

When I run the code, it works fine and the animation loops correctly.

In my void update() method, I am trying to pause/play the action/animation based of weather the player is moving or idle.

I do this with the following code:

const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
    // Player is idle AND animation is not running
    // Run animation
    this->runAction(idleAction);
} else if (isMoving && isIdleActionRunning) {
    // Player is moving but animation is running
    // Pause animation
    this->stopActionByTag(0);
}

When I run this code now, my character falls, and as soon as he hits the gound, I get an error at this->runAction(idleAction); saying:

Access violation reading location 0xDDDDDDE5

I believe this is caused due to this->stopActionByTag(0) deleting the action pointer. I've tried to clone the action to avoid this but have had no success.

Upvotes: 0

Views: 1218

Answers (3)

Default
Default

Reputation: 11

Solution: call retain() to keep your action.

It's a matter of memory management of cocos2d-x.

In create() function of your RepeatForever class (derived from Ref), the reference count is set to 1 and there is a line of code ret->autorelease() before returning the object, which means this object will be released automatically at the end of this frame.

You called runAction() function the same frame you created it, the action is retained by ActionManager, it's reference count set to 2, and then 1 at the end of the frame (autorelease).

After your stopping it, it's released by ActionManager, reference count set to 0 and it's deleted. After this you use your action, it will be an access violation method.

*Edit: don't forget to release the action manually when PlayerSprite is deleted, or it's a leak.

Upvotes: 1

Nauman Minhas
Nauman Minhas

Reputation: 83

I know this is a bit late and you might already have solved this but here goes...

Your problem is that you cannot use one instance of Action (idleAction) multiple times. So, once you have run it and removed it, it is released and cannot be used. So, you have 2 options now,

  • Either create a new idleAction Action every time before running the action.
  • Or, have an idleAction retained and don't run it ever. Instead, create a clone of this idleAction and run a new clone each time. i.e.

    idleAction->retain();
    
    const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
    const bool isMoving = !vel.isZero();
    if (!isMoving && !isIdleActionRunning) {
        // Player is idle AND animation is not running
        // Run animation
        Action idleActionClone = idleAction->clone();
        this->runAction(idleActionClone);
    } else if (isMoving && isIdleActionRunning) {
        // Player is moving but animation is running
        // Pause animation
        this->stopActionByTag(0);
    }
    

Upvotes: 1

Makalele
Makalele

Reputation: 7531

When you stop action it's being recycled from memory. In order to play action once more, you have to recreate it. So you may just make a creator function, which returns your animation. The downside is you're recreating animation each time and it'll also play from the beginning (technically you can rewind it).

But I've developed a simpler technique to pause/resume animations:

Let's say you have an action:

action = MoveBy::create(5.0f, Vec2(50, 100));

Now, you can embed this action into Speed action like this:

action = Speed::create(MoveBy::create(5.0f, Vec2(50, 100)), 1.0f);

1.0f - is speed, so it's normal action rate. Now to pause just call:

action->setSpeed(0.0f);

and to resume:

action->setSpeed(1.0f);

you can also use different speed if you need it for some reason or another ;)

Upvotes: 1

Related Questions