Основи Cocos2d-x

Статті про розробку ігор за допомогою Cocos2d-x

Видалення об'єктів

Posted at — Apr 3, 2020

Тепер, після того, як ми ознайомились з Action та іншими базовими термінами, можна розбиратись з проблемою коректного видалення об'єктів, створених Cocos2d.

Для того, щоб це проілюструвати, напишемо просту сцену, у якій буде один спрайт і одна кнопка. Після натиснення на кнопку спрайт буде переміщуватись за межі екрану і там якось видалятиметься.

Насправді, замість кнопки нам доведеться зробити меню з кількома варіантами видалення, і в цілому програма виглядатиме так:

В меню є такі варіанти

Налагоджувальний клас

Для того, щоб краще було прослідкувати за життям об'єкту, будемо використовувати не простий Sprite, а його нащадок з додатковими налагоджувальними повідомленнями у конструкторі та деструкторі.

Реалізуватиметься він ось так:

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;
}

Тут у кожного об'єкту є поле objectId з унікальним значенням, яке дозволить відрізняти об'єкти один від одного. Все інше як у звичайного спрайту.

Варіанти видалення

Якщо нічого не робити, спрайти залишатимуться у пам'яті. Цікаво, що коли ви спробуєте закрити програму, то побачите, як викликаються деструктори. Тобто, формально це не є втратою пам'яті: невидимі об'єкти все ще залишаються на сцені і вона зберігає посилання на них. Але все одно очевидно, дана ситуація є поганою і так робити не можна.

Найбільш зручним і правильним способом видалення є використання в кінці послідовності RemoveSelf — спеціальної акції, призначеної для видалення об'єкту. Працює ось так:

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

blueShip->runAction(seq);

Цей код виведе спрайт за межі екрану і після того буде видно, як виконається деструктор.

Такого ж ефекту можна досягти, якщо використати CallFunc та 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);

Такий спосіб складніший, але він може знадобитись, якщо одночасно зі спрайтом треба видалити якісь об'єкти, що не мають відношення до сцени.

Збереження для повторного використання

Припустимо, що замість видалення ви хочете зберегти об'єкт десь у пулі. Це можливо, але доведеться зробити трохи інакше, ніж в описаних вище методах.

Усі класи cocos2dx є нащадками Ref — це така спроба авторів фреймворку зробити щось схоже на Objective-C. Тому у кожного об'єкту є такі методи:

Таким чином, кожному виклику retain() має відповідати один виклик release() або autorelease().

Для об'єктів, що створені за допомогою статичних методів create(), завжди викликається autorelease() (так само, як у нашому класі EnhancedSprite). Тому, якщо хочеться, можна взагалі нічого зі створеним об'єктом не робити і він видалиться автоматично.

Як це працює? Десь глибоко в основі фреймворку знаходиться ігровий цикл, у ході якого виконуються приблизно такі дії:

Таким чином, якщо десь в коді викликати autorelease() або removeChild(), лічильник посилань на об'єкт зменшиться і він буде видалений по завершенню поточної ітерації ігрового циклу.