Игры на qt. Создание простейшего приложение в Qt и запуск его под Android. Пошло что-то не так с запуском на виртуальном устройстве

Игры на qt. Создание простейшего приложение в Qt и запуск его под Android. Пошло что-то не так с запуском на виртуальном устройстве

5 ответов

Я потратил более десяти лет на различные игры на C++, как на 2D, так и на 3D, для нескольких разных двигателей, больших и малых, и у меня также был короткий, но довольно интенсивный запуск с Qt5 (1,5 года на момент написания). Пока Qt сдул меня со всеми его аккуратно сложными кросс-платформенными и функциональными возможностями, и поэтому я планирую портировать свой крупнейший игровой проект на Qt, как только время позволит мне.

Честно говоря, я немного смутил себя тем, почему Qt не более распространен в играх. Я думаю, что основной причиной является его довольно кривая история лицензирования вместе с тем фактом, что она просто не продается для использования в играх. Qt, в конце концов, сделан главным образом для туза в кросс-платформенной графике UI + в реальном времени на многих платформах из встроенных и выше. Теперь, когда digia взяла верх и с новыми функциями QML/QtQuick, возможно, скоро появится бум Qt, кто знает? Я уверен, что больше разработчиков игр обнаружит Qt и начнет использовать его в своих проектах.

Чтобы ответить на ваш вопрос, я постараюсь сопоставить предложения Qt с потребностями разных типов игр.

Графика производительности для 2D

В традиционном Qt существуют различные абстракции для графики. Для некоторых небольших встроенных платформ он может основываться только на простой 2D-подсистеме. Но для большинства настольных платформ (Windows/MacOSX/Linux) и мобильных платформ (Android/OSX) 2D-абстракция аккуратно опирается на высокопроизводительный 3D-стек на основе OpenGL или OpenGL ES соответственно.

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

Существует множество классов, которые позволяют вам выполнять свой рисунок, как обычно, с известными понятиями, такими как цвет, толщина линии и форма, зная, что все это будет 3D ускорено под капотом.

Тогда появляется совершенно новая вещь "QtQuick" или "QML". Декларативный язык, который вместе с java script обеспечивает самый быстрый и самый скудный способ разыгрывать интерактивный интерфейс и игры, о которых я знаю на этой планете. После того, как мне удалось совершить полный полет клавиш фортепиано в 5 строках кода, и менее чем за 15 минут я был продан.

Графика производительности для 3D

EDITED 23/2-17, чтобы быть более правильным. См. S.O. историю изменений, которые были изменены в этом разделе.

Qt традиционно имеет очень сильную связь с OpenGL, и эта связь имеет сервер Qt очень хорошо, так как OpenGL переносима и работает на широком спектре платформ. Однако с осознанием того, что OpenGL показывает возраст, постоянно возникающий вопрос о сопоставимости с DirectX на платформах Microsoft и появление новых технологий, таких как Vulkan, скрывается в ближайшем будущем, Digia сделала мудрый стратегический шаг, начиная с предварительное превентивное обобщение уровня абстракции оборудования в сторону графического оборудования. Итак, теперь Qt привязан к OpenGL, но вскоре он будет поддерживать многие другие графические абстракции, к вашей большой выгоде в качестве разработчика игр.

Пока мы ждем, в Qt также нет ничего, что помешало бы вам использовать DX вместе с Qt на платформах Microsoft. Qt подходит для того, чтобы не мешать другим библиотекам на любой платформе, поэтому он будет работать с множеством других наборов инструментов и движков, и вы можете даже встроить движки игр на основе OpenGL внутри Qt, открыв контекст Qt OpenGL напрямую. Также сохранение такой же кодовой базы для игры, которая портирована на две примечательные мобильные платформы с Qt, будет намного проще без DX.

Но помимо всех классов удобства для управления эффектами OpenGL, такими как VBO, FBO, загрузкой текстур и шейдеров и т.д., на самом деле нет полноценного 3D-движка в любом месте Qt.

ОБНОВЛЕНИЕ 2016-01-29: С тех пор как я впервые написал этот ответ, я пришел к выводу, что прилагаются большие усилия для обеспечения полномасштабного 3D-движка в Qt. Он называется Qt3D 2.0 и является полным переписанием старого Qt3D, который был частью Qt4.x и был частью только одного релиза, прежде чем он был удален из-за политических причин (Nokia отбросила Qt).

У этого есть внушительный набор особенностей, и много хорошего мышления пошло в это API, чтобы гарантировать, что это может использовать весь потенциал современного графического оборудования, сохраняя при этом гибкость и эффективность. Еще одна хорошая новость заключается в предварительном просмотре технологий. Это означает, что вы можете опробовать его сегодня, и он скоро станет частью основной линии Qt. Вы можете прочитать об этом .

Math

Как часть 3D-стека, Qt имеет полный набор математических функций, таких как матрица, вектор, кватернионы, точка, прямоугольник и т.д.

Общие функции stl math, такие как sqrt, floor, sin и т.д., также обертываются в уровень независимости от платформы.

Нет поддержки для продвинутой математики, такой как полномасштабный физический движок, обнаружение столкновения или ответ.

Для этого вас может заинтересовать следующее:

Управление активами

Qt имеет полноценную систему управления ресурсами, которая компилирует ресурсы в ваш двоичный файл. Это кросс-платформа и поддерживает любой тип файла, который вам нужен, с особой поддержкой для общих форматов изображений, svg, звуков, шрифтов, HTML, CSS. И загрузка и использование ресурсов действительно просты, так как все Qt файлы handlign orutines принимают URL-адреса, указывающие на эти ресурсы. Действительно удивительный.

Пользовательские интерфейсы

Код GUI - это Qt forte. Он имеет отличные инструменты для работы с графическими интерфейсами, что для некоторых небольших 2D-игр на самом деле можно использовать в качестве редакторов уровней.

Поддержка кросс-платформы

ОБНОВЛЕНИЕ 2016-01-29: . Это часть основных значений для Qt как агностики платформы. Qt поддерживает основные настольные платформы, основные мобильные платформы и множество встроенных аппаратных средств, о которых вы никогда не слышали. Пожалуйста, ознакомьтесь с официальным списком поддерживаемых сообществом платформ (free/open source) . Это позволит вам принести свою основную неизмененную базу кодов на все платформы, которые вам нравятся, что сэкономит вам много времени, денег и усилий в тот день, когда вы решите сделать "версию iOS".

Интеграция платформы

Qt завершает всю платформу интеграции кросс-платформенным способом. Это включает в себя:

  • Значки запуска
  • Конфигурационный файл (с должным образом абстрагируемыми особенностями платформы, такими как местоположение и формат).
  • Всплывающие
  • Управление буфером обмена
  • Перетащите
  • Полноэкранный/принудительный режим.
  • Поддержка нескольких дисплеев
  • Всплывающие
  • Внешний вид платформы

2D-анимация

Qt имеет полную структуру для работы с государственными машинами. Вдобавок к этому существует полная структура анимационных свойств, которые могут быть использованы для создания интерактивного интерфейса в 2D (или даже 3D, если вы правильно играете в карты). В нем есть все, что вы ожидаете, как облегчение, автоматический переход состояния и множество удобных оберток и способы делать интересные вещи. Это похоже на работу с jQuery, но на С++. Для некоторых игр это все, что вам нужно.

Контейнеры

Qt имеет кучу STL-подобных контейнерных классов с расширениями для размещения специфических функций Qt. Эти классы включают в себя все: от списков до карт до массивов и буферов.

Строки

Строка string Qt является моей любимой на любом языке. Он имеет свои причуды, но он дает вам массу возможностей и функций, и, как обычно, кросс-платформу вместе со всеми ее друзьями:

Таймеры, потоки и события

Вся структура Qt реализована вокруг асинхронной архитектуры на основе циклов событий со светлыми событиями, называемыми "сигналы".

Qt поддерживает несколько уровней concurrency от потоков низкого уровня, через пулы управляемых потоков до современных сокращений карт и "фьючерсов". Он также имеет кросс-платформенный высокопроизводительный механизм блокировки, такой как мутации, блокировки, условия ожидания, блокировка, материал для атома и целая куча других вещей, которые у меня пока не получилось проверить.

Конечно, Qt имеет поддержку таймеров, которые хорошо вписываются во все это с множеством удобных функций, таких как настройка точности, запуск одного кадра и т.д.

Ввод

Ввод с сенсорных экранов, мышей и клавиатур обрабатывается с использованием архитектуры событий, и Qt действительно имеет это завернутое в корзину. Qt совсем недавно (23/2-17) получил экспериментальную поддержку джойстиков и игровых приставок .

Существует также поддержка датчиков, таких как положение, давление, компас, вращение, температура, поэтому вы также можете делать клоны без клона.

И есть поддержка синего зуба для подключения и обмена игровыми данными по воздуху без доступа к Wi-Fi.

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

Все кросс-платформенные.

Сценарии

Qt имеет встроенный высокопроизводительный механизм ECMA (a.k.a. java script), привязанный к его ядру. О, и вы получите плотно встроенный порт входящего в комплект google веб-кит, если вам это нужно.

Воспроизведение мультимедиа

Qt обертывает gstreamer и другие медиа-библиотеки для поддержки воспроизведения мультимедийных файлов, таких как видео и звук. Это позволяет разработчику выбирать, на каком уровне они хотят взаимодействовать со средствами массовой информации. На высоком уровне вы можете просто поместить виджет и запустить.play() на низком уровне, вы получите доступ к буферам видео и аудио для удовольствия от обработки.

Кодирование носителей

В зависимости от активного back-end Qt также дает вам доступ к кодированию потоков в сеть/диск и т.д.

Классы полезности

С чего начать?

  • MD5/SHA другие безопасные алгоритмы хеширования
  • Алгоритмы сжатия/декомпрессии
  • Алгоритмы шифрования/дешифрования
  • Работа с файлами как с блокировкой, так и без блокировки, с отображением и т.д.
  • Работа с файловой системой, например копирование, перемещение, переименование файлов
  • Мониторинг изменений файлов
  • HTTP-клиент
  • Высокопроизводительное программирование сокетов
  • Генерация синтетических пользовательских интерфейсов
  • Время и дата
  • Доступ к DNS
  • Сообщения об ошибках
  • Обработка MIME и URL-адресов
  • json parser/generator
  • распознавание жеста
  • Скомпилированное регулярное выражение
  • Последовательный IO
  • XML-парсер/генератор
  • Отменить/Повторить

Интернационализация

Внедрение интернационализации посредством использования ICU со всеми расширенными функциями, такими как поддержка RTL в текстовых виджетах и ​​т.д.

Обновления/плагины

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

Тестирование модулей

Qt имеет очень собственную структуру модульного тестирования, встроенную в нее. Этот пост становится длинным, поэтому я не буду вдаваться в подробности.

Документация

Qt имеет действительно хорошую документацию, и она постоянно совершенствуется. Если вы нажали некоторые из моих ссылок, которые вы уже знаете.

Стабильность

Qt был впервые выпущен в 1998 году и, таким образом, долгое время стабилизировался. Он имеет огромный набор регрессионных тестов, который запускается перед каждым выпуском, и он тщательно протестирован на множестве компиляторов, платформ и устройств. По какой-то причине это не входит в число многих списков разработчиков игр.

Это оказалось одним из чертовски длинного поста с большей частью Qt PRO, но я полагаю, что это то, что нужно, чтобы судить о платформе, прежде чем вы ее протестируете. Удачи вам в вашем проекте!

Новая большая MMORPG под названием Rift фактически выполняется с использованием Qt + DirectX. Это, по крайней мере, для меня первая крупномасштабная игра, которую я нашел, которая создана с Qt. Конечно, есть и DirectX, что не является большим сюрпризом. Жаль, потому что сам Qt будет мультиплатформенным, а DirectX - нет.

Итак, убедитесь, что Qt действительно очень хорошая платформа для любой разработки.

Создайте новый проект.

Задайте папку, где будете хранить проект и имя самого проекта. Не допускайте в пути и имени русские буквы!

Выберите под какие архитектуры процессоров вы будете компилировать приложение.

Переходим на форму:

Перетаскиваем на форму кнопку и растягиваем ее.

Запуск на реальном устройстве

Запускать на реальном устройстве проще, чем на виртуальном. Главное, чтобы вы включили отладку на телефоне, подключили его кабелем к компу, и чтобы на копме были драйвера на ваш телефон (на некоторые телефоны не нужны). Например, для Samsung устройств это делается через установку KIES. В общем, на тему подключения телефона аль планшета к компу для запуска Android приложений написано много статей.

У меня JIAYU G4 (для него дополнительные драйвера, например, не нужны). На разных устройствах разные могут стоят процессоры, и какую архитектуру выбрать не всегда понятно. Покажу, где это искать.

Я подключил телефон к компу. В Qt Creator выбираем первую попавшийся архитектуру.

И запускаем компиляцию приложения.

Появится окно выбора устройства для запуска приложения.

И там видим, что на моем телефоне запустить нельзя, так как на нем можно запускать приложения под архитектуру armeabi-v7a .

Значит, в настройках выбираем компиляцию под armeabi-v7a в режиме Release .

Запускаем.

И теперь наше устройство доступно для запуска приложения на телефоне:

Начнется компиляция apk файла, установка его на телефон и запуск приложения.

Процесс может быть не быстрым. Если вы увидели это:

то можете посмотреть, что на телефоне появилось. У меня вот это появилось.

Мы создали приложение и установили на телефоне! Обратите внимание, что наше приложение немного отличается от того, что мы создали в дизайнере форм по внешнему виду, и будет отличаться от того вида, которое мы увидим, если скомпилируем под Windows.

То есть, размеры кнопок и других элементов будут изменены. А чтобы и на компе и на телефоне приложение выглядели одинаково, нужно активно использовать разметку Layout, минимальные размеры компонентов и так далее. Но это уже из области верстки приложений.

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

Теперь еще один момент. Лично я сохранил проект в папке D:\QtProjects :

И там появилась после компиляции папка с нашим сгенерированным приложением:

Зайдем в него и там в папку android-build , а там в папку bin :

И вот наш apk файл. Это и есть приложение под Android, которое вы можете распространять. К сожалению, минус Qt в том, что apk файл получается большим. У нас простое приложение с одной кнопкой весит 7Мб. Но это цена того, что мы создали приложение не в JAVA через Android Studio, например, на через Qt на С++.

Запуск на виртуальном устройстве

Теперь запустим на виртуальном устройстве. Вот тут нас ждут танцы с бубном.

Щелкаем на кнопку для выбора системы под компиляцию.

Ждем, когда Qt всё настроит у себя внутри. Если ниже треугольник стал зеленым, то всё в порядке.

Нажимаем на него (на зеленый треугольник).

Если всё скомпилировалось удачно, то появится окно с выбором устройства для запуска приложения. Так как у меня еще и телефон подключен физически, то у меня виден телефон. У вас этого не будет (если только не подключите специально телефон аль планшет).

Выберете наше виртуальное устройство (оно должно быть в разделе Compatiable devises ).

Вот тут могут начать капризы! Если что-то пойдет не так, в процессе компиляции, то надо закрыть виртуальное устройство, возможно и Qt, возможно перезагрузить комп и попробовать скомпилировать и запустить! Возможно надо будет удалить папку с скомпилированным приложением.

Внизу в Qt Creftor стали появляться данные о компиляции и запуске приложения на виртуальном устройстве.

Внизу справа можно посмотреть на процент компиляции. Процесс не быстрый! Может занять несколько минут!

Если всё пойдет хорошо, то у вас на виртуальном устройстве запустится приложение, и всё будет ok. То есть вы запустили приложением, оно работает, в папке с скопилированным приложением в папке android-build , а там в папке bin найдете apk файл.

Дальнеший же текст предназначет для тех, у кого пошло что-то не так.

Пошло что-то не так с запуском на виртуальном устройстве

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

Теперь оно в разделе Uncompatiable devises . Если мы по нему дважды щелкнем, то Qt Creator выдаст ошибку «When executing step “Deploy to Android device”»

Вначале проверьте платформу процессора, под которую вы компилировали, и платформу на виртуальном устройстве. Если они совпадают, то может помочь следующее:

С этой напастью разобраться просто. Идем в папку, где у вас хранится проект.

И удаляем ту папку, которой соответствует папка с скомпилированым приложением под ту архитектуру у процессора, под которую мы компилировали.

И пробуем опять запустить.

Итак, вы создали и запустили свое первое Android приложение, созданное на Qt.

Написал ремейк небольшой логической игрушки — «Полный квадра т». Код показался мне достаточно интересным чтобы описать на блоге.

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

В любой игре, чуть более интересной чем « «, используются анимации, проигрывается звук, поэтому следующие компоненты Qt затронуты в статье:

  • класс QMovie для отображения gif-анимации тучек и ежа;
  • класс QPropertyAnimation — ежик перемещается плавно, при этом меняются его координаты (свойства);
  • QMediaPlayer из модуля Qt Multimedia . При перемещении наш ёжик топает;
  • Qt Style Sheets (QSS) используется для украшения элементов управления.
снимки игровых экранов

Архитектура игры


UML диаграмма классов игры

Игра состоит из нескольких экранов, которыми управляет ScreenController. Экраны я рисовал с использованием Qt Designer . Каждый игровой экран содержит кнопки, позволяющие перейти на другой экран, события от которых передаются контроллеру.

Логика игры вынесена в класс GameWidget , который загружает на графическую сцену блоки и как-то обрабатывает их сигналы. Например, если на сцене уже размещен ёжик и пользователь кликнул облачко, то GameWidget обеспечивает перемещение ежа в новую позицию при условии, что это не будет противоречить правилам игры.

WinScreen выводит информацию о победе и предлагает сыграть в игру еще раз.

Qt Style Sheets (QSS)

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

Установить таблицу стилей виджету (QWidget ) или приложению (QApplication ) можно методом setStyleSheet() . Для стилизации уже готового приложения можно передать таблицу стилей при запуске вместе с соответствующей опцией (-stylesheet style.qss ). Таблица стилей Qt являются надстройкой над каскадными таблицами стилей, используемыми при верстке веб страниц (CSS3 ) .

Ключевым понятием в QSS является селектор, т.е. элемент, к которому мы хотим применить стили, при этом, если некоторому объекту устанавливается таблица стилей, то стили будут распространяться также на все дочерние элементы. В качестве селектора могут выступать:

  • все виджеты; "* { background-color: rgba(176, 196, 222, 255); }"
  • имя класса виджета. Стиль применяется ко всем элементам, которые могут быть приведены к классу с заданным именем; QLineEdit, QPushButton { border-radius: 10px; }
  • имя объекта определенного класса. Стиль применяется только к этому объекту; A#obj { border-radius: 10px; }
  • имя класса и свойства объекта. Стиль применяется ко всем объектам класса, обладающими заданным свойством; QPushButton { border-radius: 10px; }
  • вложенные виджеты. Можно задавать применение стилей к объектам, вложенным непосредственно в текущий, либо с произвольным уровнем вложенности. A > B { background-color: rgba(255, 255, 255, 255); } A C { background-color: rgba(0, 0, 0, 255); }

В игре стили применяются к объекту класса ScreenController , и автоматически будут распространены на все вложенные виджеты.

SetStyleSheet("* { background-color: rgba(176, 196, 222, 255); }" "QPushButton { " " background-color: rgba(255, 153, 102, 200); " " border-style: outset;" " border-width: 2px;" " border-radius: 10px;" " border-color: beige;" " font: bold 14px;" " width: 3em;" " padding: 6px;" "}" "QPushButton:hover {" "background-color: rgba(255, 102, 0, 200);" "}" "QPushButton:pressed {" "background-color: rgba(255, 0, 0, 200);" "}" "QPushButton:disabled {" "background-color: rgba(204, 153, 102, 200);" "}" "QTextEdit {" "background-color: rgba(102, 204, 102, 200);" " border-style: outset;" " border-width: 0px;" " border-radius: 10px;" " border-color: black;" " width: 3em;" " padding: 6px;" "}");

В примере задается цвет фона для всех виджетов, параметры кнопок и текстового поля, а также цвет кнопок, находящихся в определенных псевдосостояниях . Стиль, примененный для QPushButton:disabled будет распространен на все неактивные кнопки.

При использовании QSS необходимо заглядывать в документацию, чтобы проверить возможность стилизации свойств определенного типа виджета , кроме того, в документации описано огромное количество примеров .

Анимация и звук в Qt

Ежик плавно перемещается, за счет использования QPropertyAnimation, при этом сам ёж при перемещении машет лапками — проигрывается соответствующая .gif-анимация (QMovie) .

Любой элемент игры отображает какую-либо анимацию, поэтому в класс Block включены соответствующие поля и метод установки анимации.

Class Block: public QWidget { Q_OBJECT public: explicit Block(QWidget *parent = 0); void animation(QString texturename, bool randomStartFrame = false); protected: QMovie *m_animation; //!< анимация, проигрываемая блоком QLabel *m_label; //!< метка для отображения анимации }; Block::Block(QWidget *parent) : QWidget(parent), m_animation(new QMovie(this)), m_label(new QLabel(this)) { // ... m_label->setMovie(m_animation); m_animation->setCacheMode(QMovie::CacheMode::CacheAll); resize(BlockParam::BlockSize, BlockParam::BlockSize); m_animation->setScaledSize(size()); } // ... void Block::animation(QString texturename, bool randomStartFrame) { m_animation->stop(); m_animation->setFileName(texturename); if (randomStartFrame) m_animation->jumpToFrame(qrand() % m_animation->frameCount()); m_animation->start(); }

Класс блок агрегирует экземпляр QLabel, на котором и отображается анимация. QMovie поддерживает кеширование , которое по умолчанию отключено. При кешировании, в память помещается не следующий кадр, а пачка кадров, поэтому сокращаются затраты на их переключение. В приведенном фрагменте используется метод jumpToFrame() , устанавливающий текущий кадр, который не должен использоваться при включенном кешировании , однако в нашем случае он производит лишь установку начального кадра анимации и никогда не переключает кадры во время проигрывания.

Литература по теме

  1. [Электронный ресурс] — режим доступа: https://сайт/archives/958. Дата обращения: 24.07.2014.
  2. Qt Style Sheets Reference [Электронный ресурс] — режим доступа: http://doc.qt.io/qt-5/stylesheet-reference.html. Дата обращения: 24.07.2014.
  3. Qt Style Sheets Examples [Электронный ресурс] — режим доступа: http://doc.qt.io/qt-5/stylesheet-examples.html. Дата обращения: 24.07.2014.
  4. . Дата обращения: 24.07.2014.

Космос сам себя не наложит

Предпосылки

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

Давно хотел сделать какой-нибудь проект под Android, а, как известно, основная масса проектов разрабатывается на Android SDK и Java, а NDK рекомендуют использовать только в “критичных по скорости” местах и не делать на нем все целиком.

Но кому нужны все эти рекомендации и правила, когда есть Qt? Java я не знаю в той степени, которую считаю достаточной для качественной разработки игры, и изучать мне ее не хотелось, зато у меня имеются в запасе знания C++. После нескольких тестовых проектов на Qt под Android я понял, что на нем вполне можно разработать полноценное приложение, да еще и перенести его на другие платформы. Так же, посмотрев видео Shia LaBeouf - Just Do it, стало понятно, что я обречен это сделать.

Итак, я хочу рассказать про опыт разработки игры под Android на Qt 5.5.1 и С++.

Идея

Как ни странно, идея игры пришла довольно быстро и очень необычным образом. Мы экспериментировали с датчиками телефона в попытках сделать игрушку “стукач”(название временное), смыслом которой было - чем сильнее и чаще ударяешь телефон, тем больше очков получаешь. Для этого необходимо было отличить удар телефоном от взмаха, и, к счастью, нам это не удалось, но при выводе графиков на экран рисовались интересные картины, что и натолкнуло нас на идею игры, о которой и пойдет речь.

Внезапно, захотелось сделать игру про нечто, падающее в пещеру, контуры которой меняются со временем. Первоначально предполагалось сделать двухмерную игру с графикой в рисованном стиле. В дальнейшем все перетекло в трехмерную игру с видом сверху. Был написан генератор пещеры, который позволял создавать слои с контурами. Как рассчитывать столкновения с стенами пещеры и как превратить их в трехмерную модель? Можно было долго думать и выдумать что-нибудь невероятно классное, но я пошел обходным путем и сделал воксельную геометрию мира. Расчет столкновений и вывод на экран стали заметно проще, но ценой того, что игра стала похожа на майнкрафт. Я не растерялся и сделал её еще больше на него похожей, добавив модель персонажа из него же. Так и сформировался вид и концепция игры.


Первая версия генератора пещеры (архивное фото)

Приступая к разработке игры, я не ставил себе цель заработать на ней, как многие, а хотел просто начать свой проект и довести его до конца. Поэтому игра должна быть бесплатная (кто у меня ее купит?), но все-таки с минимальным количеством рекламы.

Графика

Средства
В Qt для трехмерной графики можно использовать OpenGL ES 2.0, в этом нам могут помочь следующие классы:
  • QGLWidget - устаревший класс, наследник QWidget. Использовался до Qt 5.0
  • QOpenGLWidget - новый класс, выполняет те же функции, что и QGLWidget, но в своей реализации использует промежуточный фреймбуфер
  • QOpenGLWindow - предназначен для вывода графики OpenGL, не является виджетом и не тянет зависимостями модуль widget

Экспериментируя с этими виджетами, я сделал несколько выводов:

  • QGLWidget работает намного быстрее QOpenGLWidget на слабых смартфонах
  • QOpenGLWidget не меняет размер внутреннего фреймбуфера при изменении размеров окна, вероятно, это баг или фича, или я просто идиот, но тем не менее, при использовании этого виджета разрешение окна не совпадало с размером вьюпорта, что приводило либо к растянутому изображению, либо к поехавшей матрице проекции. При всем при этом производительность с использованием этого виджета была крайне мала
  • QOpenGLWindow - самый подходящий кандидат. Его нельзя использовать как viewport для графической сцены и нельзя добавлять дочерние виджеты, что лишает возможности создать пользовательский интерфейс средствами Qt. Но зато он самый производительный из всех трех

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

Обновление кадров было реализовано по сигналу frameSwapped(), который генерируется после swapBuffers. Использование этого сигнала позволяет достичь большей плавности смены кадров, чем при использовании таймеров.

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

Поразмыслив и обратившись к документации, я решил использовать класс QElapsedTimer , который старается использовать монотонное время и имеет разрешение до наносекунд.

Текстуры и интерфейс
Для первых версий и отладки текстуры были позаимствованы временно из майнкрафта, как и какой-то скин персонажа. Первая версия интерфейса представляла собой серые квадратные кнопки и была сделана за вечер, а переработка заняла целый месяц.

Первый скриншот игры



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

В дальнейшем мы нарисовали оригинальные текстуры для нескольких уровней и сделали текстуры персонажей.
Процесс рисования “красивого” интерфейса и персонажей оказался довольно длительным. Пока дизайнер пытался выдавить из себя нового персонажа или кнопку, мне ничего не оставалось делать, кроме как продолжать оптимизировать код.

Скриншот из предпоследней версии



Текстура уровня представляет собой большой атлас всех блоков и вмещает в себя 32 куба со стороной 16 пикселей. При таком подходе при рисовании слоев не требуется постоянно переустанавливать текущую текстуру, а можно рисовать весь уровень, используя вершинные буферы за несколько вызовов.

Проблема возникла в крайних пикселях блоков, которые являются смежными с другими блоками. При округлении текстурных координат стали появляться артефакты в виде полосок на краях блоков. Эту проблему я решил путем добавления каждому блоку окантовки шириной в один пиксель, дублирующую пиксель края блока. Такой своеобразный CLAMP_TO_EDGE.

К сожалению, при таком подходе я не смог использовать mipmap для уровня, т.к. текстурные координаты в вершинном буфере должны учитывать это смещение на один пиксель, а при создании текстуры уменьшенной детализации это смещение становится меньше, и мне бы пришлось использовать различную геометрию уровня в зависимости от степени детализации. Я решил с этим не заморачиваться, т.к. сама текстура уровня всего 256х256 пикселей, и особого выигрыша в производительности билинейная фильтрация не принесла бы, а принесла бы лишь дополнительные сложности в реализации.

Еще хотел заметить, что хотя блоков и всего 32, но в свойствах уровня я задал возможность расставлять блоки повернутыми на 90, 180 и 270 градусов, а также анимацию переключения между текстурой блока, что позволило разнообразить визуальную составляющую игры. Хотя, анимацию я применил только на одном из уровней для создания эффекта вращения вентиляторов.

Шейдеры
В Qt имеются удобные классы для работы с шейдерами. Я использовал QOpenGLShaderProgram . Этот класс позволяет добавлять вершинные и фрагментные шейдеры, компилировать их, линковать, устанавливать uniform и attrubute. Сам по себе класс является просто оберткой над множеством вызовов OpenGL и, соответственно, не является полноценным объектом, в том понимании, что работу класса можно нарушить, используя вызовы GL напрямую между вызовами этого класса.

Удобно то, что класс автоматически добавляет define таким образом, что шейдер ES компилируется нормально и на десктопе и на мобильном устройстве. Это относится в первую очередь к спецификаторам точности, которые на десктопе превращаются в ничто.

Мне пришлось написать отдельные шейдеры для игрового мира, включающие освещение и анимацию некоторых блоков, шейдер для персонажа и шейдеры интерфейса.

Шейдеры интерфейса включают в себя

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

Бар хп\щит

Этот бар я решил реализовать шейдером, сначала мне это показалось очень странной идеей, но в конце концов я ее принял как неизбежное и реализовал таким способом:

Код шейдера

#ifdef GL_ES precision highp int; #endif varying highp vec4 v_position; varying highp vec2 v_dim; uniform lowp vec4 u_color; uniform highp float u_tg; uniform highp float u_value_a; uniform highp float u_value_b; uniform highp int u_step; uniform lowp vec3 u_pallete; void main() { int hstep = u_step/2; int w = int(v_dim.x); int h = int(v_dim.y); int wd = u_step*3; int pos_bl = int(v_position.x - u_tg*v_position.y + v_dim.y); int pos_br = int(v_position.x + u_tg*v_position.y); int pos_l = (pos_bl - wd*(pos_bl/wd))/u_step; lowp float b0 = float(pos_l == 0) * float(pos_bl <= int(u_value_a*v_dim.x)); lowp float b1 = float(pos_l == 1) * float(pos_bl >= int((1.0-u_value_b)*v_dim.x)); lowp float b2 = clamp(float(pos_l == 2) + 1.0-(b0+b1), 0.0, 1.0); highp float p = abs(2.0*(v_position.w - 0.5)); highp float out_p = (1.0 - 0.25*p); lowp float i = float(int(v_position.y) > hstep) * float(int(v_position.y) < h - hstep); lowp float o = (1.0-i)*float(pos_br >= h) * float(pos_bl <=w); lowp float a = i*float(pos_br >= h+u_step) * float(pos_bl <=w - u_step); lowp float b = i*float(pos_br < h+u_step) * float(pos_br >= h); lowp float c = i*float(pos_bl > w - u_step) * float(pos_bl <= w); highp float pr = (1.0 - p)*a; gl_FragColor = vec4(u_pallete*pr*b0 + u_pallete*pr*b1 + u_pallete*pr*b2 + u_pallete*out_p * b + u_pallete*out_p * c + mix(u_pallete, u_pallete, v_position.z)*out_p*o, clamp(a+b+c+o, 0.0, 1.0)*u_color.a); }


Отдельно хотелось бы сказать про разнообразие android устройств и их графических ускорителей. Я столкнулся с проблемами:
  • Код шейдера компилируется без проблем на одном устройстве, а на другом возникают ошибки
  • Шейдер нормально компилируется, но на некоторых устройствах неправильно работает. Например, на PowerVR, если локальная переменная называется так же, как и юниформ или атрибут - это не приводит к ошибкам, но сам шейдер перестает работать корректно
  • На некоторых устройствах возникают ошибки, если переменная или функция называется так же как и встроенная функция. Например, переменная mix или clamp
  • Возникали проблемы с точностью float на некоторых устройствах

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

Звуки

Поиск
Звуки - это вообще отдельная песня. Их можно записать самому, что довольно трудоемко и требует наличия нормального микрофона и слуха (не наш случай). А можно найти желанные звуки в интернете.
Искать звуки желательно с лицензией Creative Commons 0, дабы не указывать авторство для каждого звука, коих может быть несколько десятков. Может показаться, что найти нормальный free звук довольно тяжело, и оно так и есть. Проблема не в том, что их мало, напротив, - их очень много, большинство из которых ужасны. Поиск звука - процесс, в котором надо переслушать очень-очень много звуков и выбрать из них самые подходящие.
Средства
В Qt для вывода звуков существуют классы QSound , QSoundEffect , QAudioOutput и QMediaPlayer . В первых версиях я использовал QSoundEffect для вывода эффектов и QMediaPlayer для вывода звуков. Но, как оказалось, все они не подходят.

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

  • Фризы при запуске звука. Когда музыка играет в зацикленном состоянии, при повторении возникает ощутимая задержка всего приложения.
  • При чтении медиафайла из ресурсов он создает временный файл в папке с данными приложения и не всегда удаляет его за собой, что приводит к росту размера приложения. Это было обнаружено случайно и хорошо, что вовремя.
QSound и QSoundEffect умеют работать только с несжатыми wav файлами. QSoundEffect предназначен для вывода звуков без задержек и умеет зацикливать звуки сам, но при его использовании на Android в логах часто появляется сообщение AUDIO_OUTPUT_FLAG_FAST denied by client”, что означает - формат звукового файла не может быть выведен медиасервером без задержек. Это связано с неправильной частотой дискретизации, которая различна на разных устройствах. Некоторые устройства проглатывают 44KHz, некоторым надо 48KHz, а сам обьект передает звук так, как он записан в файле wav. Размер звуковых ресурсов составлял значительный процент от размера приложения.

Все эти недостатки привели к тому, что после добавления звуков в игру появились ощутимые проседания FPS при воспроизведении звуков и музыки.

Решением стал отказ от этих классов и использованием для звуков библиотеки SFML . Очень простая и легковесная библиотека, похожая на SDL. Удобные классы для работы с графикой, звуком, устройствами ввода. Эта библиотека не умеет работать с mp3 (лицензия, все дела), но зато умеет многое другое. Я использовал для эффектов и музыки формат ogg.

Вывод по звукам
Для использовании в приложении, где звуки являются чем-то необязательным, Qt-шные родные классы подходят. Для разработки игр - совсем нет. Лучше и проще использовать SFML.

Реклама в приложении

Для рекламы я использовал уже готовую реализацию AdMob под Qt - QtAdMob .

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

Аналитика и статистика

Выкладывая первую версию на Play Market, я понадеялся на статистику в консоли разработчика. Но, как оказалось, статистика активных пользователей завязана на Google Analytics и не работает, пока в приложении не включишь трекер аналитики. А та статистика, что доступна, приходит с задержкой более суток и рассчитывается по тихоокеанскому времени. Такое положение дел не позволяет понять, как же твои действия влияют на скачивания. Поэтому я дополнил класс activity из QtAdMob, который наследуется от QtActivity функциями инициализации аналитики и функциями отправки событий игры. Примеры кода не привожу, т.к. все прекрасно описано в документации.

В события я вынес нажатие всех кнопок интерфейса, возникновение некоторых игровых ситуаций, открытия и разблокировки персонажей с уровнями.

Благодаря сбору всей этой статистики я могу сидеть за ноутбуком и наблюдать в реальном времени, как в Бразилии кто-то запустил игру, не смог пройти первую миссию, вышел и, вероятно, удалил игру.

Еще, если верить статистике,- мы сейчас чемпионы по нашей игре.

Про Google Play

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

Чтобы полноценно разрабатывать приложение самому, необходимо как минимум иметь аккаунты AdWords, AdSense, AdMob, Google Analytics. При этом между ними устанавливается связь. Все эти аккаунты - отдельные продукты Google и имеют различную техническую поддержку и настройки. Также стоит заметить, что аккаунт AdMob требует наличия аккаунтов AdWords и AdSense. При этом все эти аккаунты могут быть привязаны в единственном экземпляре к основному аккаунту Gmail. Но, как показала практика, во всем этом можно запутаться с самого начала, потому что ты открываешь один сервис, он тебе предлагает создать новый аккаунт в другом, тот в третьем и так далее.

Я каким-то магическим образом так, что сотрудник техподдержки не смог объяснить произошедшее, создал 2 аккаунта AdWords и привязал их к одной почте, при этом привязав один аккаунт к консоли разработчика, а другой к AdMob (об этом я не знал).

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

Так что, аккуратнее с этим.

Перевод

В игре
Игровое меню мы перевели на 2 языка - русский и английский. Выбор языка игры осуществляется по системной локали. Если в системе русский язык - то игры на русском, во всех остальных случаях - на английском.

Для осуществления переводов текстовой информации в Qt существует встроенный механизм, приводящийся в исполнения классом QTranslator myTranslator; myTranslator.load(":/translations/neverfall_" + QLocale::system().name()); a.installTranslator(&myTranslator);
Все строки, которые необходимо перевести, передаются в функцию QObject::tr(), для классов, не являющихся наследниками QObject можно использовать функцию QApplication::translate и для строк, объявленных в массивах макросы QT_TRANSLATE_NOOP, QT_TR_NOOP.

Но это только полдела. Необходимо создать сами переводы, что производится программами lupdate и lrelease. Первая собирает информацию из исходного кода, содержащего эти функции и макросы, и создает Xml файл с информацией для перевода.
Вторая собирает из xml файла бинарный файл qm, который загружается непосредственно в



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows