Пользователь
0,0
рейтинг
25 апреля 2014 в 17:10

Разработка → Qt 5.2, от желания до Google Play из песочницы

Здравствуйте, коллеги.

Случилось так, что мне рассказали о Qt5.2 и его новой возможности быстро и легко создавать кроссплатформенные приложения под Android и iOS. С Qt я знаком был уже давно, но в последнее время работа была связана с другими технологиями и я немного запустил его развитие. Узнав это, я отправился на сайт Qt, посмотрел красивое видео, где за 10 минут HelloWorld приложение создается сразу под android и ios. Впечатления были очень положительные.

Было принято решиение заняться мобильной разработкой. Появился план пройти путь от желания сделать приложение до его публикации в Google Play. Но на первом этапе хотелось пройти это с тем что не жалко и в чем можно делать ошибки. И все это на новом Qt5.2.


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

Выбор с++ или javascript


Я конечно люблю c++, но писать на нем такую мелочь я не решился. C++ заставляет разработчика писать вдумчиво и быть очень внимательным чтобы не «отстрелить себе ногу», мне же хотелось сделать проект в стиле XP, быстро и главное чтобы работало. Выбор пал на QML и javascript, заодно возможность разобраться с этими так продвигаемыми Digia технологиями.

Первый набросок


Быстро поставив Qt, почитав документацию на qml и рассмотрев игровую механику Flappy Bird (до этого я о ней не слышал) был сделан первый набросок игры. Были использованы простые NumberAnimation для имитации полета птицы и передвижения столбов. Все работало отлично, но появились вопросы:
  • как сделать проверку столкновений
  • как масштабировать на разные разрешения графику и физику
  • как быть с дизайном
  • что делать со звуками игры
  • как встроить монетизацию


Физика


Единственно правильным решением, для 2-х выше указанных проблем, я посчитал использовать всем известный Box2D. Плагин для qml нашелся быстро — github.com/qml-box2d/qml-box2d. Пара дней экспериментов, чтения документации box2d и все переписано и прекрасно работает. Но проблемы еще ждали впереди.

Звук


От фоновой музыки я отказался так как сам ее не люблю да и не смог бы ни подобрать хороший вариант ни тем более сделать самостоятельно. Так на www.freesound.org были подобраны 3 звука: взмах крыльями, столкновение и новое очко.
Для воспроизведения был использован хороший пример создания Flappy Bird из V-play с AudioManager-ом. Но без напильника не обошлось.
import QtQuick 2.2

Item {
  id : audioManager

  property QtObject effect1: Qt.createQmlObject("import QtMultimedia 5.0; SoundEffect{}", audioManager);
  property QtObject effect2: Qt.createQmlObject("import QtMultimedia 5.0; SoundEffect{}", audioManager);

  property int hit: 22
  property int point: 33
  property int silence: 44
  property int wing: 55

  property bool effectSwitcher: false;

  function play( sound) {

    var effect;

    if( !effectSwitcher){
        effect = effect1;
        effectSwitcher = true;
    }else if( effectSwitcher){
        effect = effect2;
        effectSwitcher = false;
    }

    if(effect == null)
        return;

    switch(sound) {
    case hit:
        effect.source = "audio/sfx_hit.wav"
        break
    case point:
        effect.source = "audio/sfx_point.wav"
        break
    case silence:
        effect.source = "audio/sfx_silence.wav"
        break
    case wing:
        effect.source = "audio/sfx_wing.wav"
        break
    }

    effect.play();
  }
}


воспроизведение:
audioManager.play( audioManager.wing);


Все заработало на desktop машине, на телефоне же приложение падало. Причина оказалась банальна, надо было в .pro файл добавить следующее:
QT += multimedia

Зачем здесь два SoundEffect и для чего sfx_silence станет понятно ниже в описании встретившихся багов.

Масштабирование


