Поддержка мультиразрешений в Cocos2d-x

Для поддержки нескольких разрешений экранов мы должны определиться: во-первых с каким соотношением сторон экрана мы будем работать, во-вторых определить разрешение разработки т.н. DesignResolutionSize. В третьих создать наборы ресурсов для всех поддерживаемых разрешений.

Допустим мы определились, что будем иметь дело с соотношением сторон 16:9 (~1.7) Размеры экранов для данного отношения можно посмотреть в таблице:

Ширина Высота Стандарт
640 360
720 405
864 486
960 540 qHD
1024 576
1280 720 HDTV
1366 768 WXGA
1600 900
1920 1080 Full HD
2048 1152
2560 1440 QHD
2880 1620
3200 1800
3840 2160 4K UHDTV
4096 2304
5120 2880 5k UHDTV
7680 4320 8K UHDTV
15360 8640 16K

Если взять любое число из первой колонки таблицы и разделить на соответствующее число из второй колонки, то как раз получаем ~1.7

Естественно, что все разрешения в таблице нам не нужны. Нам нужно выбрать, скажем, 3 разрешения существенно отличающихся по размерам, для того, что бы создать ресурсы изображений с соответствующими размерами под эти разрешения. И при старте игры будем подгружать соответствующий набор ресурсов. Ниже в коде будет показано как.

Начнем с того, что определимся какое минимальное и максимальное разрешение экрана мы будем учитывать. Допустим это будет 1024х576 и 4096х2304 (сразу скажу что это не ограничивает реальное кол-во поддерживаемых экранов), затем ищем значение разрешения посердке - 2048х1152.

Итак мы получили такие цифры: 1024х576, 2048х1152, 4096х2304 создадим три варианта наборов изображений для разрешений: 1024х576, 2048х1152, 4096х2304 и поместим их в директории:

  • Resources/small ( 1024х576 )
  • Resources/medium ( 2048х1152 )
  • Resources/large ( 4096х2304 )

Я специально взял для наглядности разрешения различающиеся в 2 раза!

Теперь выберем из этого набора значения для DesignResolutionSize - разрешения в котором будет вестись разработка игры. Возьмем среднее значение (произвольно). Теперь у нас DesignResolutionSize = 2048х1152 Далее в файле AppDelegate.cpp в методе AppDelegate::applicationDidFinishLaunching - пропишем следующий код:


// рабочее разрешение
static Size designSize = Size(2048, 1152);

static Size smallSize  = Size(1024, 576); 
static Size mediumSize = Size(2048, 1152);
static Size largeSize  = Size(4096, 2304);

