Опыт разработки аркады под Android на С++ и Qt
Космос сам себя не наложит
Предпосылки
Я, как и многие программисты, выбрал эту профессию, потому что в детстве играл в компьютерные игры и мечтал их разрабатывать. Как только я научился более-менее писать код, который может компилироваться без синтаксических ошибок, я, конечно, стал делать всякие глупые игры, которые показывал всем друзьям и знакомым. Но время шло, и оно заставило заниматься совершенно другими вещами, работать над проектами, которые, мягко говоря, серьезней, чем игры. И так продолжалось последние несколько лет. А изначальные желания никуда не делись, исчезло только свободное время.
Давно хотел сделать какой-нибудь проект под Android, а, как известно, основная масса проектов разрабатывается на Android SDK и Java, а NDK рекомендуют использовать только в “критичных по скорости” местах и не делать на нем все целиком.
Но кому нужны все эти рекомендации и правила, когда есть Qt? Java я не знаю в той степени, которую считаю достаточной для качественной разработки игры, и изучать мне ее не хотелось, зато у меня имеются в запасе знания C++. После нескольких тестовых проектов на Qt под Android я понял, что на нем вполне можно разработать полноценное приложение, да еще и перенести его на другие платформы. Так же, посмотрев видео Shia LaBeouf — Just Do it, стало понятно, что я обречен это сделать.
Итак, я хочу рассказать про опыт разработки игры под Android на Qt 5.5.1 и С++.
Идея
Как ни странно, идея игры пришла довольно быстро и очень необычным образом. Мы экспериментировали с датчиками телефона в попытках сделать игрушку “стукач”(название временное), смыслом которой было — чем сильнее и чаще ударяешь телефон, тем больше очков получаешь. Для этого необходимо было отличить удар телефоном от взмаха, и, к счастью, нам это не удалось, но при выводе графиков на экран рисовались интересные картины, что и натолкнуло нас на идею игры, о которой и пойдет речь.
Внезапно, захотелось сделать игру про нечто, падающее в пещеру, контуры которой меняются со временем. Первоначально предполагалось сделать двухмерную игру с графикой в рисованном стиле. В дальнейшем все перетекло в трехмерную игру с видом сверху. Был написан генератор пещеры, который позволял создавать слои с контурами. Как рассчитывать столкновения с стенами пещеры и как превратить их в трехмерную модель? Можно было долго думать и выдумать что-нибудь невероятно классное, но я пошел обходным путем и сделал воксельную геометрию мира. Расчет столкновений и вывод на экран стали заметно проще, но ценой того, что игра стала похожа на майнкрафт. Я не растерялся и сделал её еще больше на него похожей, добавив модель персонажа из него же. Так и сформировался вид и концепция игры.
Первая версия генератора пещеры (архивное фото)
Приступая к разработке игры, я не ставил себе цель заработать на ней, как многие, а хотел просто начать свой проект и довести его до конца. Поэтому игра должна быть бесплатная (кто у меня ее купит?), но все-таки с минимальным количеством рекламы.
Графика
Средства
-
— устаревший класс, наследник QWidget. Использовался до Qt 5.0 — новый класс, выполняет те же функции, что и QGLWidget, но в своей реализации использует промежуточный фреймбуфер — предназначен для вывода графики 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 компилируется нормально и на десктопе и на мобильном устройстве. Это относится в первую очередь к спецификаторам точности, которые на десктопе превращаются в ничто.
Мне пришлось написать отдельные шейдеры для игрового мира, включающие освещение и анимацию некоторых блоков, шейдер для персонажа и шейдеры интерфейса.
- простой шейдер, выводящий текстуру
- шейдер бара хп\щит
- шейдер круга
Бар хп\щит
Этот бар я решил реализовать шейдером, сначала мне это показалось очень странной идеей, но в конце концов я ее принял как неизбежное и реализовал таким способом:
- Код шейдера компилируется без проблем на одном устройстве, а на другом возникают ошибки
- Шейдер нормально компилируется, но на некоторых устройствах неправильно работает. Например, на PowerVR, если локальная переменная называется так же, как и юниформ или атрибут — это не приводит к ошибкам, но сам шейдер перестает работать корректно
- На некоторых устройствах возникают ошибки, если переменная или функция называется так же как и встроенная функция. Например, переменная mix или clamp
- Возникали проблемы с точностью float на некоторых устройствах
Итог всего этого — если вы планируете использовать шейдеры в мобильной игре, проверяйте их на самых распространенных моделях графических ускорителей. Если шейдер компилируется и работает у вас на компьютере, это еще не означает, что он будет работать на телефоне вашего соседа. Новый Vulkan API должен решить проблему с различными компиляторами шейдеров и привнеси порядок в этот безумный мир, но это дело будущего, а сегодня имеем то, что имеем.
Звуки
Поиск
Звуки — это вообще отдельная песня. Их можно записать самому, что довольно трудоемко и требует наличия нормального микрофона и слуха (не наш случай). А можно найти желанные звуки в интернете.
Искать звуки желательно с лицензией Creative Commons 0, дабы не указывать авторство для каждого звука, коих может быть несколько десятков. Может показаться, что найти нормальный free звук довольно тяжело, и оно так и есть. Проблема не в том, что их мало, напротив, — их очень много, большинство из которых ужасны. Поиск звука — процесс, в котором надо переслушать очень-очень много звуков и выбрать из них самые подходящие.
Средства
В Qt для вывода звуков существуют классы QSound, QSoundEffect, QAudioOutput и QMediaPlayer. В первых версиях я использовал QSoundEffect для вывода эффектов и QMediaPlayer для вывода звуков. Но, как оказалось, все они не подходят.
- Фризы при запуске звука. Когда музыка играет в зацикленном состоянии, при повторении возникает ощутимая задержка всего приложения.
- При чтении медиафайла из ресурсов он создает временный файл в папке с данными приложения и не всегда удаляет его за собой, что приводит к росту размера приложения. Это было обнаружено случайно и хорошо, что вовремя.
Все эти недостатки привели к тому, что после добавления звуков в игру появились ощутимые проседания 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 существует встроенный механизм, приводящийся в исполнения классом
Все строки, которые необходимо перевести, передаются в функцию QObject::tr(), для классов, не являющихся наследниками QObject можно использовать функцию QApplication::translate и для строк, объявленных в массивах макросы QT_TRANSLATE_NOOP, QT_TR_NOOP.
Но это только полдела. Необходимо создать сами переводы, что производится программами lupdate и lrelease. Первая собирает информацию из исходного кода, содержащего эти функции и макросы, и создает Xml файл с информацией для перевода.
Вторая собирает из xml файла бинарный файл qm, который загружается непосредственно в QTranslator.
Мы использовали в качестве переводимых строк что-то вроде тегов, которые затем переводили на английский и русский. Например, “#GameOverText” переводится в “Игра закончена”. Сделано было так, чтобы не было необходимости менять исходный код, чтобы что-то по-другому написать, а затем еще и менять все переводы, т.к. для lupdate это уже другая строчка.
На маркете
В Google Play мы пошли по самому простому пути: написали текст на русском — закинули в гугл-переводчик — перевели на английский, подкорректировали. А затем английский текст перевели на самые распространенные языки тем же самым гугл-переводчиком. Забавно, что один из вариантов описания содержал фразу “Окунись в подземелье с головой”, которая, пройдя весь этот пусть переводов на китайском, означала “Прыжки в пещере на голове”. Мы так и оставили, потому что раз они нам на АлиЭксперессе перлы выдают, так и мы не будем отставать.
Заключение и планы
В заключение хочу сказать, что процесс разработки этой игры был и является одним из самых интересных занятий, принес нам огромное удовольствие и опыт, которым я скромно делюсь в этой статье. Разработка велась в свободное время, после работы и ночью, с августа этого года. Потрачены деньги были только на аккаунт разработчика и пару тысяч на рекламу, так что я не расстроюсь, если нам эта игра принесет чуть менее, чем ничего.
Дальнейшие планы зависят от того, как люди будут реагировать на наше творение. Судя по отзывам, довольно неплохо, но судя по скачиваниям и удалениям, хочется сходить за веревкой в ближайший магазин хозтоваров. Вероятно, игра получилась довольно сложная, и у нас явный недостаток рекламной деятельности
Мы планируем перенести игру под ios, но этому мешает отсутствие яблочной техники и 100$ в год, а так же пугает перспектива общения на эльфийском Objective C.
Я надеюсь, что это не последняя наша игра, есть множество новых идей, которые я буду стараться воплотить в жизнь, учитывая полученный опыт. Эта игра — первый блин, комом он или нет — судить не мне.
Предвосхищаю комментарии по поводу того, что это можно было сделать проще и быстрее, используя готовые движки, привлекая дизайнеров и издателей. Отвечу на это, что мы желали пройти путь джедаев-разработчиков с начала и до конца, своими силами, дабы окунуться в это с головой.
Introduction
![]()
I would like to walk you through setting up a simple Android Native Activity. I will walk through the basic setup, and try to give you the tools you need to move forward.
Although my focus is on games, I will not be telling you how to write an OpenGL application or how to architect your own game engine. That is a discussion that literally fills up books.
Why C++
On Android, the OS and its supporting infrastructure are designed to support applications written in the Java or Kotlin programming languages. Applications written in these languages benefit from tooling embedded deep into the system’s underlying architecture. Many core Android system features, such as the Android UI and Intent handling, are only exposed through Java interfaces.
By choosing to use C++, you are not being “more native” to Android than using a managed language such as Kotlin or Java. Counter intuitively, you are in some ways writing a foreign application with only a subset of Android features available. For most applications, you’re better off with languages like Kotlin.
There are a few exceptions to this rule. Near and dear to my own heart is game development. Since games typically use custom rendering logic, often written in OpenGL or Vulkan, it’s expected that a game looks different from a standard Android application. When you also consider that C and C++ are near universal to every computing platform, and the relative wealth of C libraries to support games development, it may become reasonable to set off down the path of native development.
If you want to make a game from scratch or port over an existing game, Android’s Native Development Kit (or NDK) and its supporting tooling are ready and waiting. In fact, the native activity I’ll be showing you provides an easy one-stop shop in which you can set up an OpenGL canvas and start gathering user input. You may find that despite C’s cognitive overhead, some common code challenges like building vertex attribute arrays from game data become easier in C++ than higher level languages.
What I won’t Cover
I will stop short of showing you how to initialize a Vulkan or OpenGL context. I recommend reading through the samples Google provides, although I will provide some tips to make your life easier. You may opt instead to use a library like SDL or even Google’s FPLBase instead.
Setup your IDE
First we need to make sure that you have everything installed for native development. For this, we need the Android NDK. Launch Android Studio:
H Пишем Android-игру на Xamarin+MonoGame (C#) в черновиках Tutorial

Сегодня мы будем писать простенькую игру для Android на языке C# с использованием Xamarin и MonoGame.
-
— это framework для кроссплатформенной разработки мобильных приложений (iOS, Android, Windows Phone) с использованием языка C#. — это кроссплатформенная реализация игрового движка XNA, позволяющая писать игры не только под Windows и Windows Phone (как изначально задумывалось Microsoft), но и массу других платформ, включая Android.
Сформулированный принцип: PacMan должен съесть максимальное число приведений, уворачиваясь от хаотично летающих желтых шестеренок, не задев при том границы поля (оно огорожено колючей проволокой); за каждое «съеденное» приведение начисляется одно очко, за каждое ранение шестеренкой — снимается одна жизнь (из пяти), за пересечение границы поля — сразу все жизни.
Игра будет на двух языках: русском и английском. Должно присутствовать игровое меню и дружественный для пользователя интерфейс.
Под катом много скриншотов, схем и кода) В конце урока приведена ссылка на GitHub с полными исходниками игры.
Вот такой вот «очень сердитый» (даже слишком) загрузочный экран удалось нарисовать (если что, это не хоррор, а просто игра для убивания времени): 
Работать будем в Xamarin Studion (фирменная IDE, свои впечатления об ее использовании я упоминал в статье на хабре). Так как до Xamarin 5 добраться пока не довелось, весь проект оформлен в 4.x. Впрочем, желающие могут без видимых проблем использовать VS2010/2012/2013.
Подготовка графики
Для начала подберем графику. В принципе, это шаг, на котором создается вся концепция 2d-игры. Несколько часов работы в Photoshop, создание шаблонов, и, ура, весь дизайн игры продуман и проработан! Хорошо, если в вашей команде этим занимается отдельный человек — дизайнер; мне же, выполняющему все работы сразу, всегда почему-то трудно переключаться между программированием и скрупулезной работой в графическом редакторе…
На уровне шаблона в Photoshop:

Понадобятся текстуры интерфейса — PacMan-а и его врагов (а также элементов интерфейса):


Важный момент! Чтобы в дальнейшем не было проблем с масштабированием и пропорциями, всю графику адаптируем под одно разрешение. У меня выставлено 960×540. Это не значит, что при непосредственном написании кода все будет заточено под одно разрешение, напротив, позволит равномерно масштабировать согласно выставленному на устройстве (можете использовать свое, главное, чтобы вся графика создавалась в его пределах, подробности чуть дальше).
Все фоны игры выполним в одной цветовой гамме. Я выбрал синий цвет.
Меню начала игры и паузы будет выглядеть так:


Это статическая текстура. По середине будут появляется кнопки «Начать игру», «Выход»; либо «Продолжить», «Новая игра», «Выход»; в правом нижнем углу — ссылка на официальный сайт разработчика. Сами кнопки заготовим немного позже, сейчас главное подобрать основную графику.
Фон для меню победы: 
Фон для меню поражения: 
Фон самой игры: 
Границы экрана:
- background_game.png — фон игры
- background_lose.png — фон поражения
- background_win.png — фон победы
- border.png — границы экрана (колючая проволока)
- cross.png — значок, определяющий жизнь PacMan-а
- pacman.png — текстура PacMan-а
- pacman_back.png — отраженная по горизонтали текстура PacMan-а
- target.png — приведение
- target_bad.png — желтая шестеренка
- ru_LoadingScreen.png — начальный фон после загрузки и фон паузы на русском языке
- en_LoadingScreen.png — начальный фон после загрузки и фон паузы на английском языке
- ru_splash.png — фон загрузочного экрана на русском языке
- en_splash.png — фон загрузочного экрана на английском языке
Создание и настройка проекта
Создаем проект, указав «MonoGame for Android Application»: 
Переименовываем Game1 в Game, а Activity1 в ActivityMain. В итоге должны получить вот такой вот «чистый» проект: 
Теперь начинаем его настраивать. Щелкаем правой кнопкой по надписи PacMan во главе списка слева (голубой картинке), выбираем пункт Options. В General выставляем Target Framework «Android 4.0», в Android Build на вкладке Advanced выставляем все галки (armeabi, armeabi-v7a, x86), в Android Application ставим Minimum и Target Android Version в «4.0», а также ставим галочки Internet и WriteExternalStorage (понадобится для того, чтобы сохранять результаты игры). Также выбираем значок icon в «@drawble/icon».
Обратим внимание, что Version name установлена в 1.0 (в принципе, можете поменять на какую хотите, к примеру, 0.1b, это лишь отображаемая версия), а Version number равно 1. Значение Version number лучше не трогать, это по нему Google Play узнает, нужно ли обновлять игру, если в личный кабинет был загружен новый *.apk-файл. Таким образом, при каждом новом релизе нельзя использовать прошлые значения Version number.
В Resources/Drawble создаем папки drawable-hdpi, drawable-ldpi, drawable-mdpi, drawable-xhdpi, drawable-xxhdpi. В них нужно будет поместить значки в разных разрешениях, которые лучше всего сгенерировать используя App Icon Template. В drawble: 144×144, в drawable-hdpi: 72×72, в drawable-ldpi: 36×36, в drawble-mdpi: 72×72, в drawable-xhdpi: 96×96, в drawable-xhdpi: 144×144. Все значки должны быть названы просто icon.png.
splash.png — загрузочный экран Xamarin, который будет показываться при открытии приложения в момент подгрузки виртуальной машины. На деле (в таком маленьком приложении) он не будет показываться, поэтому можно заменить его на то же изображение, что и в ru_splash/en_splash, но без надписей. Сами ru_splash/en_splash понадобятся при загрузке текстур.
В Assets создаем папку Content, в ней — папки en и ru (для удобства — чтобы разделять языки). Помещаем туда заготовленные картинки как показано на скриншоте (в дальнейшем их еще придется пополнять): 
Также создадим в папке Resources папки values, values-ru, values-en, values-uk, values-be, values-sr, а в них файл strings.xml. Как вы уже наверняка догадались, это файлы со строками, каждая на своем языке, туда поместим название приложения. Не смотря на то, что сама игра будет только на русском и английском, сделаем так, чтобы название могло отображаться на украинском, белорусском и сербском. Трудозатрат никаких (Google Translate на славянских языках работает довольно качественно), а вот увидеть приложение в Google Play и, в дальнейшем, значок с подписью на национальном языке, все равно приятно. Само приложение достаточно сделать на русском — сербы, белорусы и те, кто выставили в настройках на телефоне украинский, все равно русский язык понимают хорошо.
На этом подготовительные работы закончены, можно приступать к непосредственной разработке.
Разработка «внутренних» классов
Поставим задачей опосредовать логику игровых объектов и логику самого движка XNA/MonoGame. Пусть «внутренние» классы — классы, используемые для обработки внутреннего игрового процесса, то есть игровых объектов «игровой процесс», «игрок», «враг»; а «внешние» классы — классы, используемые для взаимодействия внутреннего игрового процесса и игрового движка. Остановимся подробнее на «внутренних».
Ниже приведу код с комментариями. Я постарался расписать его максимально подробно, если что-то не понятно, спрашивайте. Сразу обратим внимание, что все координаты указаны в пределах 960×540, то есть на уровне логики игровых объектов масштабирования игрового поля согласно текущему разрешению экрана телефона не существует).
В Classes.GameProcess.cs используется класс ClassScoreManager, предназначенный для управления счетом (считывание из системного хранилища Android) и запись. О нем разговор пойдет в дальнейшем.
Разработка «внешних» классов
Рассмотрим Game.cs — основной класс «игра» (содержит все визуальные объекты и методы обработки игрового процесса). Тем, кто знаком с XNA Game Studio и используемой логикой, будет проще, поскольку в MonoGame ничего не изменилось. Если говорить кратко:
- public PacManGame() — инициализация класса (то есть все действия, выполняемые при создании класса «игра»)
- protected override void LoadContent() — загрузка контента (всех текстур, также понадобится загружать шрифт)
- protected override void Update(GameTime gameTime) — обновление игрового процесса, то есть все действия, выполняемые в единицу времени (кроме отрисовки)
- protected override void Draw(GameTime gameTime) — отрисовка визуальных объектов в единицу времени (также может содержать действия, т.е. Update можно не использовать, но с этим осторожнее: к примеру, обработка управления идет только через Update)
Для этого я ввел переменные const int NominalWidth = 960, const int NominalHeight = 540, float Dx, а также int CurrentWidth и int CurrentHeight. Что это значит? Что в CurrentWidth/CurrentHeight нужно считать текущую высоту и ширину экрана устройства, и посчитать соотношение с ScreenWidth и ScreenHeight, получив результат в Dx. А дальше отрисовывать всю графику с увеличением в Dx раз.
Не по стандартам Android? Определенно да. Тем не менее, без привлечения сторонних разработок, MonoGame не позволяет более гармоничным способом адаптировать приложение к разным экранам.
Таким образом, получается следующий код инициализации класса «игра»:
Обновление параметров экрана и коррекция координат X и Y (также учитывается тот факт, что разрешение экрана может быть не 16:9, а любое другое, в таком случае координаты центрируются по середине):
Схема отрисовки:
Альтернативных вариантов можно придумать много. Например, заранее подготовить всю графику в 16:10 и использовать черные рамки по вертикали если экран 16:10, либо предусмотреть обрезку и избежать рамок. В другом своем проекте, где все нужно сделать совсем без недочетов (так, чтобы прямо действительно совсем), я как раз использую вариант без рамок — он, к сожалению, более трудоемкий.
- SpriteBatch.Draw (TextureBackground, new Vector2 (AbsoluteX (0), AbsoluteY (0)), new Rectangle (0, 0, TextureBackground.Width, TextureBackground.Height), Color.White, 0, new Vector2 (0, 0), 1 * Dx, SpriteEffects.None, 0);
- TextureBackgroud — сама текстура (Texture2D)
- new Vector2(AbsoluteX (0), AbsoluteY (0)) — установка координат (обязательно прогонять через наши методы AbsoluteX и AbsoluteY!)
- new Rectangle (0, 0, TextureBackground.Width, TextureBackground.Height) — установка обрезки текстуры (без обрезки: левый верхний угол имеет координаты [0;0], а правый нижний угол координаты [ширина текстуры; высота текстуры])
- Color.White — наложение цветового слоя на текстуру
- new Vector2 (0, 0) — точка, относительно которой происходит отрисовка текстуры, в данном случае это верхний левый угол [0;0] (если бы нам был нужен центр, то можно указать [TextureBackground.Width/2; TextureBackground.Height/2])
- 1 * Dx — масштабирование текстуры (т.е. любая наша текстура должна увеличиваться в Dx раз вне зависимости от того, нужно ли дополнительное масштабирование, к примеру 2 * Dx, 3 * Dx, 0.5f * Dx . )
Суть такова: кнопка может существовать в двух режимах — нажатом и не нажатом, для каждого используется своя текстура. Пусть не нажатый режим использует текстуру TextureButton, а нажатый — TextureButtonLight (можно назвать TextureButtonPressed, просто в прошлом проекте у меня кнопки «подсвечивались» при нажатии, отсюда осталось Light). Сперва нужно обработать отрисовку кнопки через Process и обновление ее логики через Update, затем проверить, была ли она нажата.
На самом деле цепочка действий немного сложнее: кнопка «зажата», значит IsPressed становится true и вместо TextureButton отрисовывается TextureButtonLight, затем может быть так, что игрок передвинет с нее палец и отпустит (не захотел нажимать, либо случайно задел и сразу сдвинул палец), тем самым IsPressed снова станет false, а вот если игрок отпустит кнопку и в предыдущий момент времени палец будет на ней, то IsEnabled станет true и можно выполнять все действия по непосредственному назначению кнопки).
На примере кнопки паузы:
Интереснее пример с кнопкой посещения сайта:
Для всех кнопок нужно подготовить и загрузить текстуры. В процессе было выяснено, что понадобятся следующие кнопки: «Начать игру», «Новая игра», «Пауза», «Продолжить», «Сыграть заново», «Выход» (+ссылка на сайт, отображающаяся в меню).И, что не маловажно, кнопки управления со стрелками, которые будут располагаться по экрану.
Получается вот такая картина: 
В конце урока есть ссылка на GitHub, там собран весь проект, включая данные текстуры. Сначала создавались текстуры кнопок в нажатом состоянии (%name%Pressed.png), затем путем уменьшения в 0.8-0.9 раз и повышением прозрачности получалась обычная текстур. То есть изначально игрок видит чуть уменьшенные полупрозрачные надписи, при нажатии они увеличиваются и имеют четкие очертания. Помним: текстуры с префиксом «ru_» кладем в папку «ru», а текстуры с префиксом «en_» в папку «en», не забывая при том, что нужно добавлять их через контент-менеджер (панель слева).
Теперь интересный момент — загрузка текстур! Объявим в классе Game следующие поля:
По логике MonoGame/XNA есть один метод для загрузки LoadContent(), автоматически вызывающийся в самом начале. У него есть серьезный недостаток: когда текстур много, загрузка происходит довольно долго, игрок видит черный экран, что совсем нехорошо. Поэтому давайте сначала загрузим текстуру загрузочного экрана Splash, покажем ее, и лишь после этого начнем загружать остальные текстуры.
Для этого введем:
Метод LoadContent примет вид:
Что за странная переменная Language? Это строковое значение, равное «en» или «ru», про него упомянем чуть ниже. Таким образом, изначально загружается одна лишь текстура Splash. Тогда в методе Draw необходимо реализовать следующую логику:
Такой вот длинный код. Зато — избавляет от подвисания при загрузке, показываем загрузочный экран с названием игры и надписью «загрузка. », тем самым давая игроку подтверждение, что его телефон или планшет не завис.
Теперь по поводу использованной переменной Language. Как мы уже упомянули, она имеет значение «ru» или «en». В инициализации PacManGame нужно осуществить следующие действия:
То есть: считываем из системы текущий язык, и в зависимости от полученного значения присваиваем переменную Language и ряд других (такая длинная процедура с locale.Substring нужна для того, чтобы отделить от всех многочисленных en-US, en-GB и остальных просто частицу en). В принципе, то же самое можно было реализовать через strings.xml, но поскольку используемых строк немного, а игра всего лишь на двух языках, то можно обойтись и простым присваиванием прямо в коде.
Строки отрисовываются шрифтом Font.xnb. MonoGame поддерживает только скомпилированные *.xnb, а не XNA-шные *.spritefont, так что пришлось отдельно компилировать через XNA (см. оба файла на GitHub).
Полный код Game.cs, с комментариями:
Остановимся немного подробнее на методе Update — он выполняется параллельно Draw и удобен для обработки управления, в частности, кнопкой «назад» на самом телефоне или планшете (любое использование GamePad в пределах Draw приведет к зависанию).
Если нажата кнопка «назад» во время игры, то включается пауза, если во время паузы, то происходит возвращение в игру.
Важный момент, на котором тоже следует остановиться, это сохранение рекордов. Для этого я написал класс Game.Score.cs, который также использую во всех своих проектах с минимальными изменениями (на MonoGame подобные приемы очень удобны).
Для считывания и записи значения счета я использую отдельный класс и сериализацию. Конечно, чтобы хранить одно лишь значение это неэффективно и избыточно с технической точки зрения. Но что, если нужно сразу хранить весь перечень рекордов, да еще и из разных режимов игры? С этим приходилось сталкиваться неоднократно, сериализация очень выручает.
Запись в память мобильного устройства решает атрибут WriteExternalStorage, который мы выставили в самом начале.