Масштабирование было сделано стандартно. За основу было взято разрешение 480x800 (маленькое но наверно самое распространенное на данный момент). Относительно него были жестко заданы размеры птицы, и столбов. Дальше просто было сделано вычисление коэффициента масштабирования для текущего разрешения относительно эталонного, и затем все размеры требующие масштабирования просто на него умножались. Со всем этим сильно помог вот этот пример bitbucket.org/wearyinside/cute-plane, но как обычно множество проблем там решено не было.
    width: Screen.width
    height: Screen.height
    property int defaultWidth: 480
    property int defaultHeight: 800
    property double measure: Math.min(Math.min(width, height) / defaultWidth, Math.max(width, height) / defaultHeight)
    property double textScale: Math.sqrt( measure)

Все физические объекты мастабируются линейно, а вот текст при таком мастабировании на высоких разрешениях разрывал все рамки. Для него пришлось сделать масштабироваие по квадратному корню от основного коэффициента масштабирования.

Дизайн


Так как я программист, дизайн был для меня дремучий лес, но интернет наше все и через день чтения различных статей на эту тему была выбрана векторная графика и редактор Inkscape. Для того чтоб нарисовать мультяшную птицу тоже понадобился всего 1-2 дня. Первоначально был сделан набросок на бумаге и нарисовано несколько возможных вариантов. Затем самый лучший был перенесен в svg. Далее и все остальные изображения были сделаны в векторном формате. Для того чтобы использовать svg фаилы в qml в .pro файл надо добавить следующее:
QT += xml svg
QTPLUGIN += qsvg


Монетизация


Основная часть была написана и встал вопрос монетизации. Данный проект хоть и тестовый, но хотелось в нем уже разобраться и с монетизацией. Выбрана была монетизация рекламой admob. И тут начались первые серьезные проблемы. Оказалось что для qt/qml нет никаких плагинов для встраивания admob. Была найдена устаревшая реализация qadmob и закрытая реализация V-play AdMob plugin. Тучи сгустились и начали появляться мысли оставить Qt до лучших времен. Перерыв весь интернет стало понятно что надо перерыть исходники Qt и разобраться как он сделан под Android. И через 4 дня раскопок, тестовый рекламный баннер отобразился в игре. Вот пример как это делается github.com/AlexMarlo/AdMob-Qt5.2-Example. На отображение баннера в общем ушла неделя.

Баги


Далее стало ясно что все основные части сделаны и надо поправить мелкие, отложенные на потом, баги.

Утечка памяти


Память утекала по 100 MB за минуту игры. После переодического комментирования qml кода и проверки результатов, проблема была найдена. Оказалось что память утекала при таком присвоении:
        linearVelocity.x = 220;
        linearVelocity.y = -420;

поменяв этот вариант на
        var flyImpulseVelosityY = -420 * measure;
        var flyImpulseVelosityX = 220 * measure;
        var impulse;
        impulse = Qt.point( flyImpulseVelosityX, flyImpulseVelosityY);
        applyLinearImpulse( impulse, getWorldCenter());

утечки прекратились. Здесь похоже на проблему с qml-box2d, но копать глубже я не стал.

Пропадание звука


При очень частом тапанье и следовательно очень частом воспроизведении, звук стабильно пропадал до проигрывания другого звукового файла. Тут и появилось два SoundEffect. Причем это проявлялось только на андроидах. Для того чтоб этого пропадания не было SoundEffect-ы проигрываются поочередно. К такому решению пришел просто опытным путем. Судя по всему это какая-то проблема в самом Qt.

Приложение завершалось на assert в Box2D при включенном масштабировании


    width: Screen.width
    height: Screen.height
    property int defaultWidth: 480
    property int defaultHeight: 800
    property double measure: Math.min(Math.min(width, height) / defaultWidth, Math.max(width, height) / defaultHeight)

Проблема кроется в первых 2-х строчках. Как оказалось пока не сконструирован qml элемент в котором вызывается Screen, Screen.width и Screen.height будут равны 0. Получается что коэффициент масштабирования первоначально равен 0 и здесь box2d завершает приложение на assert, так как физические объекты не могут быть нулевого размера.
Это удалось исправить только динамически создавая объекты в тот момент, когда коэффициент масштабирования примет не нулевое значение.

Не работающее управление громкостью