// AppDelegate.cpp
bool AppDelegate::applicationDidFinishLaunching()
{
    auto fileUtil   = FileUtils::getInstance();
    auto director   = Director::getInstance();
    auto glview     = director->getOpenGLView();

    if(!glview) {
        #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || \
            (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || \
            (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) 
                glview = GLViewImpl::createWithRect("PROJECT_NAME", 
                    cocos2d::Rect(0, 0, designResolutionSize.width,
                    designResolutionSize.height));
        #else
                glview = GLViewImpl::create("PROJECT_NAME");
        #endif
        director->setOpenGLView(glview);
    }

    director->setDisplayStats(true);
    director->setAnimationInterval(1.0f / 30);

    glview->setDesignResolutionSize(
        designSize.width,
        designSize.height,
        ResolutionPolicy::NO_BORDER
    );

    auto frameSize = glview->getFrameSize();

    // Если размер экрана устройства больше среднего разрешения
    if (frameSize.height > mediumSize.height) {
        // следовательно мы "попали" на наибольшее разрешение (4096х2304):
        fileUtil->addSearchPath("large");
        director->setContentScaleFactor(largeSize.height / designSize.height); // 2304 / 1152
    }
    // Если размер экрана устройства больше наименьшего разрешения
    else if (frameSize.width > smallSize.height) {
        // следовательно мы "попали" на среднее разрешение (2048х1152):
        fileUtil->addSearchPath("medium");
        director->setContentScaleFactor(mediumSize.height / designSize.height); // 1152 / 1152
    }
    // Иначе, что логично, мы "попали" на наименьшее разрешение (1024х576):
    else {
        fileUtil->addSearchPath("small");
        director->setContentScaleFactor(smallSize.height / designSize.height); // 576 / 1152
    }

    register_all_packages();
    fileUtil->addSearchPath("res");

    auto scene = GreetingScene::createScene();
    director->runWithScene(scene);

    return true;
}

С помощью DesignResolutionSize мы получаем коэффициент масштабирования - число, которое необходимо передать в метод Director::setContentScaleFactor, что бы движок под капотом мог корректно масштабировать:

  • значения местоположения объектов на экране;
  • размеров шрифтов;
  • размеров ресурсов изображений.

Конкретный пример:

Вспомним, что у нас DesignResolutionSize = 2048х1152, и допустим, что реальный размер устройства (frameSize) = 2880х1620. Согласно коду выше мы попадаем в первое условие: if (frameSize.height > mediumSize.height) { ... } т.е. 1620 > 1152

Следовательно получаем коеффициент масштабирования (К.М.) следующим образом:

К.М. = largeSize.height / designSize.height = 2304 / 1152 = 2

Теперь для реального разрешения устройства 2880х1620 мы имеем К.М. = 2 Т.е. Движок согласно DesignResolutionSize = 2048х1152 преобразует экранные объекты к разрешению 4096х2304 при этом наши ресурсы как раз будут искаться в папке large т.к.: fileUtil->addSearchPath("large"); В папке large вспомним, у нас как раз ресурсы для разрешения 4096х2304 !

Реальный код

То что показано в примере это упрощенная интерпретация реального кода:

...
static Size designResolutionSize = cocos2d::Size(1024, 576);
static Size smallResolutionSize  = cocos2d::Size(1024, 576);
static Size mediumResolutionSize = cocos2d::Size(2048, 1536);
static Size largeResolutionSize  = cocos2d::Size(4096, 2304);
...
bool AppDelegate::applicationDidFinishLaunching()
{
    auto director = Director::getInstance();
    auto glview   = director->getOpenGLView();
    auto fileUtil = FileUtils::getInstance();

    if(!glview) {
        #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || \
            (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || \
            (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
                glview = GLViewImpl::createWithRect("PROJECT_NAME", 
                    cocos2d::Rect(0, 0, designResolutionSize.width,
                    designResolutionSize.height));
        #else
                glview = GLViewImpl::create("PROJECT_NAME");
        #endif
        director->setOpenGLView(glview);
    }

    director->setDisplayStats(true);
    director->setAnimationInterval(1.0f / 30);

    glview->setDesignResolutionSize(
        designResolutionSize.width,
        designResolutionSize.height,
        ResolutionPolicy::NO_BORDER);

    auto frameSize = glview->getFrameSize();

    if (frameSize.height > mediumResolutionSize.height) {
        fileUtil->addSearchPath("large");
        director->setContentScaleFactor(MIN(
            largeResolutionSize.height/designResolutionSize.height,
            largeResolutionSize.width/designResolutionSize.width));
    }
    else if (frameSize.height > smallResolutionSize.height) {
        fileUtil->addSearchPath("medium");
        director->setContentScaleFactor(MIN(
            mediumResolutionSize.height/designResolutionSize.height,
            mediumResolutionSize.width/designResolutionSize.width));
    }
    else {
        fileUtil->addSearchPath("small");
        director->setContentScaleFactor(MIN(
            smallResolutionSize.height/designResolutionSize.height,
            smallResolutionSize.width/designResolutionSize.width));
    }

    register_all_packages();

    fileUtil->addSearchPath("res");
    auto scene = GreetingScene::createScene();

    director->runWithScene(scene);
    return true;
}