Заключение
В принципе MonoGame прост и удобен для создания двухмерных игр, недостатком является отсутствие автоматизации многих типичных задач (даже тут с ними пришлось столкнуться: подгрузка контента, кнопки, сохранение рекордов). Если вы раньше работали с XNA Game Studio, многие вещи покажутся знакомыми, простыми и понятными, у вас есть все возможности для кроссплатформенной реализации своих проектов. Если нет (и, в особенности, если хотите делать трехмерные игры) — рассмотрите возможности других движков, к примеру, Unity.
В Google Play пользователям игра нравится, во всяком случае пока 🙂
Скриншоты получившейся игры тут и тут.
Полные исходники смотрите на GitHub
Пишем игру «Жизнь» под Android используя C++ и Qt за час.п»ї
Вы когда нибудь хотели программировать на си/c++ на смартфоне или планшете? Я как только заполучил Android устройство сразу захотел иметь компилятор под рукой, чтобы можно было быстро что нибудь скомпилить или протестировать. Несмотря на отсутствие физической клавиатуры это удобно иметь возможность в нужный момент набросать программу вытянув телефон из кармана. В GooglePlay есть несколько компиляторов с графическим интерфейсом например C4Droid и даже полноценная среда разработки TerminalIDE, но хочется чего нибудь простого, такого как gcc в Linux. И это возможно. Установка gcc в Android вполне возможна если в вас есть рут права.
Можно конечно самому компилить gcc из исходников или скачать готовые файлы и закинуть их в /system/xbin но мы поступим по другому, воспользуемся пакетным менеджером BootBrew.
Установка BootBrew
Bootbrew можно скачать в GooglePlay или с форума 4pda. После установки запустите программу и Нажмите кнопку procced для загрузки данных и инициализации приложения:

После окончания инициализации можно приступать к установке GCC.
Установка gcc в Android
Сначала обновим список пакетов:

Затем останется найти gcc в списке пакетов и нажать кнопку install.
Если все прошло так то вы счастливчик. Но у многих и в том числе и у меня во время обновления списка пакетов возникает ошибка update failed. Как я понял это из-за того что программа работает не от суперпользователя и следовательно не имеет прав на запись необходимой информации. Поэтому тем кому не повезло для управления пакетами придется использовать консоль. И так откройте Terminal Emulator, только не говорите что у вас его еще нет 🙂 и набирайте следующие команды:
получаем права суперпользователя:
обновляем список пакетов:
Вот и все, если надумаете удалить gcc наберите:
Посмотреть список всех доступных для установки пакетов можно командой:
Тестирование gcc
Наберите небольшую программу, например:
Сохраните в файле с именем main.c и наберите для компиляции:
В Android можно выполнять только в определенных папках поэтому перемонтируйте /system/bin в режим чтения и скопируйте туда программу:
$ cp ./program /system/bin/
Дайте права на выполнение:

Автор оригинала: Richard A.
- Перевод
- Tutorial
На момент написания этого текста текущей версией Android Studio была версия 1.0.1. Компилятор Intel C++ Compiler for Android, будучи частью Intel Integrated Native Developer Experience (Intel INDE), поддерживает Android Studio 1.0.1 в Intel INDE 2015 Update 1. Поскольку Android Studio 1.0.1 не поддерживает Android NDK, в этой статье описаны шаги для создания нативного Android-приложения с использованием Android NDK r10d и компилятора Intel C++ Compiler for Android. Поехали! Android Studio в качестве системы сборки использует Gradle. На момент написания статьи Gradle в процессе сборки вызывала систему NDK. В Android NDK r10 и более поздней версии после инсталляции Intel INDE компилятор Intel C++ Compiler for Android (ICC) не является более используемым по умолчанию компилятором в системе сборки NDK. Представленные ниже шаги дают общее описание подготовки, сборки и выполнения нативного приложения в Android Studio 1.0.1. Если вы используете Android Studio 0.8.6, почитайте статью «Сборка нативных Android-приложений с использованием компилятора Intel C++ Compiler в Android Studio», где рассказывается о применении Intel C++ Compiler.
Требуемые программные инструменты
Успешная инсталляция INDE 2015 Update 1 с Android Studio Integration гарантирует, что вы располагаете всем необходимым программным обеспечением. Детальные требования к программному обеспечению и системе описаны в документе Intel C++ Compiler Release Notes for Intel Integrated Native Developer Experience 2015. Приведенные в данной статье инструкции предполагают использование следующего ПО для архитектуры IA-32 и Intel-64:
- Oracle JDK 7 (нативный Intel-64 JDK для систем Windows x64)
- Android SDK 20 или старше
- NDK r10d (предполагается инсталляция в [ndk-dir] )
- Android Studio 1.0.1
Убедитесь, что в переменную среды PATH добавлены следующие директории Android NDK (если они отсутствуют, добавьте):
- C:IntelINDEIDEintegrationNDKbuildtools
- C:IntelINDEIDEintegrationNDK
Использование компилятора Intel C++ Compiler в Android Studio 1.0.1
После инсталляции Intel C++ Compiler 15.0 for Android в папку «[ndk-dir]toolchains» (директория по умолчанию – «C:IntelINDEIDEintegrationandroid-ndk-r10dtoolchains») устанавливаются следующие элементы:
- x86-icc
- x86-icc15.0.X.YYY
- x86_64-icc (если NDK поддерживает 64-разрядные цели)
- x86_64-icc15.0.X.YYY (если NDK поддерживает 64-разрядные цели)
Для NDK до r9d: после инсталляции нативным компилятором C/C++ будет Intel C++ Compiler. Дополнительных шагов для использования Intel C++ Compiler с Android Studio не требуется. Ели вы хотите для построения нативного кода использовать GNU gc, следуйте инструкциям в «Замена компилятора по умолчанию с Intel C++ Compiler на GCC для целевых приложений x86».Для NDK r10 — r10d: компилятор Intel C++ после инсталляции не является компилятором по умолчанию. Чтобы использовать Intel C++ Compiler из Android Studio, следуйте шагам 3, 4, 5 ниже. Если у вас установлено несколько Android NDK, следуйте инструкциям в статье «Интеграция Intel C++ Compiler for Android с несколькими Android NDK». Теперь создадим новый проект Android для Intel-64 с нативным вызовом функции, продемонстрировав тем самым использование компилятора Intel C++ Compiler:
1. Создание нового проекта Android с нативным интерфейсом:
1. Откройте Android Studio, создайте новый Android-проект «nativeDemo» с параметрами по умолчанию, например: 2. Откройте «appsrcmainjavaMainActivity.java» и добавьте в конец класса «MainActivity» нативную функцию как показано ниже:
Должно получиться примерно так:3. Чтобы создать проект «nativeDemo», выберите: «Build > Make Project», после чего можно будет использовать «javah».4. В «View > Tools Windows > Terminal» откройте терминальное окно и выполните указанные ниже шаги для запуска «javah» и создания заголовка jni: a) В терминальном окне перейдите в подпапку «srcmain»:
b) Чтобы создать «com_example_nativedemo_app_MainActivit.h» в папке srcmainjni, выполните следующую команду «javah» cmd:
Пример:5. В окне Project измените вид на Project, щелкните правой кнопкой мыши на папке «src» и выберите «Synchronize ‘src». Теперь в папке «srcmainjni» можно видеть файл заголовка «com_example_mydemo_nativedemo_MainActivity.h».
2. Добавление нативного исходного кода: main.c
1. Создайте «main.c»: выделите файл «com_example_mydemo_nativedemo_MainActivity.h», используйте волшебные клавиши copy/paste для создания нового «main.c» со следующим кодом:
2. Сохраните изменения3. Теперь в папке «jni» у вас два файла: com_example_mydemo_nativedemo_MainActivity.h и main.c
3. Добавление make-файла: Android.mk
1. Щелкните правой кнопкой на папке «jni», выберите «New > File»2. Наберите «Android.mk» и щёлкните на «OK»3. Добавьте в этот файл следующие строки (обратите внимание, что строка LOCAL_SRC_FILES должна содержать исходные файлы, которые находятся в папке «jni»):
4. Добавление make-файла: Application.mk
1. Щелкните правой кнопкой на папке «jni», выберите «New > File»2. Наберите «Application.mk» и щёлкните на «OK»3. Добавьте в этот файл следующие строки:
4. [Опционально] Для изменения параметров компилятора используйте следующее:
5. Сконфигурируйте свое приложение для выполнения ndk-build с помощью make-файлов
1. Откройте файл «appbuild.gradle» 2. Добавьте в начало файла следующий импорт:
3. И эти строки после секции «defaultConfig»:
4. А следующие строки – в конец файла:
5. Сохраните изменения6. Теперь создадим проект: выберите [Build > Make Project]. Вы увидите все выходные папки и файлы «libmain.so» в папках «mainlibs» и «mainobjlocal».
6. Добавьте ID «hello_textview» в виджет textview
Откройте «reslayoutactivity_main.xml» и измените виджет «textview» как показано ниже:
7. Обновите «MainActivity.java», чтобы UI textview вызов нативной библиотеки:
Для импорта виджета TextView и сохранения изменений нажмите ALT+Enter.
8. Запустите виртуальную машину Android Virtual Machine «Intel-Nexus 7 x64» и выполните приложение, щёлкнув кнопку «Run».
Это означает, что был использован компилятор Intel C++ Compiler for Android.
The end
Теперь вы знаете, как с помощью Intel C++ Compiler в Android Studio создать нативное Android-приложение. Попробуйте запустить его и сообщите нам, что оно делает. Monodroid и Monotouch это фреймворки от xamarin, которые дают возможность разрабатывать приложение на языке C# для Android и iOS соответственно. Так как это относительно новая технология информации в интернете не слишком много (за исключением офф сайта и большого количества тем на stackoverflow.com), на русском языке же я не нашел никаких туториалов и информации вообще. Что бы устранить это недоразумение решил написать небольшой туториал о том как начать разрабатывать приложения под мобильные платформы при помощи этих фреймворков. В этой статье я рассмотрю только Monodroid. Что нужно для начала разработки? 1) Visual studio C# версии professional и выше (пойдет и крякнутая) 2) Сам фреймворк (а он, в свою очередь установит за нас и джаву, и виртуальную машину и все остальное) Если с первым все понятно, то со вторым сложнее- как оказалось в процессе использования фришная версия монодроида не умеет компилировать .apk файлы, так что стоит либо его купить, либо воспользоваться кряком из интернета (который лежит далеко не на первой странице гугла).Начнем Запустив после установки всего необходимого студию мы заметим новые типы проектов для создания: Выбираем Android Application. Будет создано несколько стандартных директорий: В папке Assets хранятся все используемые программой файлы аля видео, звуков, хтмл файлов и т.д. Изначально в ней ничего нет кроме текстовика с описанием самой директории и для чего она нужна. В папке Drawable нужно размещать файлы изображений, используемые программой. Папка Layout содержит файлы графического интерфейса. В папке Values различные параметры, которые можно создавать и загружать при работе приложения (к примеру можно запомнить логин и пароль туда). После создания пустого проекта мы можем его скомпилировать, нажав F5 — откроется настройки виртуальной машины с выбором устройства на котором запускать тест программы. Если подсоединить свой девайс на андроиде со включенной функцией отладки по USB (это в настройках, вкладка «для разработчика») то можно запустить и потестить непосредственно на нем. Очень советую проводить тесты на реальном устройстве т.к. многие тач элементы нельзя протестировать на виртуалке, к тому же, лично у меня на виртуальной машине приложение развертывается довольно долго. Между компиляцией и запуском на виртуалке проходит около минуты. По умолчанию мы имеем кнопку, при нажатии на которую будет выведено сколько раз мы кликнули по оной. Попробуем сделать что-нибудь поинтереснее. Откроем файл интерфейса и попробуем его поменять. Снизу 2 вкладки- просмотр кода интерфейса и самого интерфейса. Справа- различные компоненты. Сразу скажу: пользоваться встроенной формоклепалкой это не для слабонервных. Она на столько лагуча и делает не то, что ожидаешь что просто ужас. Вместо этого можно пользоваться сайтом droiddraw.org после составления интерфейс там и нажатие на кнопку Generate можно скопипастить код во вкладку кода и все будет ок. А в прочем, самый лучший способ это создавать интерфейс из кода, посредством формул исходя из соотношений разрешения экрана. Без этого нормальный интерфейс, привлекательно смотрящийся на всех экранах создать сложно. Однако, пока пойдем по простому пути. Есть несколько типов расположения объектов на экране- типов слоев. При этом многие из них могут содержать друг друга и уживаться вместе. Рассмотрим из них: 1) LinearLayout — каждый элемент ниже предыдущего. 2) RelativeLayout — каждый элемент находится по отношению к предыдущему (к примеру, можно задать параметр находиться слева от кнопки1, снизу от текстбокса, в отступе в 40 пикселей снизу от кнопки 2 и т.д. Настройки каждого компонента у нас в окне свойств- все довольно привычно, а сами свойства схожи с винформ элементами. Создав более или менее привлекательный интерфейс его надо как то запустить. Для этого существует Activity. По умолчанию у нас файл с названием Activity1, в котором класс- наследник от класса Activity. Строка над объявлением класса- [Activity(Label = «AndroidApplication1», MainLauncher = true, Icon = «@drawable/icon»)] описывает заголовок окна приложения, иконку и узнает запускать ли эту активити при запуске приложения. Из основной автозапускаемой активити можно запустить любую другую. После автозагрузки данной активити после старта приложения мы загружаем интерфейс строкой SetContentView(Resource.Layout.Main); Для получения доступа к любому элементу мы должны воспользоваться функцией FindViewById(); при присвоении экземпляру класса таго элемента, который нам нужен. Конкретно в нашем примере мы видим строчку FindViewById(Resource.Id.MyButton); «MyButton» это название кнопки, посмотреть его можно при создании интерфейса во вкладке код. Посредством простой конструкции Button button = FindViewById(Resource.Id.MyButton); мы можем работать с кнопкой и обрабатывать все действия с ней. В данном случае обработчик клика выглядит так: button.Click += delegate < button.Text = string.Format(«<0>clicks!», count++); >; Спроектировав и написав приложение мы моем скомпилировать apk файл посредством перехода во вкладку построение и нажатии кнопки Package for android. В папке проекта появится 2 файла, один из которых подписан автоматической подписью- его мы и можем использовать для установки па устройство. На этом, думаю, этот краткий туториал можно закончить. В будущем скорее всего напишу подобный туториал по портированию и разработке на iOS. 420 231.6k 420 3 года назад
Всем привет! Недавно видел на пабликах типа ТП посты мол «делаем игру жизнь на java за час», «делаем игру жизнь на python за час», вот решил сделать пост, делаем игру жизнь под андроид используя C++ и библиотеки Qt за час.
Важно отметить что игра занимает
100 строчек кода(исключая h файлы, символы переноса и т.п.), а также то, что созданная игра будет работать и под винду, и под андроид, и под линукс, и под ios и под mac os x.В
И так начнем. Правила игры жизнь я думаю вы знаете. Есть поле с клетками. Клетка может быть живая или мертвая. По прошествии хода, если вокруг мертвой клетки есть 3 живых, она становится живой. Если вокруг живой клетки будет 1 или 0 живых, то она становится мертвой, также она становится мертвой если вокруг нее более 3 живых клеток. Впринципе все, подробнее вы можете прочесть на википедии.
И так что у нас должно быть:
Поле с N*M клеток, кнопка «Новая игра», кнопка «Начать эволюцию». Поле забито прямогуольниками, изначально все пустые. При нажатии на клетку она окрашивается в черный или белый цвет, в зависимости от того какой цвет был там раньше.
2. Логика. Мы должны иметь двумерный массив(используем QT’шный вектор), в котором 0 отмечена мертвая клетка а единицей живая клетка. У меня была цель создать игру примерно за час, и поэтому этот 0 и 1 соответствуют черному и белому цветам многоугольников. Соответственно у нас есть массив с указателями на наши прямоугольники. Это не хорошо, ибо логика должна быть отделена от GUI, но для уменьшения размера кода — решил сделать так.
Должны быть реализованы функции подсчета соседей для каждой из клеток, зная положение клетки по X и Y. После каждого хода, мы рассчитываем количество соседей для каждой из клеток, и в зависимости от состояния клетки и количества её соседей добавляем клетку в массив(вектор), для изменения её состояния. Если за 1 ход не было добавлено ни 1 клетки, то игра заканчивается, выводим пользователю количество ходов
Если вы не знакомы с наследованием и библиотекой STD в C++, то вы являетесь новичком в C++ и возможно код покажется вам запутанным. К сожалению не хотелось бы писать про все это в этом посте, ибо для новичков информации в интернете — тонны, а переписывать еще раз это — просто не зачем. Если вы с этим знакомы, но незнакомы с сигнально-слотовой системой Qt тоже порекомендую прочитать over9000 материалов по этой теме. Если потребуются пояснения я отвечу в комментах.
И так, как выглядит GUI:
Скачиваем Qt Creator с официального сайта Qt(qt.io), выбираем бесплатный вариант для GPL приложений. Запускаемся, создаем новый проект, используя QWidget, с графической формой
Переходим к созданию. Переходим в режим дизайн и добавляем на форму «Graphics View» и 2 Push Button. Примерно вот так как на следующей картинке, и кликая правой кнопкой мыши по форме выбираем (Скомпоновать по сетке)
GUI готово! Теперь нам нужно перейти к непосредственно программированию.
QGraphicsView — это виджет, который может отображать графические элементы. QGraphicsView отображает элементы, который расположены на QScene. На QScene мы размешаем элементы в декартовой системе координат, а с помощью QGrahicsView мы её отображаем(можно поворачивать, увеличивать масштаб и т.п.).
Элементы бывают разных типов. Есть простые — QGraphicsRectItem — это многоугольник, который к слову нам и понадобиться. Есть QGraphicsPixMapItem который отобразит нам изображение. ЕстьВ QGraphicsLineItem — это линия. И другие.В
И так, первая задача. Разместить на сцене N*M QGraphicsRectItem’ов — наших клеток. Мертвой клеткой будет считаться клетка, с Kletka->brush().color() == Qt::white живой Kletka-> brush().color() ==В Qt::black. И запихнуть все указатели наВ QGraphicsRectItem в отдельный двумерный вектор, чтобы мы могли с ними работать.
Однако, для этой задачи нам не подойдет «чистый»В QGraphicsRectItem. Нужно будет создать класс наследник, и в нем реализовать свою обработку нажатия на клетку. Ведь в самом начале игры нам нужно будет выставить первичные мертвые и живые клетки на поле.
Т.е. по нажатию на наш прямоугольник, он должен окрашиваться в требуемый цвет. А у чистого QGraphicsRectItem обработка данного события пустая.
Создадим класс LifeRect основанный на QGraphicsRectItrem., и переопределим в немВ mousePressEvent
Теперь займемся реализацией. Конструктор будет использовать конструктор с такими же параметрами как у QGraphicsRectItem(позиция по x, позиция по y, широта, высота, родитель). В событии mousePressEvent будет вызываться функция changeColor.
И так, переходим дальше. Работаем с нашим виджетом(формой) — В widget.c. В этом классе реализуется логика игры.
Что нам необходимо?
1. Таймер. Каждые 0.1 секунды будет происходить 1 ход.
2. Слоты нажатия на кнопку «Старт» и «Новая игра» соответственно.
3. Размеры поля — X и Y клеток. Принимаются через конструктор.
4. Переменная для хранения количества итераций(для вывода при завершении игры)
5. Двумерный вектор(используется QVector, а не std::vector) который хранит указатели на созданные нами LifeRect’ы. К его элементам мы будем обращаться, и будем представлять живое и мертвое состояние по его цвету, как я уже и писал ранее. Если среди читателей будут новички C++ в комментах могу помочь. Просто представляйте что это массив.
6. Функция для новой игры(обнуление поля, обнуления количества итераций).
7. Функция подсчета живых соседей рядом.
И так, начнем. Собственно вот заголовочный файл:
В конструкторе присвоим начальные данные(x,y из входных, 0 итераций, укажем то что сцена пока-что пустая, родительский элемент отсутствует. Проинициализируем таймер, но запускать его не будем
Создать слот on_pushButton нужно нажав правой кнопкой по кнопке на форме, выбрать перейти к слоту, и выбрать сигнал clicked()
Дальше — интереснее. И так, по нажатию на кнопку новая игра, мы должны нарисовать графическую сцену, с X*Y мертвыми многоугольниками, и наполнить двумерный вектор указателей на многоугольники ими же. Чтобы потом,когда игрок заполнит поле и нажмет на В кнопку «СТАРТ» запускается таймер, по каждому тику таймера обходить этот вектор, вычислить количество живых соседей каждой клетки, и перерисовать требуемые.
Постарался максимально прокомментировать код.В
К слову говоря, позиции осей X и Y в графической сцене таковы:
И так, мы прорисовали свои элементы, и уже можем потыкать и поменять их цвета, но пока-что не можем ничего запустить. Нужно реализовать функцию обработки каждого хода. И написать что по 2 кнопке мы запускаем таймер.
Осталось лишь только реализовать функцию подсчета соседей. Думаю, в ней проблем не будет. Мы просто смотрим на цвета наших 8 соседей(если вылезли за границы то перемещаемся в начало или конец соответственно) и выдаем на выход их количество
Все готово! Запускаем и играем на Linux, Windows или вашем Mac.
Что потребуется(если вкратце) для запуска на Android?
1. Пакеты Android SDK и Android NDK(для C++).
2. Устройство андроид или AVD.
Вы должны сконфигурировать ваш Qt Creator, в таком виде(ваши пути), и добавить в проектах сборку Android. Потребуется скачать в AVD драйвера USB, и API требуемых версий Android. Затем уже попробовать установить приложение на телефон/эмулятор. Я могу помочь, или привести ссылки где это подробно разбирается в комментах