Основи Cocos2d-x

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

Створення карт рівнів

Posted at — May 4, 2020

Для більш-менш великих ігрових проектів, таких як стратегії або платформери, вам знадобиться окремий засіб для проектування карт рівнів. Таким засобом є Tiled — універсальний редактор рівнів з відкритим кодом.

Основи

Інтерфейс Tiled в цілому схожий на інші редактори, тому я не буду тут його детально описувати (але, якщо треба, варто глянути сюди). Суть в тому, що ви берете створений художником набір елементів (tileset) і використовуєте його для побудови карти.

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

Робота з Tiled

Є деякі особливості, специфічні для Cocos2dx:

Карта завантажується дуже просто, фактично одним рядком:

const char mapFilename[] = "beach/beach_map.tmx";

TMXTiledMap* mapNode = TMXTiledMap::create(mapFilename);
if (mapNode == nullptr) {
// обробка помилки  
}
addChild(mapNode, ZO_BACKGROUND);

TMXTiledMap::create створює звичайний об'єкт-нащадок Node і далі з ним можна робити все те ж саме, що з іншими, зокрема, використовувати у якості фону.

Додаткова інформація на карті

Скоріш за все, під час створення рівня вам треба буде зберегти на карті якусь додаткову інформацію. Наприклад, вказати місце появи головного героя, а по позначити квадрати, на які герой не може заходити.

Для того, щоб це зробити, використовується такий прийом:

Службовий тайлсет може виглядати так:

Робота з Tiled

Немає значення, що саме зображено на квадратах; в даному випадку це просто цифри і позначки.

До кожного елементу тайлсету треба додати спеціальну властивість — числовий код, за допомогою яких програма відрізнятиме тайли один від одного. На малюнку вище така властивість названа ‘MetaCode’, додається вона після натиснення на синій “+” внизу панелі.

Завантаження службового шару виглядає так:

bool SimpleNoScrollScene::loadMetaInfo(TMXTiledMap* const mapNode) {

  const string metaLayerName = "meta";
  TMXLayer* const metaLayer = mapNode->getLayer(metaLayerName);
  if (metaLayer == nullptr) {
    printf("Failed to find %s layer\n", metaLayerName.c_str());
    return false;
  }

  const Size mapSize = mapNode->getMapSize();
  obstaclesMapWidth = mapSize.width;
  obstaclesMapHeight = mapSize.height;
  obstaclesMap = new bool[obstaclesMapWidth*obstaclesMapHeight];
  memset(obstaclesMap, 0, (obstaclesMapWidth*obstaclesMapHeight));

  for (int tileX = 0; tileX < mapSize.width; tileX++) {
    for (int tileY = 0; tileY < mapSize.height; tileY++) {
      const int tileGid = metaLayer->getTileGIDAt(Vec2(tileX, tileY));
      const Value prop    = mapNode->getPropertiesForGID(tileGid);

      if (prop.isNull()) {
        continue;
      }

      const ValueMap vm   = prop.asValueMap();
      const auto frez = vm.find("MetaCode");

      if (frez == vm.end()) {
        continue;
      }

      const int metaCode = frez->second.asInt();

      switch (metaCode) {
      case MMC_OBSTACLE:
        obstaclesMap[obstaclesMapWidth*(obstaclesMapHeight - tileY - 1) + tileX] = true;
        break;

      case MMC_MAGE_START:
        mageStartX   = tileX;
        mageStartY   = mapSize.height - tileY - 1;
        break;

      case MMC_KNIGHT_START:
        knightStartX   = tileX;
        knightStartY   = mapSize.height - tileY - 1;

        currentKnightX = knightStartX;
        currentKnightY = knightStartY;
        break;

        // Note there is no suitable default action here
        // default:
      }
    }
  }

  metaLayer->setVisible(false);

  return true;
}

Тут ми отримуємо шар методом getLayer і обходимо всі його квадрати. Для кожного квадрату намагаємось отримати значення параметру “MetaCode”, якщо таке є. В залежності від значення, це може бути або стартове місце, або позначка квадрату, на який не можна заходити.

У Tiled квадрат з координатами “0:0” знаходиться у верхньому лівому куті карти, а вісь Y зростає зверху до низу. Це суперечить підходу Cocos2dx, у якому точка “0:0” знаходиться зліва внизу, а Y збільшується вверх. Тому при завантаженні координати Y доводиться перераховувати, використовуючи вираз типу knightStartY = mapSize.height - tileY - 1;.

Таким чином, можна взяти таку карту:

Робота з Tiled

В програмі вона буде виглядати так:

Лицар рухається до берега

Цифри тайлсету позначають стартові позиції мага та лицаря, хрестики — місця, де не можна ходити. Лицар запрограмований так, щоб пересуватись на один квадрат вправо, доки у наступному квадраті не буде позначки-заборони. Повний прилад можна переглянути тут.

Інші редактори

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