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:
Our variants are
removeChild() is a bit complicated and can be dangerousIn 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.
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.
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:
retain() increases reference counter, “protects” the object from removalrelease() decreases reference counter. The object will be removed if the counter gets 0.autorelease() decreases reference counter, but the counter value will be checked later.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.