2D-платформер от Qt 5.2 до Qt 5.4

    image Привет, Хабр. Эта статья должна была быть написана еще зимой, но по объективным причинам пишу ее сейчас.

    Решил я как-то год назад заняться ГеймДевом. Поздновато, конечно, но история не об этом. Сразу решил, что надо писать для мобильных устройств. Направление перспективное и для меня оказалось очень интересным.

    Поскольку я программлю на С++, значит, надо подобрать кроссплатформенную среду разработки. Выбор пал на Qt. Сейчас точно не скажу, почему именно она, но выбор был сделан, да и история не об этом.

    Достал книгу на русском, Внимательно прочитал половину, сделал детскую игру «Быки и Коровы». Здесь не о чем говорить, это был тестовый проект.

    Почитал еще, обнаружил, что у Qt очень хорошая документация на родном сайте и в ней легко найти описание необходимых объектов. И это не может радовать.

    Теперь стал вопрос о том, чтобы сделать проект немного посерьёзней. Везде советуют начинающим браться сначала за что-то простое, так сказать попробовать, научиться на своих ошибках и т.д. Я уже начал подготавливаться, чтобы написать старую и всем знакомую Lines, но вот тут стала необходимость для одного проекта написать что-нибудь на Android. И так родилась идея написать платформер. И вот, собственно говоря, история как раз об этом.

    Решено было писать на С++, хотелось посмотреть, насколько адекватно оно будет потом работать на Android.
    И сразу определился, что писать буду с использованием графической библиотеки Qt, потому что по ней достаточно много хорошего мануала, как говорилось раньше. Я не собираюсь описывать процесс создания платформера, этого добра в мировой сети достаточно на разных языках.

    Начал писать это чудо в конце сентября на Qt 5.2, знаю, что уже была 5.3, но лень было заниматься обновлением. В начале ноября всё уже было готово, но по определённым причинам, мероприятие, для которого игра писалась, не состоялось и было перенесено на весну. Выкладывать для мира проект раньше времени нельзя. Было достаточно времени поработать над мелкими доработками и перенести её в Qt 5.4.

    Кратко процесс реализации описал, теперь, собственно говоря, с какими проблемами я столкнулся во время разработки и переходом от одной версии к другой.

    Графическая библиотека:

    Графическая библиотека
    Начнём с косяков.
    Нативная библиотека Qt кушает очень много памяти. Пользовался компонентом QImage. При загрузке в память картинки размером 780 байт объём используемой памяти увеличивался не на пару килобайт, а на пару десятков, а иногда и сотен килобайт. Данный факт меня не очень радовал, а иногда раздражал.

    Чтобы как-то уменьшить объемы используемой памяти, все графические элементы поместил в один файл. Но это было связанно еще и с удобством использования. Объем используемой памяти сразу уменьшился на 10М.

    Второе, что бросилось в глаза, Это насколько сильно грузится процессор. Не 100%, но более 50%. Может, для игр это и нормально, но дальше было интересней.

    При выходе Qt 5.4 компания-разработчик утверждала, что они улучшили графическую библиотеку.
    Я подтверждаю, что-то они улучшили :). При переносе на 5.4 стало еще меньше использоваться оперативной памяти и процессор уже загружен был менее, чем на 50%. Эти все параметры смотрел в Windows, как дела обстоят на андроиде — не в курсе.

    В процессе обнаружился еще один интересный факт с Зумом.
    Стандартный размер экрана для моей игры — 540х960. Он единственный. При уменьшении картинки проблем никаких не возникало, даже начинало работать чуть пошустрее. А вот при увеличении были выявлены некоторые проблемы.

    Во-первых — гладкого масштабирования не получилось, процесс отрисовки сразу увеличивался в 20-30 раз. Но от этого можно и отказаться, не такой уж это серьёзный проект, где надо много внимания уделять графике. Но для себя отметил, что надо готовить картинки для разных разрешений экрана.

    Вторая проблема заключалась в следующем. Как ранее говорилось, все графические элементы были помещены в один файл. Надо было просто указать область, которую перерисовать на игровой экран. При увеличении данной области почему-то захватывался на 1 пиксель больше указанного.

    Решением проблемы является то, что между элементами надо оставлять немного свободного места в 1-2 пикселя. Или сами элементы делать чуть меньшего размера, чем сама область. У меня получился 2-й вариант.

    И последнее, что мне не понравилось. Это отсутствия возможности работать с картинками с маленькой цветовой гаммой (16 и 256). В QIMage реализовано якобы 256 бит, но каждый бит надо прописать заранее.

    В остальном библиотека меня порадовала. Удобно с ней работать, поддерживает разные форматы картинок, можно сохранить полученное изображение на диск. Разнообразный размер инструментов


    Работа со звуком

    Работа со звуком
    В книге описана работа с мультимедиа для версии 4.8. Но с тех пор всё поменялось. И компоненты, и работа с ними. Немного гугления и чтения документации привели к успеху. В итоге оказалось всё не так сложно. Описывать подробно не стану, на сегодня этой инфы достаточно на форумах и документация есть. Но если кому лень разбираться, пишите, отвечу.

    Собственно говоря, с какими проблемами столкнулся. Файлы WAV больших размеров не терпят. Больших — имеется в виду дольше 3-х сек. MP3-файлы тоже не особо жалует. Более 5MB не получилось подключить. И не хотело собираться, когда итоговый размер медиа был более 10MB. Может, это ограничение как-то и регулируется, но я не нашел, как. Я уменьшил качество аудио MP3 файлов и всё пошло.

    В остальном компоненты по управлению звуками работали достойно. Можно управлять уровнем громкости, MUTE'ировать звук без остановки аудио. Пускать музыку/звуки на фоне, не останавливая основной поток воспроизведения. Только если вы запустили в отдельном потоке звук, то он будет звучать, пока не закончится, и остановить принудительно его нельзя.

    Еще столкнулся с проблемой в 5.2 — не работали внешние кнопки регуляции уровня звука. Пришлось допиливать Activity. О том, как я это делал, на Хабре статью написал. В версии 5.4 уже всё работает, ничего допиливать не надо.


    Тачскрин

    Тачскрин
    Вот тут у меня самое большое недоразумение случилось. А суть заключается в следующем.

    В версии 5.2 я изучил подробно работу с событиями тачскрина. Есть контейнер, который хранит координаты и состояние всех «пальцев» на экране. Есть события, которые реагируют на первое прикосновение (первый «палец»), событие на изменение (передвижение «пальцев» или изменение их количества на экране), и событие, которое сообщает о завершении тача (когда все «пальцы» убираются с экрана).

    По сути, если у Вас в приложении будет использоваться мультитач, то события мышки можно даже и не обрабатывать. Всё прекрасно работает на событиях Тачскрина. Но по определённым надобностям мне пришлось обрабатывать и события мышки. Впоследствии это мне и помогло разобраться в проблемах.

    Всё изменилось с версией 5.4. Когда я собрал на новом Qt проект и сел протестировать новый функционал, каково было моё удивление, когда обнаружил, что управление полетело. А произошло следующее.

    Зачем-то разработчики Q, поменяли логику работы событий тачскрина и мышки! Теперь это работает как-то по-чудному. При первом касании «пальца», генерируются события MouseButtonPress и TouchBegin. Но потом, когда генерируется событие MouseMove а TouchUpdate не генерируется. Но в контейнере тача точка остаётся, но координата и состояние ее не меняется. И когда генерируется событие MouseButtonRelease, событие TouchEnd не генерируется и получается, что виртуально у вас всегда на экране «висит» один «палец».
    Эта проблема решается следующим образом: при событии TouchBegin копируете контейнер состояния в свой, а при MouseButtonRelease очищаете его до следующего TouchBegin.

    После того как на экране появляется второй «палец», генерация событий мышки отключается. И после этого, сколько бы вы пальцев ни ставили на экран или сколько бы ни оставляли, всегда генерируются события TouchUpdate. Когда все «пальцы» убираются с экрана, генерируется событие TouchEnd, а MouseButtonRelease нет. Проблем можно избежать, отреагировав на событие TouchEnd так же, как и на MouseButtonRelease. Но дело в том, что при следующей генерации TouchBegin события, MouseButtonPress не генерируется и вы будете дальше работать с событиями тачскрина. Вот тут-то и затаилась самая большая проблема.

    MouseButtonPress не будет генерироваться до тех пор, пока не будет события MouseButtonRelease. Если MouseButtonRelease вызывать искусственно, то всё равно ничего не получится. Нужно поставить на экран и убрать только один «палец», после этого системно будет генерироваться MouseButtonRelease.
    Эту проблему решал следующим образом. Вместе с TouchBegin искусственно вызывал событие MouseButtonPress. В функции обработке этого события вначале проверял состояние глобальной переменной, если она false, тогда ей присваивалось true и выполнялся код функции, если же она true значит, мы только что обрабатывали это событие, и просто выходим из функции. При TouchEnd или MouseButtonRelease этой переменной присваивался false.

    Если бы в 5.4 ничего не поменялось в работе тачскрина, то этой части текста здесь могло бы и не быть. А так я очень остался недоволен. Раз в «пятилетку» баг выскакивает в работе тачскрина, но не могу его воспроизвести и как следствие — понять, почему он возникает. Как только это случится, то обязательно здесь отпишусь.


    Что еще

    Еще добавил перевод на английский, но там проблем не возникло, и инфы по интернационализации приложений в Qt достаточно.

    Итоги

    Игра была написана и успешно запущенна в проекте. Свои первоначальные предназначения она выполнила — мне набраться опыта в геймдеве и стать одним из заданий в городском квесте. Теперь она будет выполнять своё второстепенное предназначение — быть частью моего портфолио.

    Спасибо Вам, за то, что Вы это читали.
    Метки:
    Поделиться публикацией
    Комментарии 14
    • +2
      QImage — для ввода\вывода, обработки, рез рисования.
      QPixmap — для отрисовки на экран, работает гораздо шустрее, по памяти не в курсе.
      • –2
        Сейчас не вспомню почему выбрал QImage. Но в следующий раз обязательно попробую QPixmap, и тогда сравним.
        • 0
          QPixmap позволяет создавать обёртку над внешней памятью? Я помню, когда-то мне тоже нужно было рисовать картинку на графической сцене, но данные (буфер памяти) приходили из другой библиотеки, поэтому я использовал QImage просто как обёртку над этой памятью. QPainter умеет рисовать как QImage, так и QPixmap.
          • 0
            QPixmap::loadFromData(http://doc.qt.io/qt-5/qpixmap.html#loadFromData) инициализирует QPixmap изображениеим из сырых данных

            Но это не является именно обёрткой, внутри он производит преобразования и копирование данных себе.
            • 0
              Мне нужна была именно обёртка, чтобы не было копирования данных, так как данные постоянно изменялись извне (естественно без изменения размера), и эти изменения нужно было отображать на экране в реальном времени. Преобразование и копирование в QPixmap на корню убило бы всю производительность в этом случае, а QImage вполне справлялся с достаточной производительностью при разумном разрешении экрана (размере буфера).
          • 0
            А еще лучше QGLPixelBuffer. И нет ничего удивительного в том, что распакованные картинки в памяти занимают больше места.
          • +5
            А о чём статья?! Или даже, а зачем статья?!
            • –2
              О работе с 3-я компонентами Qt, их плюсы и минусы.
              • +2
                А обязательно в хаб «Разработка под Android» помещать было?
                А то что-то мне не нравится фраза:
                При переносе на 5.4 стало еще меньше использоваться оперативной памяти и процессор уже загружен был менее, чем на 50%. Эти все параметры смотрел в Windows, как дела обстоят на андроиде — не в курсе.

                Может, всё-таки, под Windows?
            • +3
              80% cтатьи — вода. Без скриншотов самой игры так вообще бесполезная затея.
              • –1
                Нужны нескучные скриншоты?
                • 0
                  А куда без них, ты шо? Любой текст длиннее 100 символов без картинки смотрится СЫРО.
              • +2
                Google тоже попробовали на Qt'e игрушку написать — VoltAir. Если внимательно почитать все ридми, то можно заметить, что производительностью они не особо довольны, как и загрузкой ЦП.
                Для игр тяжеловата обвязка в общем…
                • 0
                  По мне так это логично. Qt — фрэймворк для создания интерфейсов, а не для работы с реалтаймовой графикой. Для игр есть отдельные движки, специально для этого заточенные. На них, кстати, обычно не очень удобно создавать интерфейсы, что, впрочем, никого не удивляет )

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