Как оказалось, в текущей версии Qt под андроид, кнопки управления громкостью не работают. Все советы на форумах того же Qt предлогали перехватывать нажатия кнопок в Activity, что и было сделано.
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
        if ( keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
                AudioManager am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
                int index = am.getStreamVolume( AudioManager.STREAM_MUSIC) + 1;
                if( index <= am.getStreamMaxVolume( AudioManager.STREAM_MUSIC))
                        am.setStreamVolume( AudioManager.STREAM_MUSIC, index, 0);
        }
        if( keyCode == KeyEvent.KEYCODE_VOLUME_DOWN){
                AudioManager am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
                int index = am.getStreamVolume( AudioManager.STREAM_MUSIC) - 1;
                if( index >= 0)
                        am.setStreamVolume( AudioManager.STREAM_MUSIC, index, 0);
        }
        return super.onKeyDown(keyCode, event);
    }


Тестирование на разных устройствах и снова баги


Дальше наступил этап тестирования на разных андроидах, вот примерный список устройств:
  • Motorola Droid Razr
  • Nexus One
  • Samsung Galaxy S Duos
  • Nexus S
  • Nexus 7 1st gen
  • Nexus 7 2nd gen
  • Nexus 5
  • Sone Xperia Ray
  • Samsung Galaxy S3
  • Nexus 4
  • Acer Iconia Tab A510
  • Galaxy S Plus
  • Alcatel OneTouch M'POP 5020D
  • Samsung Galaxy Fame GT-S6810
  • ASUS Transformer Pad TF300TG


Samsung


И все проблемы вылезли на самсунгах, причем чем лучше телефон тем сильнее проявлялись баги, а судя по вот этой статистике www.appbrain.com/stats/top-android-phones оставлять баги на самсунгах просто нельзя.

Лаг при проигрывании первого звука


По непонятной причине первое проигрывание звука через SoundEffect подвисало и потом все работало нормально. Особенно сильно это проявлялось на Samsung Galaxy S3, на других самсунгах тоже было, но не так заметно. На устройствах других производителей данной проблемы не было. Тут и появился sfx_silence.wav. Это по сути пустой звукойвой файл который проигрывается при загрузке игры.

Лаг при динамическом создании Box2D объектов


Следующая проблема была по причине того что, Box2D объекты создавались динамически для корректного масштабирования и это создание очень тормозило на самсунгах, особенно на том же Samsung Galaxy S3.
Создание объектов земли:
Nexus One 97 ms
Samsung Galaxy S Duos 986 ms

Разница на порядок, но разбираться в нюансах реализации qml-box2d и самого Box2D я не стал, а просто перенес все создание на момент загрузки игры. Грузится дольше но зато во время игры никаких тормозов.

Выводы:


Qt под Android не смотря на множество непонятных багов и недоделок вполне пригоден для разработки. Особенно если вы уже знакомы с Qt. Но надо быть готовым к тому что вы встретите проблемы на которые еще нет ответов в сети.

Одним из минусов, который так и не удалось решить, стал большой размер приложения:
  • apk ~ 10 MB
  • установленное приложение ~38 MB

Самое печальное, что примерно 70% это библиотеки самого Qt и с этим придется мириться. С другой стороны для современных устройств такой размер уже не так критичен.

PS:
Скриншоты результата:
Скриншот 1

Скриншот 2

Скриншот 3

Скриншот 4
@xanm
карма
20,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (28)

  • 0
    Попробуйте на досуге сделать замер FPS в зависимости от объектов на сцене, это интересно. Рисовать через svg — отличная идея, но сможет ли она тягаться с нативными вызовами к QSG-методам
    P.S. Canvas2D это очевидно (видно на глаз) худшее решение для разработки игр, так что с ним сравнивать смысла нет.
  • +1
    Относительно недавно тоже выпустил свою первую игру на Qt в Google Play (порт с Harmattan/Symbian). Некоторые сложности возникли только со звуком (хотелось сохранить совместимость с Symbian/Harmattan и чтобы был один QML код на все платформы) и с InAppPurchase — пришлось разобраться с JNI.
    • 0
      «Один QML код под все платформы» Зачем ?? Ну а JNI вообще сказка… все прозрачно, а с iOs вообще ничего не надо. QML компоненты пишутся на раз два три.
      qml-box2d зря ругали. Лучше бы уделили время для решения проблем поглубже. Там ничего сложного… Помогли бы сообществу парой мерж реквестов.
      • 0
        В данной игре я тоже писал практически один qml код. Зачем? Да все просто чем больше на нем написано те легче будет перенести на другое платформы. Да можно написать на c++, но это будет дольше.
        qml-box2d не ругал, просто констатировал то что там есть ошибки, а разбираться просто не было времени.
  • +2
    Давно не смотрел на Qt под Андроид. Как там эти пляски с Министро — всё ещё надо ставить, или уже можно всё статически слинковать?
    • 0
      Честно говоря до конца не разобрался с этим, в моем случае все .so-ки лежат в самом apk, за счет чего размер установленного приложения очень велик.
      • 0
        Та ну и чёрт с ним, но установки Ministro — не нужно?
        • +1
          Там сейчас в опциях QtCreator-а можно выбрать требовать установку Ministro или все включать в apk.
          • 0
            Это как раз то, что я хотел узнать, спасибо.
  • +1
    Скриншотов бы.
    • 0
      Скриншоты чего именно нужны?
      • 0
        Результата. Вы же про приложение пишете.
        • 0
          Хорошо вот, просто не стал выкладывать ничего связанного с самой игрой, так как поплатился за это в первой версии статьи)
          • 0
            Спасибо. Но лучше большие картинки прятать под спойлер.
            • 0
              Добавил скриншоты в статью со спойлерами. Спасибо.
  • +1
    Переместите, пожалуйста, статью в хаб «Разработка под Android»
    • 0
      Поправил. Спасибо.
  • +2
    >> Память утекала по 100 MB за минуту игры. После переодического комментирования qml кода и проверки результатов, проблема была найдена.
    Целесообразнее использовать профилировщики
    • 0
      Ожидал такой комментарий. Но кода было мало и все было на qml, разбираться с профилированием которого не стал. Да и на вскидку было всего несколько мест где может быть утечка. И проверив их поочереди все нашлось достаточно быстро.
      • 0
        Хм… А вы знаете что в библиотеках которые вы подключаете в своём проекте могут быть утечки памяти независимо от количества кода в вашем проекте? Конечно вам решать, но помоему более эффективно пользоваться инструментами для разработки чтобы ваши достижения не были случайными, а имели желаемый реультат.
        • 0
          Конечно. Но всему свое место и время. Учитывая масштабы проекта и все обстоятельства написания кода сейчас очевидно, что необходимости в этом не было. В серьезных, больших проектах естественно деббагер, профилировщик и прочие прелести.
  • 0
    Спасибо за статью! Как раз смотрю в сторону Qt. Мда, как то… печально. Баги на ровном месте, такие, что и связываться страшно, да еще и на разных устройствах разные. И 38 Мб приложение, при том, что графика векторная. Мне показалось, вместо такой кроссплатформы выгоднее переписать приложение под каждую
  • 0
    Больше всего разочаровал именно размер apk. Есть задумки на счет следующего мобильного приложения и для него уже смотрю в сторону libGDX.
  • 0
    Подскажите, как с помощью Qt на Андроиде отправлять СМС и мониторить входящие СМС?
    • 0
      JNI и Abdroid SDK, можно еще посмотреть что есть в NDK
      • 0
        А средствами Qt?
        • 0
          Только если в будущих версиях
  • 0
    Написал свою 2d игру в Qt 5.2, писал на C++ и с использованием графических библиотек Qt. Багов со звуком не возникало. В начале немного запоздалая реакция на действия, но через секунд 5-10 уже всё работает как надо.
    Не понравилось, что очень сильно загружается процессор и много кушается памяти. Загружал отдельно каждую картинку, их было около 20-ти, в результате чего кушалось много оперативной памяти. Как только склеял их в один файл, сразу 5MB оперативки сэкономил.
    В общем, основная проблема со скоростью работы графической библиотеки. В Qt 5.4 вроде бы как улучшили графический узел, откомпилирую, тогда посмотрим.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.