Cocos2d-x basics

Some articles about Cocos2d-x framework

How to remove objects

Posted at — Sep 25, 2020

Since we already know about actions and other basic things, now we can think about correct memory usage.

To illustrate it we’ll create a simple scene with one sprite and one button. After pressing the button the sprite will out of the scene and then it should be removed somehow.

Actually, there will be few variants of object removal, so we’ll make a menu instead of one button:

Simple illustration scene

Our variants are

A class for debugging

In order to ensure that object gets deleted (or not deleted) we’ll create a special subclass of Sprite. It will print debugging messages in constructor and destructor.

Something like this:

class EnhancedSprite : public cocos2d::Sprite {
public:
  static EnhancedSprite* create(const std::string& filename);
protected:
  EnhancedSprite();
  virtual ~EnhancedSprite();

  int objectId;
};

static int objectIdGenerator = 0;

EnhancedSprite::EnhancedSprite() {
  objectId = objectIdGenerator++;
  log("EnchancedSprite constructor here, id %02d", objectId);
}

EnhancedSprite::~EnhancedSprite() {
  log("EnchancedSprite destructor here, id %02d", objectId);
}

EnhancedSprite* EnhancedSprite::create(const std::string& filename) {
  EnhancedSprite *result = new (std::nothrow) EnhancedSprite();
  if (result && result->initWithFile(filename)) {
    result->autorelease();
    return result;
  }
  CC_SAFE_DELETE(result);
  return nullptr;
}

Here each object an objectId field with its own unique value. We can use this value to distinguish one object from another. Everything else remains the same as default Sprite.

Variants

If you do nothing the objects will remain in memory. However, if you’ll try to close the application you’ll see the destructors called in the end. That’s because the sprites are the children of the scene; when the scene finally gets deleted it will delete all the children. So, strictly speaking this is not a memory loss, but still it’s a bad situation and everybody should avoid it.

The best and most simple way to remove the sprite is to add RemoveSelf to the end of sequence like this:

MoveTo* moveAct = MoveTo::create(5, Vec2(800, 120));
Sequence* seq = Sequence::create(moveAct, RemoveSelf::create(), nullptr);

blueShip->runAction(seq);

This code will move the sprite and later you’ll see that destructor was called.

Also you can use a CallFunc and removeChild():

MoveTo* moveAct = MoveTo::create(5, Vec2(800, 120));

Sprite* oldShip = blueShip; //save blueShip pointer because it may get changed by the time
                            // the ship reaches "out of range: destination
CallFunc* finalCf = CallFunc::create([this, oldShip]() {
  log("%s: callback implemented in processMenuRemoveChild: move finished", __func__);
  this->removeChild(oldShip);
});

Sequence* seq = Sequence::create(moveAct, finalCf, nullptr);

blueShip->runAction(seq);

This second approach is a bit complicated but still good.

Keeping and reusing the object

Maybe you want to keep the object and save it into a pool. That’s possible, but requires additional explanations.

Almost all classes in cocos2d-x are subclassed from the Ref class. The framework developers wanted to make something similar to Objective-C memory management. So, each object has following methods:

So, for every retain() there should be one and only one release() or autorelease() call.

The create() functions always call autorelease() for their objects (you can check the implementation in our EnhancedSprite class). That means you can create the object, then do nothing with it and it will be removed automatically.

How does it work? There is a game loop and it does something like this:

So, if the autorelease() (or removeChild()) is called then reference counter will be decreased and the object will be deleted after current iteration of the game loop.