Привет. Мы полностью переписали мобильную версию Хабра. Теперь все работает быстрее и выглядит современнее.

Qt 5.2, от желания до Google Play

из песочницы
xanm 25 апреля 2014 в 17:10 59,3k
Здравствуйте, коллеги.

Случилось так, что мне рассказали о 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
Проголосовать:
+32
Сохранить: